Repository: miniLV/Interview-series Branch: master Commit: 262275f6ba6b Files: 267 Total size: 3.1 MB Directory structure: gitextract_jkwm6bgm/ ├── .gitignore ├── BlockFile.md ├── InterView-obj-isa-class/ │ ├── InterView-obj-isa-class/ │ │ ├── NSObject+MNTest.h │ │ ├── NSObject+MNTest.m │ │ └── main.m │ ├── InterView-obj-isa-class.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── libmalloc-166.220.1/ │ │ ├── .clang-format │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── include/ │ │ │ └── malloc/ │ │ │ ├── _malloc.h │ │ │ └── malloc.h │ │ ├── libmalloc.xcodeproj/ │ │ │ └── project.pbxproj │ │ ├── man/ │ │ │ ├── malloc.3 │ │ │ ├── malloc_size.3 │ │ │ ├── malloc_zone_malloc.3 │ │ │ └── manpages.lst │ │ ├── private/ │ │ │ ├── malloc_private.h │ │ │ └── stack_logging.h │ │ ├── resolver/ │ │ │ ├── resolver.c │ │ │ ├── resolver.h │ │ │ └── resolver_internal.h │ │ ├── src/ │ │ │ ├── base.h │ │ │ ├── bitarray.c │ │ │ ├── bitarray.h │ │ │ ├── debug.h │ │ │ ├── dtrace.h │ │ │ ├── empty.s │ │ │ ├── frozen_malloc.c │ │ │ ├── frozen_malloc.h │ │ │ ├── internal.h │ │ │ ├── legacy_malloc.c │ │ │ ├── legacy_malloc.h │ │ │ ├── locking.h │ │ │ ├── magazine_inline.h │ │ │ ├── magazine_large.c │ │ │ ├── magazine_lite.c │ │ │ ├── magazine_malloc.c │ │ │ ├── magazine_malloc.h │ │ │ ├── magazine_rack.c │ │ │ ├── magazine_rack.h │ │ │ ├── magazine_small.c │ │ │ ├── magazine_tiny.c │ │ │ ├── magazine_zone.h │ │ │ ├── magmallocProvider.d │ │ │ ├── malloc.c │ │ │ ├── malloc_common.c │ │ │ ├── malloc_common.h │ │ │ ├── malloc_printf.c │ │ │ ├── nano_malloc.c │ │ │ ├── nano_malloc.h │ │ │ ├── nano_malloc_common.c │ │ │ ├── nano_malloc_common.h │ │ │ ├── nano_zone.h │ │ │ ├── nano_zone_common.h │ │ │ ├── nanov2_malloc.c │ │ │ ├── nanov2_malloc.h │ │ │ ├── nanov2_zone.h │ │ │ ├── platform.h │ │ │ ├── printf.h │ │ │ ├── purgeable_malloc.c │ │ │ ├── purgeable_malloc.h │ │ │ ├── radix_tree.c │ │ │ ├── radix_tree.h │ │ │ ├── radix_tree_debug.c │ │ │ ├── radix_tree_internal.h │ │ │ ├── stack_logging_disk.c │ │ │ ├── stack_logging_internal.h │ │ │ ├── thresholds.h │ │ │ ├── trace.h │ │ │ ├── vm.c │ │ │ └── vm.h │ │ ├── tests/ │ │ │ ├── Makefile │ │ │ ├── MallocBench/ │ │ │ │ ├── Benchmark.cpp │ │ │ │ ├── Benchmark.h │ │ │ │ ├── CPUCount.cpp │ │ │ │ ├── CPUCount.h │ │ │ │ ├── CommandLine.cpp │ │ │ │ ├── CommandLine.h │ │ │ │ ├── Interpreter.cpp │ │ │ │ ├── Interpreter.h │ │ │ │ ├── balloon.cpp │ │ │ │ ├── balloon.h │ │ │ │ ├── big.cpp │ │ │ │ ├── big.h │ │ │ │ ├── churn.cpp │ │ │ │ ├── churn.h │ │ │ │ ├── fragment.cpp │ │ │ │ ├── fragment.h │ │ │ │ ├── list.cpp │ │ │ │ ├── list.h │ │ │ │ ├── mbmalloc.cpp │ │ │ │ ├── mbmalloc.h │ │ │ │ ├── medium.cpp │ │ │ │ ├── medium.h │ │ │ │ ├── memalign.cpp │ │ │ │ ├── memalign.h │ │ │ │ ├── message.cpp │ │ │ │ ├── message.h │ │ │ │ ├── realloc.cpp │ │ │ │ ├── realloc.h │ │ │ │ ├── stress.cpp │ │ │ │ ├── stress.h │ │ │ │ ├── stress_aligned.cpp │ │ │ │ ├── stress_aligned.h │ │ │ │ ├── tree.cpp │ │ │ │ └── tree.h │ │ │ ├── MallocBench.cpp │ │ │ ├── asan.c │ │ │ ├── basic_malloc_free_perf.c │ │ │ ├── calloc_test.c │ │ │ ├── libmalloc_tests.xcodeproj/ │ │ │ │ └── project.pbxproj │ │ │ ├── madvise.c │ │ │ ├── magazine_malloc.c │ │ │ ├── magazine_rack.c │ │ │ ├── magazine_small_test.c │ │ │ ├── magazine_testing.h │ │ │ ├── magazine_tiny_test.c │ │ │ ├── malloc_claimed_address_tests.c │ │ │ ├── malloc_free_test.c │ │ │ ├── malloc_size_test.c │ │ │ ├── nano_tests.c │ │ │ ├── perf_contended_malloc_free.c │ │ │ ├── perf_realloc.c │ │ │ ├── radix_tree_test.m │ │ │ ├── reallocarray.c │ │ │ ├── stack_logging_test.c │ │ │ ├── stress_test.c │ │ │ └── tsan.c │ │ ├── tools/ │ │ │ ├── malloc_replay.cpp │ │ │ ├── malloc_replay.h │ │ │ ├── malloc_replay_plotter.py │ │ │ ├── radix_tree_main.m │ │ │ └── read-radix-tree │ │ ├── xcodeconfig/ │ │ │ ├── interposable.list │ │ │ ├── libmalloc.dirty │ │ │ ├── libmalloc.xcconfig │ │ │ ├── libmalloc_eos.xcconfig │ │ │ ├── libmalloc_resolved.xcconfig │ │ │ ├── libmalloc_resolver.xcconfig │ │ │ └── libmalloc_static.xcconfig │ │ └── xcodescripts/ │ │ ├── install-codes.sh │ │ ├── manpages.sh │ │ ├── reindent.sh │ │ └── sanitise_headers.sh │ └── objc4-750/ │ ├── APPLE_LICENSE │ ├── ReleaseNotes.rtf │ ├── interposable.txt │ ├── libobjc.order │ ├── markgc.cpp │ ├── objc.sln │ ├── objc.vcproj │ ├── objc.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── objcrt/ │ │ └── objcrt.vcproj │ ├── prebuild.bat │ ├── runtime/ │ │ ├── Messengers.subproj/ │ │ │ ├── objc-msg-arm.s │ │ │ ├── objc-msg-arm64.s │ │ │ ├── objc-msg-i386.s │ │ │ ├── objc-msg-simulator-i386.s │ │ │ ├── objc-msg-simulator-x86_64.s │ │ │ ├── objc-msg-win32.m │ │ │ └── objc-msg-x86_64.s │ │ ├── Module/ │ │ │ ├── ObjectiveC.apinotes │ │ │ └── module.modulemap │ │ ├── NSObjCRuntime.h │ │ ├── NSObject.h │ │ ├── NSObject.mm │ │ ├── Object.h │ │ ├── Object.mm │ │ ├── OldClasses.subproj/ │ │ │ ├── List.h │ │ │ └── List.m │ │ ├── Protocol.h │ │ ├── Protocol.mm │ │ ├── arm64-asm.h │ │ ├── hashtable.h │ │ ├── hashtable2.h │ │ ├── hashtable2.mm │ │ ├── isa.h │ │ ├── llvm-AlignOf.h │ │ ├── llvm-DenseMap.h │ │ ├── llvm-DenseMapInfo.h │ │ ├── llvm-MathExtras.h │ │ ├── llvm-type_traits.h │ │ ├── maptable.h │ │ ├── maptable.mm │ │ ├── message.h │ │ ├── objc-abi.h │ │ ├── objc-accessors.mm │ │ ├── objc-api.h │ │ ├── objc-auto.h │ │ ├── objc-auto.mm │ │ ├── objc-block-trampolines.h │ │ ├── objc-block-trampolines.mm │ │ ├── objc-blocktramps-arm.s │ │ ├── objc-blocktramps-arm64.s │ │ ├── objc-blocktramps-i386.s │ │ ├── objc-blocktramps-x86_64.s │ │ ├── objc-cache-old.h │ │ ├── objc-cache-old.mm │ │ ├── objc-cache.h │ │ ├── objc-cache.mm │ │ ├── objc-class-old.mm │ │ ├── objc-class.h │ │ ├── objc-class.mm │ │ ├── objc-config.h │ │ ├── objc-env.h │ │ ├── objc-errors.mm │ │ ├── objc-exception.h │ │ ├── objc-exception.mm │ │ ├── objc-file-old.h │ │ ├── objc-file-old.mm │ │ ├── objc-file.h │ │ ├── objc-file.mm │ │ ├── objc-gdb.h │ │ ├── objc-initialize.h │ │ ├── objc-initialize.mm │ │ ├── objc-internal.h │ │ ├── objc-layout.mm │ │ ├── objc-load.h │ │ ├── objc-load.mm │ │ ├── objc-loadmethod.h │ │ ├── objc-loadmethod.mm │ │ ├── objc-lockdebug.h │ │ ├── objc-lockdebug.mm │ │ ├── objc-locks-new.h │ │ ├── objc-locks-old.h │ │ ├── objc-locks.h │ │ ├── objc-object.h │ │ ├── objc-opt.mm │ │ ├── objc-os.h │ │ ├── objc-os.mm │ │ ├── objc-private.h │ │ ├── objc-probes.d │ │ ├── objc-ptrauth.h │ │ ├── objc-references.h │ │ ├── objc-references.mm │ │ ├── objc-runtime-new.h │ │ ├── objc-runtime-new.mm │ │ ├── objc-runtime-old.h │ │ ├── objc-runtime-old.mm │ │ ├── objc-runtime.h │ │ ├── objc-runtime.mm │ │ ├── objc-sel-old.mm │ │ ├── objc-sel-set.h │ │ ├── objc-sel-set.mm │ │ ├── objc-sel-table.s │ │ ├── objc-sel.mm │ │ ├── objc-sync.h │ │ ├── objc-sync.mm │ │ ├── objc-typeencoding.mm │ │ ├── objc-weak.h │ │ ├── objc-weak.mm │ │ ├── objc.h │ │ ├── objcrt.c │ │ ├── objcrt.h │ │ └── runtime.h │ ├── unexported_symbols │ ├── version.bat │ └── version.rc ├── LICENSE ├── README.md ├── category分析.md ├── isa&&Class&&meta-class.md └── 什么是NSObject.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output # Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ================================================ FILE: BlockFile.md ================================================ # Block > 面试驱动技术合集(初中级iOS开发),关注仓库,及时获取更新 [Interview-series](https://github.com/miniLV/Interview-series) ![](https://user-gold-cdn.xitu.io/2019/3/10/16967d7dc4bb1e67?w=1360&h=896&f=jpeg&s=158902) Block 在 iOS 算比较常见常用且常考的了,现在面试中,要么没面试题,有面试题的,基本都会考到 block 的点。 先来个面试题热热身,题目: **手撕代码 - 用Block实现两个数的求和** *(这题如果会的,block基础知识可以跳过了,直接到* Block原理探究) #### 简单介绍block入门级用法 Block结构比较复杂,一般用 typedef 定义,直接调用的感觉比较简单、清晰易懂 ``` //typedef block的时候有提示 typedef void(^MNBlock)(int); @interface ViewController () @property (nonatomic, copy) MNBlock block; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //直接用self.block调用 self.block = ^(int a) { //dosomething... }; } ``` - **参数解释:** `typedef <#returnType#>(^<#name#>)(<#arguments#>);` ![](https://user-gold-cdn.xitu.io/2019/3/6/16952561d5d28953?w=838&h=281&f=png&s=378402) **题目: 手撕代码 - 用Block实现两个数的求和** *日常开发中,block声明一般写的比较多,实现一般是靠Xcode自动补全提示出现的,手撕代码的情况下,等号右侧的block实现要怎么写?* 声明: ``` typedef int(^MNBlock)(int a, int b); @interface ViewController () @property (nonatomic, copy) MNBlock sum; ``` Vip补全功能: ![](https://user-gold-cdn.xitu.io/2019/3/6/1695258327b5d40d?w=338&h=41&f=png&s=4862) 纸上按Enter没用啊兄弟!看来还是需要了解一下Block右边的东西~ 先在 Xcode上按下 Enter,了解下再撕 ![](https://user-gold-cdn.xitu.io/2019/3/6/16952597e5c08f81?w=1251&h=80&f=png&s=13055) ``` ^int(int a, int b) { //Control reaches end of non-void block 因为返回值是int类型,所以这里需要返回 } ``` ![](https://user-gold-cdn.xitu.io/2019/3/6/169526002706cf63?w=926&h=516&f=png&s=681574) ``` int(^Sum)(int, int) = ^(int a, int b){ return a + b; }; int result = Sum(5, 10); ``` ![](https://user-gold-cdn.xitu.io/2019/3/6/16952676c9db9951?w=1030&h=285&f=png&s=504069) #### Block的坑出现!新手可能会写错的地方 1.声明出错 - `void ^(testBlock)` ![](https://user-gold-cdn.xitu.io/2019/3/6/169526924a0616d0?w=713&h=91&f=png&s=14053) 修正版: ``` void (^testBlock)() = ^{ }; ``` block的声明,^ 和 blockName 都是在小括号里面!! 2.block各种实现的参数问题 声明`typedef int(^MNBlock)(int, int);` ![](https://user-gold-cdn.xitu.io/2019/3/6/169526c0aac25322?w=773&h=180&f=png&s=39960) ``` self.sum = ^int(int a, int b) { return a + b; }; ``` 这里要注意,block声明里面只有参数类型,没有实际参数的话,Xcode提示也只有参数,这里涉及到形参和实参的问题 声明是形参,可以不写参数,但是使用的时候,必须有实际参数,才可以进行使用,所以这里需要实参,可以在 `^int(int , int)` 中手动添加实参`^int(int a, int b)`,就可以让a 和 b 参与运算 小tips:实际开发中,建议声明的时候,如果需要带参数,最好形参也声明下,这样使用Xcode提示的时候,会把参数带进去,方便得多~(踩过坑的自然懂!) 3. 省略void导致看不懂block结构的 *(正常是两个void导致局面混乱)* ``` //声明 typedef void(^MNBlock)(void); //实现 self.sum = ^{ //dosomething... }; ``` 这种情况下,能知道怎么省略的,声明里两个void,能知道怎么对应的吗? 这个其实比较简单,block不管声明 or 实现,最后一个小括号,里面都是参数,而参数是可以省略的! 而为了把声明的两个void区分开,返回值 or 参数区分开,其实就ok了 参数非void的例子 ``` //声明非void的参数 typedef void(^MNBlock)(int a); //实现就必须带参数,不可省略! self.sum = ^(int a) { } ``` 参数void的例子 ==> 参数可以省略 ``` typedef int(^MNBlock)(void); self.sum = ^{ //声明的返回值类型是int,所以一定要return; return 5; }; ``` 其实-返回值是void的,也可以不省略 ``` typedef void(^MNBlock)(void); //实现的返回值不省略 self.sum = ^void () { }; ``` 参数是void的省略: ``` typedef int(^MNBlock)(); //实现里面,没有参数,就可以不写() self.sum = ^int{ return 5; } ``` **注意!! 声明里面的返回值void是不可以省略的!!** ![](https://user-gold-cdn.xitu.io/2019/3/7/16956f058259665d?w=403&h=88&f=png&s=13283) 4. 小箭头^混乱的问题,到底放小括号内还是小括号外 声明是 `int(^MNBlock)(int a , int b)` 实现是 `^int(int a, int b)` 注意,这里箭头之后的,不管是多写() 还是少写,都会出错 ![](https://user-gold-cdn.xitu.io/2019/3/6/1695278e3061b90d?w=1020&h=103&f=png&s=23020) ![](https://user-gold-cdn.xitu.io/2019/3/6/16952795f17d3bb9?w=1038&h=191&f=png&s=32246) > 所以这里还不能死记,比如不管声明还是实现,死记 (^ xxx) 是没问题的 or 死记 ^…… xxx 不加括号是没问题的,在这里都行不通,只能靠脑记了 这时候,就需要用到巧记了! *^ 和小括号组合的,一共有三种情况* - 一种是声明的,`void(^MNBlock)` - 一种是实现的,`^int(int a,)` - 还一种 `^(int a)` 兄弟,看到这你还不乱吗!! ![](https://user-gold-cdn.xitu.io/2019/3/7/16956e251f2c8ce3?w=222&h=227&f=jpeg&s=6734) 怎么记看这里, - 手写分为两个部分,block等号左边 or 等号右边的,左边为声明,右边为实现区分开 - 声明记住:^后面跟blockName,他们需要包起来! (^blockName),只有声明会用到blockName,先记住一点,如果有blockName,要和^一起,用小括号包起来 - 实现又分为两种: - `^int`:^后面跟的是返回值类型 - ^ 直接跟类型,不用加"( )" ==> `^int` - `^(int a)`:^后面直接跟参数 *(返回值是void)*。 - 参数都是要用"( )"包起来的,如果^后面跟参数,就得用"( )" ==> `^(int a)`, - 实现里,肯定有实际参数,这时候,参数类型和实参,就得用( )包起来 ### ^与小括号纠缠的总结 - ^ 后面仅跟类型,不需要小括号,==> `^int` - ^ 后面跟参数,参数需要小括号 ==> `^(int a)` - ^ 后面跟block名称,^和blockName需要小括号 ==> `void (^MNBlock)`
## Block原理探究 ```objective-c void (^MNBlock)(void) = ^(void){ NSLog(@"this is a Block~ rua~"); }; MNBlock(); ``` 使用 `xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m` 转成 C++ 代码, 查看底层结构 ```objective-c //对应上面的 MNBlock声明 void (*MNBlock)(void) = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); //对应上面的 MNblock() 调用 MNBlock->FuncPtr(MNBlock); ``` ```objective-c //block声明调用的 - __main_block_impl_0 struct __main_block_impl_0 { //结构体内的参数 struct __block_impl impl; struct __main_block_desc_0* Desc; //c++中的构造函数,类似于 OC 的 init 方法,返回一个结构体对象 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } ``` 这里的block封装的函数调用解释`MNBlock->FuncPtr(MNBlock);` MNBlock 其实内部结构是 `__main_block_impl_0`, ``` struct __main_block_impl_0 { //函数调用地址在这个结构体内 struct __block_impl impl; struct __main_block_desc_0* Desc; } struct __block_impl { void *isa; int Flags; int Reserved; //函数调用地址在这里 void *FuncPtr; }; ``` 内部只有两个参数,一个`impl`,一个`Desc`,而函数的调用地址 - `FuncPtr`是再`impl`中的,为什么这里能直接这样写呢? > 因为,__main_block_impl_0 结构的地址和他的第一个成员一样,第一个成员的地址是__block_impl,所以__main_block_impl_0 和 __block_impl 的地址其实是同一个,通过格式强制转换,将 main_block_impl_0 转成 block_impl 就可以直接拿到他内部的 FuncPtr 函数地址,然后进行调用! ![image-20190307213258239](https://user-gold-cdn.xitu.io/2019/3/8/1695da89a57989cf?w=1489&h=1080&f=jpeg&s=303137) - 可见- block本质上是OC对象,内部有一个isa指针 - block是封装了函数调用已经函数调用的oc对象 ### Block面试题抛砖引玉~ **开胃菜先来一下,以下结果输出什么** ``` int a = 10; void (^MNBlock)(void) = ^{ NSLog(@"a = %d",a); }; a += 20; MNBlock(); ``` 调用 `MNBlock();` 之前,a 已经 + 20了,输出30? 太天真了兄弟,这里涉及到capture的概念,即变量捕获 ### Block捕获变量(capture) 捕获:Block内部会新增一个成员,来存储传进来的变量 ![image-20190307214010613](https://user-gold-cdn.xitu.io/2019/3/8/1695da89a686ef14?w=1988&h=586&f=jpeg&s=141880) block 内部直接捕获了穿进去的这个变量a(10) ![image-20190307214351958](/Users/liangyuhang/Library/Application Support/typora-user-images/image-20190307214351958.png) 创建block的时候,已经将变量a=10 捕获到 block内部,之后再怎么修改,不会影响block 内部的 a **auto 和 static的区别**:以下会输出什么~ ``` static int b = 10; void (^MNBlock)(void) = ^{ NSLog(@"a = %d, b = %d",a,b); }; a = 20; b = 20; MNBlock(); ``` 输出 ``` 2019-03-07 21:49:49 Block-Demo a = 10, b = 20 ``` why? 查看原因: ``` auto int a = 10; static int b = 10; void (*MNBlock)(void) = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a, &b)); ``` 发现:两种变量,都有捕获到block内部。 a 是auto变量,走的是值传递, b 是 static 变量,走的是地址传递,所以会影响(指针指向同一块内存,修改的等于是同个对象) **总结** - 只有局部变量才需要捕获, - 全局变量不需要捕获,因为在哪都可以访问 - 需不需要捕获,其实主要是看作用域问题 - auto局部变量 ==>值传递->因为会销毁 - static局部练练==>不会销毁==>所以地址传递 **看图就行~** ![image-20190307220857223](https://user-gold-cdn.xitu.io/2019/3/8/1695da89a7997ead?w=1566&h=1012&f=jpeg&s=247371) **进阶考题 - self 会被捕获到 block 内部吗** ``` void (^MNBlock)(void) = ^{ NSLog(@"p = %p",self); }; ``` 模拟看官作答:不会,因为整个类里,都能调用self,应该是全局的,全局变量不会捕获到block中 哈哈哈哈!中计了!其实 self 是参数(局部变量) ``` struct __MNDemo__test_block_impl_0 { struct __block_impl impl; struct __MNDemo__test_block_desc_0* Desc; MNDemo *self; ==> 捕捉到了兄弟 } ``` > 解释原因: > > - 每个OC函数,其实默认有两个参数,一个self,一个_cmd,只是他们倆兄弟默认是隐藏的 > - 而由于他们是参数,所以是局部变量,局部变量就要被 block 捕获 > - `- (void)test(self, SEL _cmd){XXX}` 默认的OC方法里面其实有这两个隐藏的参数!所以上题的答案,self是会被block捕获的!**(能听懂掌声!)** **进进阶考题 - 成员变量_name 会被捕获到 block 内部吗** ``` void (^MNBlock)(void) = ^{ NSLog(@"==%@",_name); }; ``` 模拟看官作答:呵呵,老子都中了这么多次技了,这题学会了!! 因为_name是成员变量,全局的,也没有self,所以不需要捕获整个类就都可以随便访问它! 哎,兄弟,还是太年轻了!! ``` void (^MNBlock)(void) = ^{ NSLog(@"==%@",self->_name); }; ``` 看图说话,不多bb, *(能听懂掌声!)* ## Block的类型 - `__NSGlobalBlock__` - `__NSStackBlock__` - `__NSMallocBlock__` MRC环境下 ``` void (^global)() = ^{ NSLog(@"globalValue = %d",globalValue); }; void (^autoBlock)() = ^{ NSLog(@"this is a Block~ rua~ = %d",a); }; void (^copyAuto)() = [autoBlock copy]; -------------------------------------------- print class 2019-03-08 17:40:43 Block-Demo global class = __NSGlobalBlock__ autoBlock class = __NSStackBlock__ copyAuto = __NSMallocBlock__ ``` 总结: ![image-20190308174640436](/Users/liangyuhang/Library/Application Support/typora-user-images/image-20190308174640436.png) ![](https://user-gold-cdn.xitu.io/2019/3/8/1695daddd8d9af3c?w=1482&h=920&f=png&s=1979727) 栈上的内存系统会自动回收 - 栈空间的block 不会对 对象进行强引用 - 堆空间的block 可能会对对象产生强引用: - 如果是weak指针,不会强引用 - 如果是strong指针,会强引用 堆上的内存是由程序员控制,所以一般将block 拷贝到堆上,让程序员控制他与内部变量的生命周期 题目:以下输出的顺序是什么(ARC环境下) ``` @implementation MNPerson - (void)dealloc{ NSLog(@"MNPerson - dealloc"); } @end -------------------------------------- MNPerson *person = [[MNPerson alloc]init]; __weak MNPerson *weakPerson = person; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"1-----%@",person); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"2------%@",weakPerson); }); }); NSLog(@"touchesBegan"); ``` 输出结果 ``` 2019-03-08 22:38:59.038452+0800 touchesBegan 2019-03-08 22:39:00.056746+0800 1----- 2019-03-08 22:39:00.057891+0800 MNPerson - dealloc 2019-03-08 22:39:02.058011+0800 2-----(null) ``` 解释: 1. gcd的block会自动对auto变量进行copy操作 2. block内部对 auto 变量的强弱引用,取决于指针类型 3. 1 中的auto变量是 person,没声明默认对象是 strong 类型,所以 gcd1 会对 person进行 1s的强引用 4. gcd2 中的变量是 weakPerson,看到是__wesk指针,所以block内部不会对其产生强引用 5. 随后,gcd1 对 person进行1s的强引用之后,gcd1 的block销毁,person对象销毁,打印MNPerson dealloc 6. 最终,2s过后打印 2——weakPerson,因为person对象在gcd1 block结束之后,释放掉了,所以此时person是空,因为是weak指针,对象是null不会crash,最终打印null #### 对象类型的auto变量 - 当 block 内部访问了对象类型的auto变量时 - 如果block在展示,不会对 auto 变量产生强引用 - 如果 block 被 拷贝到堆上 - 会调用 block 内部的 copy 函数 - copy 函数内部会调用 _Block_object_assign 函数 - _Block_object_assign 函数会根据auto变量的修饰符 *( strong、 weak、unsafe_unretained )* 做出对应的操作,看对内部auto变量进行强引用还是弱引用(类似于 retain) - 如果 block 从 堆上移除 - 会调用 block 内部的 dispose 函数 - dispose函数内部会调用_Block_object_dispose 函数 - _Block_object_dispose 类似于 release,会对auto变量进行自动释放(当引用计数器=0的时候 ) ![image-20190308173027757](/Users/liangyuhang/Library/Application%20Support/typora-user-images/image-20190308173027757.png) #### block中的copy - 在ARC环境下,编译器会根据情况,自动将栈上的block拷贝到堆上,比如以下几种情况 - block 作为函数返回值的时候 - 将block复制给__strong指针的时候 - block作为Cocoa API中方法名含有usingBlock的方法参数事 - 比如:`[array enumerateObjectsUsingBlock:XXX]` ### __block 修饰符的使用 题目:以下代码的是否编译通过,可以的话输出结果是什么 ``` int a = 10; void (^block)() = ^{ a = 20; NSLog(@"a = %d",a); }; ``` 结果如下: ![image-20190308225448279](https://user-gold-cdn.xitu.io/2019/3/8/1695dee6803c470f?w=1770&h=280&f=jpeg&s=75077) *思考:无法编译,为啥呢?编译的时候,block应该是会把auto变量捕获进去的,那block结构中应该有a才对啊* ``` //main函数 int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int a = 10; void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); } return 0; } //block执行地址 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_kh_0rp73c0s2mvfp5gjf25j5y6h0000gn_T_main_1a12fa_mi_0,a);} ``` block执行的时候,内部是 `__main_block_func_0` 函数,而a的声明,是在`main`函数,两个函数相互独立,对于他们来说,a都是一个局部变量,而且两个函数中都对a初始化,两个函数的中a不是同一个,那怎么可以在 执行函数中,修改main函数中的局部变量呢,所以编译报错! 如何改? - **方案一:使用static** ``` static int a = 10; void (^block)() = ^{ a = 20; NSLog(@"a = %d",a); }; ``` 因为static修饰的auto变量,最终在block中进行的不是值传递,而是地址传递,措意执行函数中的a 和 main 函数中的a,是同一个地址 ==> 等于同一个a,所以可以修改,输出20 但是使用static,就会变成静态变量,永远在内存中 - **方案二: 使用__blcok** ``` __block auto int a = 10; void (^block)() = ^{ a = 20; NSLog(@"a = %d",a); }; ``` ``` struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref ==> auto的话,是int a,__block,变成对象了 } ``` ``` struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding;==> 指向自己的结构体 int __flags; int __size; int a; ==> 10在这里 }; ``` a = 20;最终转成 `(a->__forwarding->a) = 20;` > 解释下:__forwarding 是指向结构体本身的指针,等价于a本身,其实就是通过a的结构体指针,拿到里面的成员a,再对他赋值 > > 指针传递,所以可以修改 auto 变量,通过block,间接引用 auto 变量 ![image-20190309205908169](https://user-gold-cdn.xitu.io/2019/3/9/16962f6575523772?w=1526&h=978&f=jpeg&s=287383) #### __block的内存管理 - 当 block 在栈上的时候,不会对内部的__block 变量产生强硬有 - 当 block 从栈上被 copy 到堆上的时候 - 会调用block内部的copy函数 - copy函数内部会调用_Block_object_assign 函数 - _Block_object_assign 函数会对 __block 变量进行一次 retain操作,产生强引用 抄图分析 : ![image-20190309210956453](https://user-gold-cdn.xitu.io/2019/3/9/16962f6581a28a56?w=832&h=400&f=jpeg&s=27226) ![image-20190309211009229](https://user-gold-cdn.xitu.io/2019/3/9/16962f657a06fcdf?w=954&h=394&f=jpeg&s=28143) - 当block从堆中移除时 - 会调用 block 内部的 dispose 函数 - dispose内部会调用_Block_object_dispose函数 - _Block_object_dispose函数会对`__block`变量进行一次release操作,如果retainCount为0,自动释放该__block变量 ![image-20190309211246277](https://user-gold-cdn.xitu.io/2019/3/9/16962f6579f78ec5?w=754&h=388&f=jpeg&s=20257) ![image-20190309211257030](https://user-gold-cdn.xitu.io/2019/3/9/16962f657a5060f1?w=1018&h=438&f=jpeg&s=41360) **总结:** - block在栈上的时候,不会对内部的变量产生强引用 - 当block从栈上 copy 到堆上的时候,内部都会调用 __Block_object_assign - 如果是`__block`修饰的变量,会__block修饰的对象产生强引用 - 如果是普通auto变量,看修饰的指针类型是strong 还是 weak(unsafe_unretained) - strong修饰的,block就会对内部的auto变量产生强引用 - weak修饰的,block就不会对内部的auto变量产生强引用 - 特别注意!上述条件仅在ARC环境下生效,如果是MRC环境下,block不会对内部auto变量产生强引用!**(MRC下不会进行retain操作)** - 当block从堆上移除的时候,内部会调用`__Block_object_dispose `函数,相当于对`block`内部所持有的对象进行移除release操作,如果retainCount为0,自动释放该__block变量 #### __block中的 _ forwarding 指针 内存拷贝的时候,如果block从栈被copy到堆上,肯定也希望内部的变量一起存储到堆上(让变量的生命周期可控,才不会被回收) 加入变量a在栈上,在栈上的指针,指向堆上的 block,堆上的block的 forwarding指向他自己,就可以保证,修改&获取的变量,都是堆上的变量 ![image-20190309213120820](https://user-gold-cdn.xitu.io/2019/3/9/16962f657a64e5e6?w=1404&h=1046&f=jpeg&s=83580) 最终,__block指向的变量,是指向堆上的 #### __block 修饰的类型 ``` @implementation MNObject - (void)dealloc{ NSLog(@"MNObject - dealloc"); } @end -------------------------------------------- typedef void (^MNBlock)(); MNBlock block; { MNObject *obj = [[MNObject alloc]init]; __block __weak MNObject *weakObj = obj; block = ^{ NSLog(@"----------%p",weakObj); }; } block(); ``` 问,上述代码的输出顺序是? ``` 2019-03-09 21:57:56.673296+0800 Block-Demo[72692:8183596] MNObject - dealloc 2019-03-09 21:57:56.673520+0800 Block-Demo[72692:8183596] ----------0x0 ``` 解释:ARC下 ![image-20190309220353476](https://user-gold-cdn.xitu.io/2019/3/9/16962f65b2fe9473?w=1444&h=922&f=jpeg&s=274783) 上述代码,block 持有的是 weakObj,weak指针,所以block内部的__block结构体,对他内部持有的person不强引用!所以出了 小括号后,person没有被强引用,生命gg,先dealloc,输出`dealloc`,之后进行block调用,打印 --------- **特别注意,上述逻辑进在ARC下,如果在MRC下,中间结构体对象,不会对person 进行retain操作! 即便 person 是强指针修饰,也不会对内部的person对象进行强引用!** MRC环境下 ``` MNBlock block; { MNObject *obj = [[MNObject alloc]init]; block = [^{ NSLog(@"----------%p",obj); }copy]; [obj release]; } block(); [block release]; -------------------- 输出: 2019-03-09 21:59:56.673296+0800 Block-Demo[72692:8183596] MNObject - dealloc 2019-03-09 21:59:56.673520+0800 Block-Demo[72692:8183596] ----------0x0 ``` 上述代码,obj 是 __strong 修饰,但是并没有被 block 强引用!可见MRC环境下,__修饰的对象,生成的中间block对象不会对 auto变量产生强引用。 ### Block的循环应用问题 传送门:[ 实际开发中-Block导致循环引用的问题(ARC环境下)](https://www.jianshu.com/p/fc2f4d207d25) **考题:MRC 下,block的循环引用如何解决呢?** - **方案1:unsafe_unretained** MRC下,没有__weak,所以只能用_unsafe_unretained指针,原理和 weak 一样(ARC环境下不推荐使用,可能导致野指针,推荐使用weak) ``` __unsafe_unretained MNObject *weakSelf = self; self.block = [^{ NSLog(@"----------%p",weakSelf); }copy]; ``` - **方案2: __block** ``` __block self; self.block = [^{ NSLog(@"----------%p",self); }copy]; ``` why? 上面关于 __block的总结 > 特别注意!上述条件仅在ARC环境下生效,如果是MRC环境下,block不会对内部auto变量产生强引用!(MRC下不会进行retain操作) ![image-20190309224535679](https://user-gold-cdn.xitu.io/2019/3/9/16962f914228a686?w=1374&h=940&f=jpeg&s=262830) - **方案3: 手动在block函数内将对象制空,并且必须手动保证block调用** ``` MNObject *obj = [[MNObject alloc]init]; __unsafe_unretained MNObject *weakObj = obj; obj.block = [^{ NSLog(@"----------%p",obj); obj = nil; }copy]; obj.block(); ``` ![image-20190309225056495](https://user-gold-cdn.xitu.io/2019/3/9/16962f65bf4c0f7f?w=1550&h=584&f=jpeg&s=175229) 但是这个一定要注意,block必须调用,因为对象指针的清空操作,是写在block函数中的,如果没调用block,循环引用问题还是会存在,所以不推荐使用。 实际开发中,循环引用的检测工具推荐,facebook开源的 [FBRetainCycleDetector](https://github.com/facebook/FBRetainCycleDetector),用过的都说好~ --- 老实说,block其实非常难,能考得特别深,本文也只是简单探究&总结下中级iOS常见的block考题,以及对Block底层的初步探究,如果是像我所在的三线城市,去面试那种非一线公司的话,如果能掌握本文,可能block相关的题目能答个八九不离十吧!*(可能题目会变换组合,但是万变不离其宗)* block的文章其实很多,但是如果要真的深入理解,还是得动手,这里推荐初中级iOSer可以跟着本文的思路,一步一步跟着探究试试,本文只是起个抛砖引玉的作用
---
友情演出:[小马哥MJ](https://github.com/CoderMJLee) *参考资料* [实际开发中-Block导致循环引用的问题(ARC环境下)](https://www.jianshu.com/p/fc2f4d207d25) [招聘一个靠谱的 iOS](https://blog.sunnyxx.com/2015/07/04/ios-interview/) [ChenYilong/iOSInterviewQuestions](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#45-addobserverforkeypathoptionscontext%E5%90%84%E4%B8%AA%E5%8F%82%E6%95%B0%E7%9A%84%E4%BD%9C%E7%94%A8%E5%88%86%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88observer%E4%B8%AD%E9%9C%80%E8%A6%81%E5%AE%9E%E7%8E%B0%E5%93%AA%E4%B8%AA%E6%96%B9%E6%B3%95%E6%89%8D%E8%83%BD%E8%8E%B7%E5%BE%97kvo%E5%9B%9E%E8%B0%83) ================================================ FILE: InterView-obj-isa-class/InterView-obj-isa-class/NSObject+MNTest.h ================================================ // // NSObject+MNTest.h // InterView-obj-isa-class // // Created by 梁宇航 on 2019/1/20. // Copyright © 2019年 梁宇航. All rights reserved. // #import @interface NSObject (MNTest) + (void)checkSuperclass; @end ================================================ FILE: InterView-obj-isa-class/InterView-obj-isa-class/NSObject+MNTest.m ================================================ // // NSObject+MNTest.m // InterView-obj-isa-class // // Created by 梁宇航 on 2019/1/20. // Copyright © 2019年 梁宇航. All rights reserved. // #import "NSObject+MNTest.h" @implementation NSObject (MNTest) //+ (void)checkSuperclass{ // NSLog(@"+NSObject checkSuperclass - %p",self); //} - (void)checkSuperclass{ NSLog(@"-NSObject checkSuperclass - %p",self); } @end ================================================ FILE: InterView-obj-isa-class/InterView-obj-isa-class/main.m ================================================ // // main.m // InterView-obj-isa-class // // Created by 梁宇航 on 2019/1/20. // Copyright © 2019年 梁宇航. All rights reserved. // #import #import #import "NSObject+MNTest.h" @interface MNSuperclass : NSObject - (void)superclassInstanceMethod; + (void)superClassMethod; @end @implementation MNSuperclass - (void)superclassInstanceMethod{ NSLog(@"superclass-InstanceMethod - %p",self); } + (void)superClassMethod{ NSLog(@"+ superClass-classMethod- %p",self); } @end @interface MNSubclass : MNSuperclass - (void)subclassInstanceMethod; - (void)compareSelfWithSuperclass; @end @implementation MNSubclass - (void)subclassInstanceMethod{ NSLog(@"subclassInstanceMethod- %p",self); } - (void)compareSelfWithSuperclass{ NSLog(@"self class = %@",[self class]); NSLog(@"super class = %@",[super class]); } @end int main(int argc, char * argv[]) { @autoreleasepool { MNSubclass *subclass = [[MNSubclass alloc]init]; [subclass superclassInstanceMethod]; NSLog(@"subclass = %p, MNSubclass = %p",subclass,[MNSubclass class]); [MNSubclass superClassMethod]; //检验 - root-meta-class的superclass 是否指向 root-class [MNSubclass checkSuperclass]; NSLog(@"MNSubclass = %p",[MNSubclass class]); //回答 - [self class] && [super class] 的答案 [subclass compareSelfWithSuperclass]; } return 0; } ================================================ FILE: InterView-obj-isa-class/InterView-obj-isa-class.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ A793EB6421F4CECA003661CD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A793EB6321F4CECA003661CD /* main.m */; }; A793EB6C21F4CEDF003661CD /* NSObject+MNTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A793EB6A21F4CEDF003661CD /* NSObject+MNTest.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ A793EB5E21F4CECA003661CD /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ A793EB6021F4CECA003661CD /* InterView-obj-isa-class */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "InterView-obj-isa-class"; sourceTree = BUILT_PRODUCTS_DIR; }; A793EB6321F4CECA003661CD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; A793EB6A21F4CEDF003661CD /* NSObject+MNTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MNTest.m"; sourceTree = ""; }; A793EB6B21F4CEDF003661CD /* NSObject+MNTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+MNTest.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ A793EB5D21F4CECA003661CD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ A793EB5721F4CECA003661CD = { isa = PBXGroup; children = ( A793EB6221F4CECA003661CD /* InterView-obj-isa-class */, A793EB6121F4CECA003661CD /* Products */, ); sourceTree = ""; }; A793EB6121F4CECA003661CD /* Products */ = { isa = PBXGroup; children = ( A793EB6021F4CECA003661CD /* InterView-obj-isa-class */, ); name = Products; sourceTree = ""; }; A793EB6221F4CECA003661CD /* InterView-obj-isa-class */ = { isa = PBXGroup; children = ( A793EB6B21F4CEDF003661CD /* NSObject+MNTest.h */, A793EB6A21F4CEDF003661CD /* NSObject+MNTest.m */, A793EB6321F4CECA003661CD /* main.m */, ); path = "InterView-obj-isa-class"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ A793EB5F21F4CECA003661CD /* InterView-obj-isa-class */ = { isa = PBXNativeTarget; buildConfigurationList = A793EB6721F4CECA003661CD /* Build configuration list for PBXNativeTarget "InterView-obj-isa-class" */; buildPhases = ( A793EB5C21F4CECA003661CD /* Sources */, A793EB5D21F4CECA003661CD /* Frameworks */, A793EB5E21F4CECA003661CD /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = "InterView-obj-isa-class"; productName = "InterView-obj-isa-class"; productReference = A793EB6021F4CECA003661CD /* InterView-obj-isa-class */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ A793EB5821F4CECA003661CD /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0930; ORGANIZATIONNAME = "梁宇航"; TargetAttributes = { A793EB5F21F4CECA003661CD = { CreatedOnToolsVersion = 9.3; }; }; }; buildConfigurationList = A793EB5B21F4CECA003661CD /* Build configuration list for PBXProject "InterView-obj-isa-class" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = A793EB5721F4CECA003661CD; productRefGroup = A793EB6121F4CECA003661CD /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( A793EB5F21F4CECA003661CD /* InterView-obj-isa-class */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ A793EB5C21F4CECA003661CD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A793EB6C21F4CEDF003661CD /* NSObject+MNTest.m in Sources */, A793EB6421F4CECA003661CD /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ A793EB6521F4CECA003661CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; A793EB6621F4CECA003661CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; name = Release; }; A793EB6821F4CECA003661CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; A793EB6921F4CECA003661CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ A793EB5B21F4CECA003661CD /* Build configuration list for PBXProject "InterView-obj-isa-class" */ = { isa = XCConfigurationList; buildConfigurations = ( A793EB6521F4CECA003661CD /* Debug */, A793EB6621F4CECA003661CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; A793EB6721F4CECA003661CD /* Build configuration list for PBXNativeTarget "InterView-obj-isa-class" */ = { isa = XCConfigurationList; buildConfigurations = ( A793EB6821F4CECA003661CD /* Debug */, A793EB6921F4CECA003661CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = A793EB5821F4CECA003661CD /* Project object */; } ================================================ FILE: InterView-obj-isa-class/InterView-obj-isa-class.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: InterView-obj-isa-class/InterView-obj-isa-class.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/.clang-format ================================================ # Format of this file is YAML # Minimum clang-format version required: clang-format version 3.6.0 # Detailed description of options available at http://clang.llvm.org/docs/ClangFormatStyleOptions.html AlignEscapedNewlinesLeft: true # Bad: # void foo() { # someFunction(); # someOtherFunction(); # } # Good: # void foo() { # someFunction(); # someOtherFunction(); # } AlignTrailingComments: true # align all comments to right based of // # == Avoid using // based comments altogether == AlignAfterOpenBracket: false # don't align after a bracket, uses indentation rules to indent AllowAllParametersOfDeclarationOnNextLine: false # allow funtion definition as # someFunction(foo, # bar, # baz); AlignConsecutiveAssignments: false # does not align consecutive assignments with '=' operator AllowShortBlocksOnASingleLine: true # single statement block can be merged on one line # e.g if (a) { return; } AllowShortCaseLabelsOnASingleLine: false # Single statement case statements should be on their own lines AllowShortFunctionsOnASingleLine: None # Bad: # int foo() { return 123; } AllowShortIfStatementsOnASingleLine: false # Bad: # if (someOtherVar) return; # Good: # if (someOtherVar) # return; AllowShortLoopsOnASingleLine: false # Bad: # while(i>0) i--; # Good: # while(i>0) { # i--; # } AlwaysBreakAfterDefinitionReturnType: true # Ensures return type is one its own line # e.g. unsigned int # function(char param) { } AlwaysBreakBeforeMultilineStrings: true # multine strings should begin on new line BinPackArguments: true BinPackParameters: false # functions arguments should all be on one line or have a single line for each param BreakBeforeBinaryOperators: None # break for new line after binary operator in case of length is over ColumnLimit # e.g. # int foo = bar + # baz; BreakBeforeBraces: Linux # Always attach braces to surrounding context except - # break before braces on function, namespace and class definitions ColumnLimit: 132 # every body has wide screen now. 132 seems to be reasonable limit now. ContinuationIndentWidth: 8 # indent continued lines by two tabs IndentCaseLabels: false # case labels have same indentation as switch statement. IndentWidth: 4 # 4 spaces for indentation TabWidth: 4 # tabwidth is 4 spaces UseTab: Always # always indent lines with tabs IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false # remove excess empty lines at start of blocks. PointerAlignment: Right # "void *foo" (vs. "void* foo" or "void * foo") SpaceAfterCStyleCast: false # No space after (cast). E.g # int blah = (int)((void *)foo + bar) SpaceBeforeAssignmentOperators: true # Assignment = should be seperated by spaces on both sides. SpaceBeforeParens: ControlStatements # for control statements a space is required before '{' # Bad: for(){ statement; } # Good: for() { statement; } SpaceInEmptyParentheses: false # No spaces required for empty () SpacesInCStyleCastParentheses: false # No spaces required for (unsigned int) type cast SpacesInParentheses: false SpacesInSquareBrackets: false # No spaces in [count] style invocations of [] ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/.gitattributes ================================================ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/.gitignore ================================================ # /libmalloc.xcodeproj/ /libmalloc.xcodeproj/*.xcworkspace /libmalloc.xcodeproj/xcuserdata ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/include/malloc/_malloc.h ================================================ /* * Copyright (c) 2018 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _MALLOC_UNDERSCORE_MALLOC_H_ #define _MALLOC_UNDERSCORE_MALLOC_H_ /* * This header is included from , so the contents of this file have * broad source compatibility and POSIX conformance implications. * Be cautious about what is included and declared here. */ #include #include #include <_types.h> #include __BEGIN_DECLS void *malloc(size_t __size) __result_use_check __alloc_size(1); void *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2); void free(void *); void *realloc(void *__ptr, size_t __size) __result_use_check __alloc_size(2); #if !defined(_ANSI_SOURCE) && (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) void *valloc(size_t) __alloc_size(1); #endif // !defined(_ANSI_SOURCE) && (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) int posix_memalign(void **__memptr, size_t __alignment, size_t __size) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0); __END_DECLS #endif /* _MALLOC_UNDERSCORE_MALLOC_H_ */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/include/malloc/malloc.h ================================================ /* * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _MALLOC_MALLOC_H_ #define _MALLOC_MALLOC_H_ #include #include #include #include #if __has_feature(ptrauth_calls) #include // Zone function pointer, type-diversified but not address-diversified (because // the zone can be copied). Process-independent because the zone structure may // be in the shared library cache. #define MALLOC_ZONE_FN_PTR(fn) __ptrauth(ptrauth_key_process_independent_code, \ FALSE, ptrauth_string_discriminator("malloc_zone_fn." #fn)) fn // Introspection function pointer, address- and type-diversified. // Process-independent because the malloc_introspection_t structure that contains // these pointers may be in the shared library cache. #define MALLOC_INTROSPECT_FN_PTR(fn) __ptrauth(ptrauth_key_process_independent_code, \ TRUE, ptrauth_string_discriminator("malloc_introspect_fn." #fn)) fn // Pointer to the introspection pointer table, type-diversified but not // address-diversified (because the zone can be copied). // Process-independent because the table pointer may be in the shared library cache. #define MALLOC_INTROSPECT_TBL_PTR(ptr) __ptrauth(ptrauth_key_process_independent_data,\ FALSE, ptrauth_string_discriminator("malloc_introspect_tbl")) ptr #endif // __has_feature(ptrauth_calls) #ifndef MALLOC_ZONE_FN_PTR #define MALLOC_ZONE_FN_PTR(fn) fn #define MALLOC_INTROSPECT_FN_PTR(fn) fn #define MALLOC_INTROSPECT_TBL_PTR(ptr) ptr #endif // MALLOC_ZONE_FN_PTR __BEGIN_DECLS /********* Type definitions ************/ typedef struct _malloc_zone_t { /* Only zone implementors should depend on the layout of this structure; Regular callers should use the access functions below */ void *reserved1; /* RESERVED FOR CFAllocator DO NOT USE */ void *reserved2; /* RESERVED FOR CFAllocator DO NOT USE */ size_t (* MALLOC_ZONE_FN_PTR(size))(struct _malloc_zone_t *zone, const void *ptr); /* returns the size of a block or 0 if not in this zone; must be fast, especially for negative answers */ void *(* MALLOC_ZONE_FN_PTR(malloc))(struct _malloc_zone_t *zone, size_t size); void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */ void *(* MALLOC_ZONE_FN_PTR(valloc))(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */ void (* MALLOC_ZONE_FN_PTR(free))(struct _malloc_zone_t *zone, void *ptr); void *(* MALLOC_ZONE_FN_PTR(realloc))(struct _malloc_zone_t *zone, void *ptr, size_t size); void (* MALLOC_ZONE_FN_PTR(destroy))(struct _malloc_zone_t *zone); /* zone is destroyed and all memory reclaimed */ const char *zone_name; /* Optional batch callbacks; these may be NULL */ unsigned (* MALLOC_ZONE_FN_PTR(batch_malloc))(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); /* given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested) */ void (* MALLOC_ZONE_FN_PTR(batch_free))(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed); /* frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the process */ struct malloc_introspection_t * MALLOC_INTROSPECT_TBL_PTR(introspect); unsigned version; /* aligned memory allocation. The callback may be NULL. Present in version >= 5. */ void *(* MALLOC_ZONE_FN_PTR(memalign))(struct _malloc_zone_t *zone, size_t alignment, size_t size); /* free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.*/ void (* MALLOC_ZONE_FN_PTR(free_definite_size))(struct _malloc_zone_t *zone, void *ptr, size_t size); /* Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8. */ size_t (* MALLOC_ZONE_FN_PTR(pressure_relief))(struct _malloc_zone_t *zone, size_t goal); /* * Checks whether an address might belong to the zone. May be NULL. Present in version >= 10. * False positives are allowed (e.g. the pointer was freed, or it's in zone space that has * not yet been allocated. False negatives are not allowed. */ boolean_t (* MALLOC_ZONE_FN_PTR(claimed_address))(struct _malloc_zone_t *zone, void *ptr); } malloc_zone_t; /********* Creation and destruction ************/ extern malloc_zone_t *malloc_default_zone(void); /* The initial zone */ extern malloc_zone_t *malloc_create_zone(vm_size_t start_size, unsigned flags); /* Creates a new zone with default behavior and registers it */ extern void malloc_destroy_zone(malloc_zone_t *zone); /* Destroys zone and everything it allocated */ /********* Block creation and manipulation ************/ extern void *malloc_zone_malloc(malloc_zone_t *zone, size_t size) __alloc_size(2); /* Allocates a new pointer of size size; zone must be non-NULL */ extern void *malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size) __alloc_size(2,3); /* Allocates a new pointer of size num_items * size; block is cleared; zone must be non-NULL */ extern void *malloc_zone_valloc(malloc_zone_t *zone, size_t size) __alloc_size(2); /* Allocates a new pointer of size size; zone must be non-NULL; Pointer is guaranteed to be page-aligned and block is cleared */ extern void malloc_zone_free(malloc_zone_t *zone, void *ptr); /* Frees pointer in zone; zone must be non-NULL */ extern void *malloc_zone_realloc(malloc_zone_t *zone, void *ptr, size_t size) __alloc_size(3); /* Enlarges block if necessary; zone must be non-NULL */ extern malloc_zone_t *malloc_zone_from_ptr(const void *ptr); /* Returns the zone for a pointer, or NULL if not in any zone. The ptr must have been returned from a malloc or realloc call. */ extern size_t malloc_size(const void *ptr); /* Returns size of given ptr */ extern size_t malloc_good_size(size_t size); /* Returns number of bytes greater than or equal to size that can be allocated without padding */ extern void *malloc_zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size) __alloc_size(3) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0); /* * Allocates a new pointer of size size whose address is an exact multiple of alignment. * alignment must be a power of two and at least as large as sizeof(void *). * zone must be non-NULL. */ /********* Batch methods ************/ extern unsigned malloc_zone_batch_malloc(malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); /* Allocates num blocks of the same size; Returns the number truly allocated (may be 0) */ extern void malloc_zone_batch_free(malloc_zone_t *zone, void **to_be_freed, unsigned num); /* frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the process; This function will always free even if the zone has no batch callback */ /********* Functions for libcache ************/ extern malloc_zone_t *malloc_default_purgeable_zone(void) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0); /* Returns a pointer to the default purgeable_zone. */ extern void malloc_make_purgeable(void *ptr) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0); /* Make an allocation from the purgeable zone purgeable if possible. */ extern int malloc_make_nonpurgeable(void *ptr) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0); /* Makes an allocation from the purgeable zone nonpurgeable. * Returns zero if the contents were not purged since the last * call to malloc_make_purgeable, else returns non-zero. */ /********* Functions for zone implementors ************/ extern void malloc_zone_register(malloc_zone_t *zone); /* Registers a custom malloc zone; Should typically be called after a * malloc_zone_t has been filled in with custom methods by a client. See * malloc_create_zone for creating additional malloc zones with the * default allocation and free behavior. */ extern void malloc_zone_unregister(malloc_zone_t *zone); /* De-registers a zone Should typically be called before calling the zone destruction routine */ extern void malloc_set_zone_name(malloc_zone_t *zone, const char *name); /* Sets the name of a zone */ extern const char *malloc_get_zone_name(malloc_zone_t *zone); /* Returns the name of a zone */ size_t malloc_zone_pressure_relief(malloc_zone_t *zone, size_t goal) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); /* malloc_zone_pressure_relief() advises the malloc subsystem that the process is under memory pressure and * that the subsystem should make its best effort towards releasing (i.e. munmap()-ing) "goal" bytes from "zone". * If "goal" is passed as zero, the malloc subsystem will attempt to achieve maximal pressure relief in "zone". * If "zone" is passed as NULL, all zones are examined for pressure relief opportunities. * malloc_zone_pressure_relief() returns the number of bytes released. */ typedef struct { vm_address_t address; vm_size_t size; } vm_range_t; typedef struct malloc_statistics_t { unsigned blocks_in_use; size_t size_in_use; size_t max_size_in_use; /* high water mark of touched memory */ size_t size_allocated; /* reserved in memory */ } malloc_statistics_t; typedef kern_return_t memory_reader_t(task_t remote_task, vm_address_t remote_address, vm_size_t size, void **local_memory); /* given a task, "reads" the memory at the given address and size local_memory: set to a contiguous chunk of memory; validity of local_memory is assumed to be limited (until next call) */ #define MALLOC_PTR_IN_USE_RANGE_TYPE 1 /* for allocated pointers */ #define MALLOC_PTR_REGION_RANGE_TYPE 2 /* for region containing pointers */ #define MALLOC_ADMIN_REGION_RANGE_TYPE 4 /* for region used internally */ #define MALLOC_ZONE_SPECIFIC_FLAGS 0xff00 /* bits reserved for zone-specific purposes */ typedef void vm_range_recorder_t(task_t, void *, unsigned type, vm_range_t *, unsigned); /* given a task and context, "records" the specified addresses */ typedef struct malloc_introspection_t { kern_return_t (* MALLOC_INTROSPECT_FN_PTR(enumerator))(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder); /* enumerates all the malloc pointers in use */ size_t (* MALLOC_INTROSPECT_FN_PTR(good_size))(malloc_zone_t *zone, size_t size); boolean_t (* MALLOC_INTROSPECT_FN_PTR(check))(malloc_zone_t *zone); /* Consistency checker */ void (* MALLOC_INTROSPECT_FN_PTR(print))(malloc_zone_t *zone, boolean_t verbose); /* Prints zone */ void (* MALLOC_INTROSPECT_FN_PTR(log))(malloc_zone_t *zone, void *address); /* Enables logging of activity */ void (* MALLOC_INTROSPECT_FN_PTR(force_lock))(malloc_zone_t *zone); /* Forces locking zone */ void (* MALLOC_INTROSPECT_FN_PTR(force_unlock))(malloc_zone_t *zone); /* Forces unlocking zone */ void (* MALLOC_INTROSPECT_FN_PTR(statistics))(malloc_zone_t *zone, malloc_statistics_t *stats); /* Fills statistics */ boolean_t (* MALLOC_INTROSPECT_FN_PTR(zone_locked))(malloc_zone_t *zone); /* Are any zone locks held */ /* Discharge checking. Present in version >= 7. */ boolean_t (* MALLOC_INTROSPECT_FN_PTR(enable_discharge_checking))(malloc_zone_t *zone); void (* MALLOC_INTROSPECT_FN_PTR(disable_discharge_checking))(malloc_zone_t *zone); void (* MALLOC_INTROSPECT_FN_PTR(discharge))(malloc_zone_t *zone, void *memory); #ifdef __BLOCKS__ void (* MALLOC_INTROSPECT_FN_PTR(enumerate_discharged_pointers))(malloc_zone_t *zone, void (^report_discharged)(void *memory, void *info)); #else void *enumerate_unavailable_without_blocks; #endif /* __BLOCKS__ */ void (* MALLOC_INTROSPECT_FN_PTR(reinit_lock))(malloc_zone_t *zone); /* Reinitialize zone locks, called only from atfork_child handler. Present in version >= 9. */ } malloc_introspection_t; extern void malloc_printf(const char *format, ...); /* Convenience for logging errors and warnings; No allocation is performed during execution of this function; Only understands usual %p %d %s formats, and %y that expresses a number of bytes (5b,10KB,1MB...) */ /********* Functions for performance tools ************/ extern kern_return_t malloc_get_all_zones(task_t task, memory_reader_t reader, vm_address_t **addresses, unsigned *count); /* Fills addresses and count with the addresses of the zones in task; Note that the validity of the addresses returned correspond to the validity of the memory returned by reader */ /********* Debug helpers ************/ extern void malloc_zone_print_ptr_info(void *ptr); /* print to stdout if this pointer is in the malloc heap, free status, and size */ extern boolean_t malloc_zone_check(malloc_zone_t *zone); /* Checks zone is well formed; if !zone, checks all zones */ extern void malloc_zone_print(malloc_zone_t *zone, boolean_t verbose); /* Prints summary on zone; if !zone, prints all zones */ extern void malloc_zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats); /* Fills statistics for zone; if !zone, sums up all zones */ extern void malloc_zone_log(malloc_zone_t *zone, void *address); /* Controls logging of all activity; if !zone, for all zones; If address==0 nothing is logged; If address==-1 all activity is logged; Else only the activity regarding address is logged */ struct mstats { size_t bytes_total; size_t chunks_used; size_t bytes_used; size_t chunks_free; size_t bytes_free; }; extern struct mstats mstats(void); extern boolean_t malloc_zone_enable_discharge_checking(malloc_zone_t *zone) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); /* Increment the discharge checking enabled counter for a zone. Returns true if the zone supports checking, false if it does not. */ extern void malloc_zone_disable_discharge_checking(malloc_zone_t *zone) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); /* Decrement the discharge checking enabled counter for a zone. */ extern void malloc_zone_discharge(malloc_zone_t *zone, void *memory) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); /* Register memory that the programmer expects to be freed soon. zone may be NULL in which case the zone is determined using malloc_zone_from_ptr(). If discharge checking is off for the zone this function is a no-op. */ #ifdef __BLOCKS__ extern void malloc_zone_enumerate_discharged_pointers(malloc_zone_t *zone, void (^report_discharged)(void *memory, void *info)) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); /* Calls report_discharged for each block that was registered using malloc_zone_discharge() but has not yet been freed. info is used to provide zone defined information about the memory block. If zone is NULL then the enumeration covers all zones. */ #else extern void malloc_zone_enumerate_discharged_pointers(malloc_zone_t *zone, void *) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); #endif /* __BLOCKS__ */ __END_DECLS #endif /* _MALLOC_MALLOC_H_ */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/libmalloc.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXAggregateTarget section */ 3FE9201116A9111000D1238A /* libmalloc */ = { isa = PBXAggregateTarget; buildConfigurationList = 3FE9201216A9111000D1238A /* Build configuration list for PBXAggregateTarget "libmalloc" */; buildPhases = ( 3FC1927C16DD946500315C26 /* Install Man Pages */, 3FE9201D16A9143E00D1238A /* Sanitise Headers (rdar://problem/10241868) */, ); dependencies = ( C0CE45501C52CCBD00C24048 /* PBXTargetDependency */, 3FE9201816A9111600D1238A /* PBXTargetDependency */, 3FE9201616A9111400D1238A /* PBXTargetDependency */, ); name = libmalloc; productName = libmalloc; }; 45039161198FFF73004EE2A3 /* libmalloc_test */ = { isa = PBXAggregateTarget; buildConfigurationList = 45039162198FFF73004EE2A3 /* Build configuration list for PBXAggregateTarget "libmalloc_test" */; buildPhases = ( ); dependencies = ( 45039168198FFFA6004EE2A3 /* PBXTargetDependency */, 925383D91BD03D0000F745DB /* PBXTargetDependency */, ); name = libmalloc_test; productName = libmalloc_test; }; B60A57932009307E006215CB /* executables */ = { isa = PBXAggregateTarget; buildConfigurationList = B60A57962009307E006215CB /* Build configuration list for PBXAggregateTarget "executables" */; buildPhases = ( ); dependencies = ( B60A579820093093006215CB /* PBXTargetDependency */, ); name = executables; productName = executables; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 084F5E841D50204F006CD296 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 084F5E831D50204F006CD296 /* Foundation.framework */; }; 084F5E851D502102006CD296 /* radix_tree_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = 08C28B3A1D501ACC000AE997 /* radix_tree_debug.c */; }; 088C4D771D1AF049005C6B36 /* radix_tree.c in Sources */ = {isa = PBXBuildFile; fileRef = 088C4D741D1AEFB5005C6B36 /* radix_tree.c */; }; 088C4D841D1AF16F005C6B36 /* radix_tree.c in Sources */ = {isa = PBXBuildFile; fileRef = 088C4D741D1AEFB5005C6B36 /* radix_tree.c */; }; 08FEED021D501F6B00BE8A69 /* radix_tree_main.m in Sources */ = {isa = PBXBuildFile; fileRef = 08C28B421D501D2C000AE997 /* radix_tree_main.m */; }; 0D468DCF1C7BEF51006FACF5 /* magazine_lite.c in Sources */ = {isa = PBXBuildFile; fileRef = 0D468DCC1C7BEE56006FACF5 /* magazine_lite.c */; }; 0D468DD01C7BEF71006FACF5 /* stack_logging_internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D468DCD1C7BEE65006FACF5 /* stack_logging_internal.h */; }; 2B67B5682040B3AF0003E78F /* _malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B67B5672040B3A50003E78F /* _malloc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D157E7420354E02001630BF /* perfdata.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D157E7320354E02001630BF /* perfdata.framework */; }; 3FE91FED16A90B9200D1238A /* bitarray.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD116A90A8D00D1238A /* bitarray.c */; }; 3FE91FF016A90B9200D1238A /* magazine_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD616A90A8D00D1238A /* magazine_malloc.c */; }; 3FE91FF116A90B9200D1238A /* magmallocProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD716A90A8D00D1238A /* magmallocProvider.d */; }; 3FE91FF216A90B9200D1238A /* malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD816A90A8D00D1238A /* malloc.c */; }; 3FE91FF416A90B9200D1238A /* nano_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDA16A90A8D00D1238A /* nano_malloc.c */; }; 3FE91FF616A90B9200D1238A /* stack_logging_disk.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDC16A90A8D00D1238A /* stack_logging_disk.c */; }; 3FE91FFA16A90BEF00D1238A /* malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FE91FF916A90BEF00D1238A /* malloc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3FE91FFF16A9109E00D1238A /* bitarray.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD116A90A8D00D1238A /* bitarray.c */; }; 3FE9200116A9109E00D1238A /* magazine_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD616A90A8D00D1238A /* magazine_malloc.c */; }; 3FE9200216A9109E00D1238A /* magmallocProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD716A90A8D00D1238A /* magmallocProvider.d */; }; 3FE9200316A9109E00D1238A /* malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD816A90A8D00D1238A /* malloc.c */; }; 3FE9200416A9109E00D1238A /* nano_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDA16A90A8D00D1238A /* nano_malloc.c */; }; 3FE9200616A9109E00D1238A /* stack_logging_disk.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDC16A90A8D00D1238A /* stack_logging_disk.c */; }; 925383DA1BD03D5100F745DB /* stress_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 925383D11BD03B4A00F745DB /* stress_test.c */; }; B61341DE20114B660038D163 /* ktrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B61341DD20114B070038D163 /* ktrace.framework */; }; B629CF28202BA149007719B9 /* nanov2_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7FA41FCDD9A500BAD1AA /* nanov2_malloc.c */; }; B629CF2D202BB337007719B9 /* radix_tree.c in Sources */ = {isa = PBXBuildFile; fileRef = 088C4D741D1AEFB5005C6B36 /* radix_tree.c */; }; B629CF2E202BB337007719B9 /* bitarray.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD116A90A8D00D1238A /* bitarray.c */; }; B629CF2F202BB337007719B9 /* purgeable_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429E1BF681B00027269A /* purgeable_malloc.c */; }; B629CF30202BB337007719B9 /* magazine_large.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429B1BF672F80027269A /* magazine_large.c */; }; B629CF31202BB337007719B9 /* magazine_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD616A90A8D00D1238A /* magazine_malloc.c */; }; B629CF32202BB337007719B9 /* empty.s in Sources */ = {isa = PBXBuildFile; fileRef = C9ABCA041CB6FC6800ECB399 /* empty.s */; }; B629CF33202BB337007719B9 /* magazine_small.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742981BF670D00027269A /* magazine_small.c */; }; B629CF34202BB337007719B9 /* legacy_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742AA1BF685CB0027269A /* legacy_malloc.c */; }; B629CF35202BB337007719B9 /* magmallocProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD716A90A8D00D1238A /* magmallocProvider.d */; }; B629CF36202BB337007719B9 /* malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD816A90A8D00D1238A /* malloc.c */; }; B629CF37202BB337007719B9 /* frozen_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742A41BF6842F0027269A /* frozen_malloc.c */; }; B629CF38202BB337007719B9 /* nanov2_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7FA41FCDD9A500BAD1AA /* nanov2_malloc.c */; }; B629CF39202BB337007719B9 /* nano_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDA16A90A8D00D1238A /* nano_malloc.c */; }; B629CF3A202BB337007719B9 /* stack_logging_disk.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDC16A90A8D00D1238A /* stack_logging_disk.c */; }; B629CF3B202BB337007719B9 /* magazine_tiny.c in Sources */ = {isa = PBXBuildFile; fileRef = C957428F1BF419DF0027269A /* magazine_tiny.c */; }; B629CF3C202BB337007719B9 /* nano_malloc_common.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7F9D1FCDCBC600BAD1AA /* nano_malloc_common.c */; }; B65FBE2C2087AA2F00E21F59 /* malloc_printf.c in Sources */ = {isa = PBXBuildFile; fileRef = B65FBE2B2087AA2F00E21F59 /* malloc_printf.c */; }; B66C71D92034BFAE0047E265 /* malloc_common.h in Headers */ = {isa = PBXBuildFile; fileRef = B66C71D72034BFAE0047E265 /* malloc_common.h */; }; B66C71DA2034BFAE0047E265 /* malloc_common.c in Sources */ = {isa = PBXBuildFile; fileRef = B66C71D82034BFAE0047E265 /* malloc_common.c */; }; B66C71DB2034BFD30047E265 /* malloc_common.c in Sources */ = {isa = PBXBuildFile; fileRef = B66C71D82034BFAE0047E265 /* malloc_common.c */; }; B66C71DC2034BFD40047E265 /* malloc_common.c in Sources */ = {isa = PBXBuildFile; fileRef = B66C71D82034BFAE0047E265 /* malloc_common.c */; }; B68B7F9E1FCDCBC600BAD1AA /* nano_malloc_common.h in Headers */ = {isa = PBXBuildFile; fileRef = B68B7F9C1FCDCBC600BAD1AA /* nano_malloc_common.h */; }; B68B7F9F1FCDCBC600BAD1AA /* nano_malloc_common.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7F9D1FCDCBC600BAD1AA /* nano_malloc_common.c */; }; B68B7FA01FCDCBE700BAD1AA /* nano_malloc_common.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7F9D1FCDCBC600BAD1AA /* nano_malloc_common.c */; }; B68B7FA11FCDCBE800BAD1AA /* nano_malloc_common.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7F9D1FCDCBC600BAD1AA /* nano_malloc_common.c */; }; B68B7FA31FCDD67100BAD1AA /* nanov2_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = B68B7FA21FCDD60F00BAD1AA /* nanov2_malloc.h */; }; B68B7FA51FCDD9A500BAD1AA /* nanov2_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7FA41FCDD9A500BAD1AA /* nanov2_malloc.c */; }; B68B7FA61FCDD9B200BAD1AA /* nanov2_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7FA41FCDD9A500BAD1AA /* nanov2_malloc.c */; }; B68B7FA71FCDD9B200BAD1AA /* nanov2_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7FA41FCDD9A500BAD1AA /* nanov2_malloc.c */; }; B6910F67202B630D00FF2EB0 /* radix_tree.c in Sources */ = {isa = PBXBuildFile; fileRef = 088C4D741D1AEFB5005C6B36 /* radix_tree.c */; }; B6910F68202B630D00FF2EB0 /* bitarray.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD116A90A8D00D1238A /* bitarray.c */; }; B6910F69202B630D00FF2EB0 /* purgeable_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429E1BF681B00027269A /* purgeable_malloc.c */; }; B6910F6A202B630D00FF2EB0 /* magazine_large.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429B1BF672F80027269A /* magazine_large.c */; }; B6910F6B202B630D00FF2EB0 /* magazine_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD616A90A8D00D1238A /* magazine_malloc.c */; }; B6910F6C202B630D00FF2EB0 /* empty.s in Sources */ = {isa = PBXBuildFile; fileRef = C9ABCA041CB6FC6800ECB399 /* empty.s */; }; B6910F6D202B630D00FF2EB0 /* magazine_small.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742981BF670D00027269A /* magazine_small.c */; }; B6910F6E202B630D00FF2EB0 /* legacy_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742AA1BF685CB0027269A /* legacy_malloc.c */; }; B6910F6F202B630D00FF2EB0 /* magmallocProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD716A90A8D00D1238A /* magmallocProvider.d */; }; B6910F70202B630D00FF2EB0 /* malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD816A90A8D00D1238A /* malloc.c */; }; B6910F71202B630D00FF2EB0 /* frozen_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742A41BF6842F0027269A /* frozen_malloc.c */; }; B6910F73202B630D00FF2EB0 /* nano_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDA16A90A8D00D1238A /* nano_malloc.c */; }; B6910F74202B630D00FF2EB0 /* stack_logging_disk.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDC16A90A8D00D1238A /* stack_logging_disk.c */; }; B6910F75202B630D00FF2EB0 /* magazine_tiny.c in Sources */ = {isa = PBXBuildFile; fileRef = C957428F1BF419DF0027269A /* magazine_tiny.c */; }; B6910F76202B630D00FF2EB0 /* nano_malloc_common.c in Sources */ = {isa = PBXBuildFile; fileRef = B68B7F9D1FCDCBC600BAD1AA /* nano_malloc_common.c */; }; B6CA644E1FCE2C1900DEBA12 /* nanov2_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = B6CA644D1FCE2C0A00DEBA12 /* nanov2_zone.h */; }; B6CA644F1FCE2C1A00DEBA12 /* nanov2_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = B6CA644D1FCE2C0A00DEBA12 /* nanov2_zone.h */; }; B6CA64501FCE2C1B00DEBA12 /* nanov2_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = B6CA644D1FCE2C0A00DEBA12 /* nanov2_zone.h */; }; B6CA64521FCF1AD200DEBA12 /* nano_zone_common.h in Headers */ = {isa = PBXBuildFile; fileRef = B6CA64511FCF1AAD00DEBA12 /* nano_zone_common.h */; }; B6CA64531FCF1AD400DEBA12 /* nano_zone_common.h in Headers */ = {isa = PBXBuildFile; fileRef = B6CA64511FCF1AAD00DEBA12 /* nano_zone_common.h */; }; B6CA64541FCF1AD400DEBA12 /* nano_zone_common.h in Headers */ = {isa = PBXBuildFile; fileRef = B6CA64511FCF1AAD00DEBA12 /* nano_zone_common.h */; }; B6D2ED572007D91A007AF994 /* malloc_replay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6D2ED552007D91A007AF994 /* malloc_replay.cpp */; }; B6D5C7F1202E26F80035E376 /* resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = B6D5C7ED202E26CA0035E376 /* resolver.c */; }; B6D5C7F2202E26F80035E376 /* resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = B6D5C7ED202E26CA0035E376 /* resolver.c */; }; B6D5C7F3202E26F90035E376 /* resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = B6D5C7ED202E26CA0035E376 /* resolver.c */; }; B6D5C7F4202E26F90035E376 /* resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = B6D5C7ED202E26CA0035E376 /* resolver.c */; }; B6D5C7F5202E26FA0035E376 /* resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = B6D5C7ED202E26CA0035E376 /* resolver.c */; }; C0352EC71C3F3C4400DB5126 /* malloc_private.h in Headers */ = {isa = PBXBuildFile; fileRef = C0352EC61C3F3C3600DB5126 /* malloc_private.h */; settings = {ATTRIBUTES = (Private, ); }; }; C0CE45311C52C90500C24048 /* bitarray.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD116A90A8D00D1238A /* bitarray.c */; }; C0CE45321C52C90500C24048 /* purgeable_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429E1BF681B00027269A /* purgeable_malloc.c */; }; C0CE45331C52C90500C24048 /* magazine_large.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429B1BF672F80027269A /* magazine_large.c */; }; C0CE45341C52C90500C24048 /* magazine_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD616A90A8D00D1238A /* magazine_malloc.c */; }; C0CE45351C52C90500C24048 /* magazine_small.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742981BF670D00027269A /* magazine_small.c */; }; C0CE45361C52C90500C24048 /* legacy_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742AA1BF685CB0027269A /* legacy_malloc.c */; }; C0CE45371C52C90500C24048 /* magmallocProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD716A90A8D00D1238A /* magmallocProvider.d */; }; C0CE45381C52C90500C24048 /* malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FD816A90A8D00D1238A /* malloc.c */; }; C0CE45391C52C90500C24048 /* frozen_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742A41BF6842F0027269A /* frozen_malloc.c */; }; C0CE453A1C52C90500C24048 /* nano_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDA16A90A8D00D1238A /* nano_malloc.c */; }; C0CE453C1C52C90500C24048 /* stack_logging_disk.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FE91FDC16A90A8D00D1238A /* stack_logging_disk.c */; }; C0CE453D1C52C90500C24048 /* magazine_tiny.c in Sources */ = {isa = PBXBuildFile; fileRef = C957428F1BF419DF0027269A /* magazine_tiny.c */; }; C0CE45401C52C90500C24048 /* magazine_inline.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742921BF41C970027269A /* magazine_inline.h */; }; C0CE45411C52C90500C24048 /* nano_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = C957427E1BF33D130027269A /* nano_zone.h */; }; C0CE45421C52C90500C24048 /* thresholds.h in Headers */ = {isa = PBXBuildFile; fileRef = C957428C1BF411330027269A /* thresholds.h */; }; C0CE45431C52C90500C24048 /* debug.h in Headers */ = {isa = PBXBuildFile; fileRef = C957427B1BF2C8DE0027269A /* debug.h */; }; C0CE45441C52C90500C24048 /* frozen_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742A51BF6842F0027269A /* frozen_malloc.h */; }; C0CE45451C52C90500C24048 /* magazine_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742861BF3F9550027269A /* magazine_zone.h */; }; C0CE45461C52C90500C24048 /* magazine_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742951BF41E480027269A /* magazine_malloc.h */; }; C0CE45471C52C90500C24048 /* purgeable_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C957429F1BF681B00027269A /* purgeable_malloc.h */; }; C0CE45481C52C90500C24048 /* base.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742891BF3FD290027269A /* base.h */; }; C0CE454E1C52C9E600C24048 /* libmalloc.a in CopyFiles */ = {isa = PBXBuildFile; fileRef = C0CE454C1C52C90500C24048 /* libmalloc.a */; }; C932D2681D6B8D840063B19E /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = C932D2661D6B8D840063B19E /* vm.c */; }; C932D2691D6B8D840063B19E /* vm.h in Headers */ = {isa = PBXBuildFile; fileRef = C932D2671D6B8D840063B19E /* vm.h */; }; C938BBD31C74F7A400522BBD /* trace.h in Headers */ = {isa = PBXBuildFile; fileRef = C938BBD21C74F7A400522BBD /* trace.h */; }; C9571C3A1C18AA1D00A67EE3 /* stack_logging.h in Headers */ = {isa = PBXBuildFile; fileRef = C9571C391C18AA1D00A67EE3 /* stack_logging.h */; settings = {ATTRIBUTES = (Private, ); }; }; C95742721BF2C2880027269A /* bitarray.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FE91FD216A90A8D00D1238A /* bitarray.h */; }; C95742731BF2C2880027269A /* internal.h in Headers */ = {isa = PBXBuildFile; fileRef = C957426D1BF2C0C80027269A /* internal.h */; }; C95742741BF2C2880027269A /* locking.h in Headers */ = {isa = PBXBuildFile; fileRef = C957426E1BF2C1480027269A /* locking.h */; }; C95742751BF2C2880027269A /* printf.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FE91FD916A90A8D00D1238A /* printf.h */; }; C95742761BF2C2880027269A /* platform.h in Headers */ = {isa = PBXBuildFile; fileRef = C9F77BBA1BF2B84800812E13 /* platform.h */; }; C95742771BF2C2880027269A /* legacy_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FE91FFB16A90E6C00D1238A /* legacy_malloc.h */; }; C957427A1BF2C67E0027269A /* nano_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742791BF2C5F40027269A /* nano_malloc.h */; }; C957427C1BF2C8DE0027269A /* debug.h in Headers */ = {isa = PBXBuildFile; fileRef = C957427B1BF2C8DE0027269A /* debug.h */; }; C957427D1BF2C8DE0027269A /* debug.h in Headers */ = {isa = PBXBuildFile; fileRef = C957427B1BF2C8DE0027269A /* debug.h */; }; C957427F1BF33D130027269A /* nano_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = C957427E1BF33D130027269A /* nano_zone.h */; }; C95742801BF33D130027269A /* nano_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = C957427E1BF33D130027269A /* nano_zone.h */; }; C95742871BF3F9550027269A /* magazine_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742861BF3F9550027269A /* magazine_zone.h */; }; C95742881BF3F9550027269A /* magazine_zone.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742861BF3F9550027269A /* magazine_zone.h */; }; C957428A1BF3FD290027269A /* base.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742891BF3FD290027269A /* base.h */; }; C957428B1BF3FD290027269A /* base.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742891BF3FD290027269A /* base.h */; }; C957428D1BF411330027269A /* thresholds.h in Headers */ = {isa = PBXBuildFile; fileRef = C957428C1BF411330027269A /* thresholds.h */; }; C957428E1BF411330027269A /* thresholds.h in Headers */ = {isa = PBXBuildFile; fileRef = C957428C1BF411330027269A /* thresholds.h */; }; C95742901BF419DF0027269A /* magazine_tiny.c in Sources */ = {isa = PBXBuildFile; fileRef = C957428F1BF419DF0027269A /* magazine_tiny.c */; }; C95742911BF419DF0027269A /* magazine_tiny.c in Sources */ = {isa = PBXBuildFile; fileRef = C957428F1BF419DF0027269A /* magazine_tiny.c */; }; C95742931BF41C970027269A /* magazine_inline.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742921BF41C970027269A /* magazine_inline.h */; }; C95742941BF41C970027269A /* magazine_inline.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742921BF41C970027269A /* magazine_inline.h */; }; C95742961BF41E480027269A /* magazine_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742951BF41E480027269A /* magazine_malloc.h */; }; C95742971BF41E480027269A /* magazine_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742951BF41E480027269A /* magazine_malloc.h */; }; C95742991BF670D00027269A /* magazine_small.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742981BF670D00027269A /* magazine_small.c */; }; C957429A1BF670D00027269A /* magazine_small.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742981BF670D00027269A /* magazine_small.c */; }; C957429C1BF672F80027269A /* magazine_large.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429B1BF672F80027269A /* magazine_large.c */; }; C957429D1BF672F80027269A /* magazine_large.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429B1BF672F80027269A /* magazine_large.c */; }; C95742A01BF681B00027269A /* purgeable_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429E1BF681B00027269A /* purgeable_malloc.c */; }; C95742A11BF681B00027269A /* purgeable_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C957429E1BF681B00027269A /* purgeable_malloc.c */; }; C95742A21BF681B00027269A /* purgeable_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C957429F1BF681B00027269A /* purgeable_malloc.h */; }; C95742A31BF681B00027269A /* purgeable_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C957429F1BF681B00027269A /* purgeable_malloc.h */; }; C95742A61BF6842F0027269A /* frozen_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742A41BF6842F0027269A /* frozen_malloc.c */; }; C95742A71BF6842F0027269A /* frozen_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742A41BF6842F0027269A /* frozen_malloc.c */; }; C95742A81BF6842F0027269A /* frozen_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742A51BF6842F0027269A /* frozen_malloc.h */; }; C95742A91BF6842F0027269A /* frozen_malloc.h in Headers */ = {isa = PBXBuildFile; fileRef = C95742A51BF6842F0027269A /* frozen_malloc.h */; }; C95742AB1BF685CB0027269A /* legacy_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742AA1BF685CB0027269A /* legacy_malloc.c */; }; C95742AC1BF685CB0027269A /* legacy_malloc.c in Sources */ = {isa = PBXBuildFile; fileRef = C95742AA1BF685CB0027269A /* legacy_malloc.c */; }; C99E320B1D6F7366005655A8 /* magazine_rack.c in Sources */ = {isa = PBXBuildFile; fileRef = C99E32091D6F7366005655A8 /* magazine_rack.c */; }; C99E320C1D6F7366005655A8 /* magazine_rack.h in Headers */ = {isa = PBXBuildFile; fileRef = C99E320A1D6F7366005655A8 /* magazine_rack.h */; }; C9ABCA051CB6FC6800ECB399 /* empty.s in Sources */ = {isa = PBXBuildFile; fileRef = C9ABCA041CB6FC6800ECB399 /* empty.s */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 3FE9201516A9111400D1238A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFC1BE516A908F800027192 /* Project object */; proxyType = 1; remoteGlobalIDString = 3FE91FFD16A9109E00D1238A; remoteInfo = libmalloc_eOS; }; 3FE9201716A9111600D1238A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFC1BE516A908F800027192 /* Project object */; proxyType = 1; remoteGlobalIDString = 3FE91FE716A90AEC00D1238A; remoteInfo = libsystem_malloc; }; 45039167198FFFA6004EE2A3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFC1BE516A908F800027192 /* Project object */; proxyType = 1; remoteGlobalIDString = 456E51C8197DF0D600A7E488; remoteInfo = libmalloc_stress_test; }; 925383D81BD03D0000F745DB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFC1BE516A908F800027192 /* Project object */; proxyType = 1; remoteGlobalIDString = 925383D41BD03C0500F745DB; remoteInfo = darwintests; }; B60A579720093093006215CB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFC1BE516A908F800027192 /* Project object */; proxyType = 1; remoteGlobalIDString = B6D2ED492007D76F007AF994; remoteInfo = libmalloc_replay; }; B629CF43202BB389007719B9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFC1BE516A908F800027192 /* Project object */; proxyType = 1; remoteGlobalIDString = B629CF2B202BB337007719B9; remoteInfo = libmalloc_alt; }; B676F4AB202B66EF00933F6D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFC1BE516A908F800027192 /* Project object */; proxyType = 1; remoteGlobalIDString = B6910F65202B630D00FF2EB0; remoteInfo = libmalloc_mp; }; C0CE454F1C52CCBD00C24048 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFC1BE516A908F800027192 /* Project object */; proxyType = 1; remoteGlobalIDString = C0CE452F1C52C90500C24048; remoteInfo = libmalloc_static; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 456E51C7197DF0D600A7E488 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; C0CE454D1C52C9D900C24048 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; dstPath = /usr/local/lib/loaderd; dstSubfolderSpec = 0; files = ( C0CE454E1C52C9E600C24048 /* libmalloc.a in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 084F5E831D50204F006CD296 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 088C4D731D1AEFB5005C6B36 /* radix_tree_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = radix_tree_internal.h; sourceTree = ""; }; 088C4D741D1AEFB5005C6B36 /* radix_tree.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = radix_tree.c; sourceTree = ""; }; 088C4D751D1AEFB5005C6B36 /* radix_tree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = radix_tree.h; sourceTree = ""; }; 088C4D761D1AEFC5005C6B36 /* radix_tree_test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = radix_tree_test.m; sourceTree = ""; }; 08C28B3A1D501ACC000AE997 /* radix_tree_debug.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = radix_tree_debug.c; sourceTree = ""; }; 08C28B401D501D2C000AE997 /* radix-tree */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "radix-tree"; sourceTree = BUILT_PRODUCTS_DIR; }; 08C28B421D501D2C000AE997 /* radix_tree_main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = radix_tree_main.m; sourceTree = ""; }; 0D468DCC1C7BEE56006FACF5 /* magazine_lite.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = magazine_lite.c; sourceTree = ""; }; 0D468DCD1C7BEE65006FACF5 /* stack_logging_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stack_logging_internal.h; sourceTree = ""; }; 0D468DCE1C7BEE74006FACF5 /* stack_logging_test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = stack_logging_test.c; sourceTree = ""; }; 2B67B5672040B3A50003E78F /* _malloc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _malloc.h; sourceTree = ""; }; 3D157E7320354E02001630BF /* perfdata.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = perfdata.framework; path = ../MacOSX10.14.Internal.sdk/System/Library/PrivateFrameworks/perfdata.framework; sourceTree = SDKROOT; }; 3FC452FF18E4ABFE003D6A38 /* manpages.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = manpages.sh; sourceTree = ""; }; 3FE91FC916A90A8D00D1238A /* malloc.3 */ = {isa = PBXFileReference; lastKnownFileType = text; path = malloc.3; sourceTree = ""; }; 3FE91FCA16A90A8D00D1238A /* malloc_size.3 */ = {isa = PBXFileReference; lastKnownFileType = text; path = malloc_size.3; sourceTree = ""; }; 3FE91FCB16A90A8D00D1238A /* malloc_zone_malloc.3 */ = {isa = PBXFileReference; lastKnownFileType = text; path = malloc_zone_malloc.3; sourceTree = ""; }; 3FE91FD116A90A8D00D1238A /* bitarray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bitarray.c; sourceTree = ""; }; 3FE91FD216A90A8D00D1238A /* bitarray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bitarray.h; sourceTree = ""; }; 3FE91FD616A90A8D00D1238A /* magazine_malloc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = magazine_malloc.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; 3FE91FD716A90A8D00D1238A /* magmallocProvider.d */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.dtrace; path = magmallocProvider.d; sourceTree = ""; }; 3FE91FD816A90A8D00D1238A /* malloc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = malloc.c; sourceTree = ""; }; 3FE91FD916A90A8D00D1238A /* printf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = printf.h; sourceTree = ""; }; 3FE91FDA16A90A8D00D1238A /* nano_malloc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nano_malloc.c; sourceTree = ""; }; 3FE91FDC16A90A8D00D1238A /* stack_logging_disk.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = stack_logging_disk.c; sourceTree = ""; usesTabs = 1; }; 3FE91FE016A90A8D00D1238A /* libmalloc.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = libmalloc.xcconfig; sourceTree = ""; }; 3FE91FE116A90A8D00D1238A /* libmalloc_eos.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = libmalloc_eos.xcconfig; sourceTree = ""; }; 3FE91FE316A90A8D00D1238A /* sanitise_headers.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = sanitise_headers.sh; sourceTree = ""; }; 3FE91FE816A90AEC00D1238A /* libsystem_malloc.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libsystem_malloc.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; 3FE91FF916A90BEF00D1238A /* malloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = malloc.h; sourceTree = ""; }; 3FE91FFB16A90E6C00D1238A /* legacy_malloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = legacy_malloc.h; sourceTree = ""; }; 3FE9201016A9109E00D1238A /* libmalloc_eOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libmalloc_eOS.a; sourceTree = BUILT_PRODUCTS_DIR; }; 456E51C9197DF0D600A7E488 /* libmalloc_stress_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = libmalloc_stress_test; sourceTree = BUILT_PRODUCTS_DIR; }; 8CB962B01F7E9F610046942E /* asan.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = asan.c; sourceTree = ""; }; 8CB962B11F7E9FD00046942E /* tsan.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tsan.c; sourceTree = ""; }; 925383D01BD03B4A00F745DB /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 925383D11BD03B4A00F745DB /* stress_test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = stress_test.c; sourceTree = ""; }; 925383D31BD03B8F00F745DB /* manpages.lst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = manpages.lst; sourceTree = ""; }; B61341DD20114B070038D163 /* ktrace.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ktrace.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.Internal.sdk/System/Library/PrivateFrameworks/ktrace.framework; sourceTree = DEVELOPER_DIR; }; B629CF29202BA3C2007719B9 /* libmalloc_resolver.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = libmalloc_resolver.xcconfig; sourceTree = ""; }; B629CF42202BB337007719B9 /* libmalloc_alt.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libmalloc_alt.a; sourceTree = BUILT_PRODUCTS_DIR; }; B629CF46202BBDEC007719B9 /* resolver_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = resolver_internal.h; sourceTree = ""; }; B629CF48202BBE3B007719B9 /* resolver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = resolver.h; sourceTree = ""; }; B64E100A205311DC004C4BA6 /* malloc_size_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = malloc_size_test.c; sourceTree = ""; }; B6536A62204754B6005FBE22 /* perf_contended_malloc_free.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = perf_contended_malloc_free.c; sourceTree = ""; }; B6536A6320475BA4005FBE22 /* basic_malloc_free_perf.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = basic_malloc_free_perf.c; sourceTree = ""; }; B65FBE2B2087AA2F00E21F59 /* malloc_printf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = malloc_printf.c; sourceTree = ""; }; B66AA658202A70B00019D607 /* libmalloc_resolved.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = libmalloc_resolved.xcconfig; sourceTree = ""; }; B66C71D72034BFAE0047E265 /* malloc_common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = malloc_common.h; sourceTree = ""; }; B66C71D82034BFAE0047E265 /* malloc_common.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = malloc_common.c; sourceTree = ""; }; B670DABD2072D0BB00139A1D /* perf_realloc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = perf_realloc.c; sourceTree = ""; }; B671CFFD207578CC00EEAF20 /* libmalloc.dirty */ = {isa = PBXFileReference; lastKnownFileType = text; path = libmalloc.dirty; sourceTree = ""; }; B675F74520213D0A00B5038B /* nano_tests.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nano_tests.c; sourceTree = ""; }; B68B7F9C1FCDCBC600BAD1AA /* nano_malloc_common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nano_malloc_common.h; sourceTree = ""; }; B68B7F9D1FCDCBC600BAD1AA /* nano_malloc_common.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nano_malloc_common.c; sourceTree = ""; }; B68B7FA21FCDD60F00BAD1AA /* nanov2_malloc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nanov2_malloc.h; sourceTree = ""; }; B68B7FA41FCDD9A500BAD1AA /* nanov2_malloc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nanov2_malloc.c; sourceTree = ""; }; B6910F89202B630D00FF2EB0 /* libmalloc_mp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libmalloc_mp.a; sourceTree = BUILT_PRODUCTS_DIR; }; B69B2B941FB3D00500FD5A8F /* magazine_malloc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = magazine_malloc.c; sourceTree = ""; }; B6A414EA1FBDF01C0038DC53 /* malloc_claimed_address_tests.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = malloc_claimed_address_tests.c; sourceTree = ""; }; B6A494971F9918DD0016A799 /* calloc_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = calloc_test.c; sourceTree = ""; }; B6A9C48C1F991716007D0853 /* malloc_free_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = malloc_free_test.c; sourceTree = ""; }; B6C1C9C720D9B70F002CCC0B /* nano_trace_replay.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nano_trace_replay.c; sourceTree = ""; }; B6CA644D1FCE2C0A00DEBA12 /* nanov2_zone.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nanov2_zone.h; sourceTree = ""; }; B6CA64511FCF1AAD00DEBA12 /* nano_zone_common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nano_zone_common.h; sourceTree = ""; }; B6D2ED512007D76F007AF994 /* libmalloc_replay */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = libmalloc_replay; sourceTree = BUILT_PRODUCTS_DIR; }; B6D2ED552007D91A007AF994 /* malloc_replay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = malloc_replay.cpp; sourceTree = ""; }; B6D2ED562007D91A007AF994 /* malloc_replay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = malloc_replay.h; sourceTree = ""; }; B6D5C7ED202E26CA0035E376 /* resolver.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = resolver.c; sourceTree = ""; }; C0352EC61C3F3C3600DB5126 /* malloc_private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = malloc_private.h; sourceTree = ""; }; C0CE450E1C52B9E300C24048 /* libmalloc_static.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = libmalloc_static.xcconfig; sourceTree = ""; }; C0CE454C1C52C90500C24048 /* libmalloc.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libmalloc.a; sourceTree = BUILT_PRODUCTS_DIR; }; C92853A01C767F08001FEAF3 /* install-codes.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "install-codes.sh"; sourceTree = ""; }; C931B58F1C81248100D0D230 /* madvise.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = madvise.c; sourceTree = ""; }; C932D2631D6B6ED40063B19E /* magazine_tiny_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = magazine_tiny_test.c; sourceTree = ""; }; C932D2641D6B73270063B19E /* dtrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dtrace.h; sourceTree = ""; }; C932D2661D6B8D840063B19E /* vm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vm.c; sourceTree = ""; }; C932D2671D6B8D840063B19E /* vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vm.h; sourceTree = ""; }; C938BBD21C74F7A400522BBD /* trace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trace.h; sourceTree = ""; }; C93F76D71D6B9F8C0088931B /* magazine_testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = magazine_testing.h; sourceTree = ""; }; C9571C391C18AA1D00A67EE3 /* stack_logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stack_logging.h; sourceTree = ""; }; C9571C3C1C18AD5F00A67EE3 /* balloon.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = balloon.cpp; sourceTree = ""; }; C9571C3D1C18AD5F00A67EE3 /* balloon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = balloon.h; sourceTree = ""; }; C9571C3E1C18AD5F00A67EE3 /* Benchmark.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Benchmark.cpp; sourceTree = ""; }; C9571C3F1C18AD5F00A67EE3 /* Benchmark.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Benchmark.h; sourceTree = ""; }; C9571C401C18AD5F00A67EE3 /* big.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = big.cpp; sourceTree = ""; }; C9571C411C18AD5F00A67EE3 /* big.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = big.h; sourceTree = ""; }; C9571C421C18AD5F00A67EE3 /* churn.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = churn.cpp; sourceTree = ""; }; C9571C431C18AD5F00A67EE3 /* churn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = churn.h; sourceTree = ""; }; C9571C441C18AD5F00A67EE3 /* CommandLine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CommandLine.cpp; sourceTree = ""; }; C9571C451C18AD5F00A67EE3 /* CommandLine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommandLine.h; sourceTree = ""; }; C9571C461C18AD5F00A67EE3 /* CPUCount.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CPUCount.cpp; sourceTree = ""; }; C9571C471C18AD5F00A67EE3 /* CPUCount.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CPUCount.h; sourceTree = ""; }; C9571C4C1C18AD5F00A67EE3 /* fragment.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = fragment.cpp; sourceTree = ""; }; C9571C4D1C18AD5F00A67EE3 /* fragment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fragment.h; sourceTree = ""; }; C9571C4E1C18AD5F00A67EE3 /* Interpreter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Interpreter.cpp; sourceTree = ""; }; C9571C4F1C18AD5F00A67EE3 /* Interpreter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Interpreter.h; sourceTree = ""; }; C9571C501C18AD5F00A67EE3 /* list.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = list.cpp; sourceTree = ""; }; C9571C511C18AD5F00A67EE3 /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = list.h; sourceTree = ""; }; C9571C521C18AD5F00A67EE3 /* mbmalloc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mbmalloc.cpp; sourceTree = ""; }; C9571C531C18AD5F00A67EE3 /* mbmalloc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mbmalloc.h; sourceTree = ""; }; C9571C541C18AD5F00A67EE3 /* medium.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = medium.cpp; sourceTree = ""; }; C9571C551C18AD5F00A67EE3 /* medium.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = medium.h; sourceTree = ""; }; C9571C561C18AD5F00A67EE3 /* memalign.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = memalign.cpp; sourceTree = ""; }; C9571C571C18AD5F00A67EE3 /* memalign.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = memalign.h; sourceTree = ""; }; C9571C581C18AD5F00A67EE3 /* message.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = message.cpp; sourceTree = ""; }; C9571C591C18AD5F00A67EE3 /* message.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = message.h; sourceTree = ""; }; C9571C5A1C18AD5F00A67EE3 /* realloc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = realloc.cpp; sourceTree = ""; }; C9571C5B1C18AD5F00A67EE3 /* realloc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = realloc.h; sourceTree = ""; }; C9571C5E1C18AD5F00A67EE3 /* stress_aligned.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = stress_aligned.cpp; sourceTree = ""; }; C9571C5F1C18AD5F00A67EE3 /* stress_aligned.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = stress_aligned.h; sourceTree = ""; }; C9571C601C18AD5F00A67EE3 /* stress.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = stress.cpp; sourceTree = ""; }; C9571C611C18AD5F00A67EE3 /* stress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = stress.h; sourceTree = ""; }; C9571C641C18AD5F00A67EE3 /* tree.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tree.cpp; sourceTree = ""; }; C9571C651C18AD5F00A67EE3 /* tree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tree.h; sourceTree = ""; }; C9571C661C18AD6A00A67EE3 /* MallocBench.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MallocBench.cpp; sourceTree = ""; }; C957426D1BF2C0C80027269A /* internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = internal.h; sourceTree = ""; }; C957426E1BF2C1480027269A /* locking.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = locking.h; sourceTree = ""; }; C95742791BF2C5F40027269A /* nano_malloc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nano_malloc.h; sourceTree = ""; }; C957427B1BF2C8DE0027269A /* debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug.h; sourceTree = ""; }; C957427E1BF33D130027269A /* nano_zone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nano_zone.h; sourceTree = ""; }; C95742861BF3F9550027269A /* magazine_zone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = magazine_zone.h; sourceTree = ""; }; C95742891BF3FD290027269A /* base.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base.h; sourceTree = ""; }; C957428C1BF411330027269A /* thresholds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thresholds.h; sourceTree = ""; }; C957428F1BF419DF0027269A /* magazine_tiny.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = magazine_tiny.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; C95742921BF41C970027269A /* magazine_inline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = magazine_inline.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; C95742951BF41E480027269A /* magazine_malloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = magazine_malloc.h; sourceTree = ""; }; C95742981BF670D00027269A /* magazine_small.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = magazine_small.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; C957429B1BF672F80027269A /* magazine_large.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = magazine_large.c; sourceTree = ""; }; C957429E1BF681B00027269A /* purgeable_malloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = purgeable_malloc.c; sourceTree = ""; }; C957429F1BF681B00027269A /* purgeable_malloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = purgeable_malloc.h; sourceTree = ""; }; C95742A41BF6842F0027269A /* frozen_malloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = frozen_malloc.c; sourceTree = ""; }; C95742A51BF6842F0027269A /* frozen_malloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = frozen_malloc.h; sourceTree = ""; }; C95742AA1BF685CB0027269A /* legacy_malloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = legacy_malloc.c; sourceTree = ""; }; C99E32091D6F7366005655A8 /* magazine_rack.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = magazine_rack.c; sourceTree = ""; }; C99E320A1D6F7366005655A8 /* magazine_rack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = magazine_rack.h; sourceTree = ""; }; C9ABCA041CB6FC6800ECB399 /* empty.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = empty.s; sourceTree = ""; }; C9F77BBA1BF2B84800812E13 /* platform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = platform.h; sourceTree = ""; }; C9F8C2681D70B521008C4044 /* magazine_small_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = magazine_small_test.c; sourceTree = ""; }; C9F8C2691D74C93A008C4044 /* magazine_rack.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = magazine_rack.c; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 08C28B3D1D501D2C000AE997 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 084F5E841D50204F006CD296 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 3FE91FE516A90AEC00D1238A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 3FE9200716A9109E00D1238A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 456E51C6197DF0D600A7E488 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; B629CF3D202BB337007719B9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; B6910F77202B630D00FF2EB0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; B6D2ED4C2007D76F007AF994 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3D157E7420354E02001630BF /* perfdata.framework in Frameworks */, B61341DE20114B660038D163 /* ktrace.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; C0CE453E1C52C90500C24048 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 084F5E821D50204F006CD296 /* Frameworks */ = { isa = PBXGroup; children = ( 3D157E7320354E02001630BF /* perfdata.framework */, B61341DD20114B070038D163 /* ktrace.framework */, 084F5E831D50204F006CD296 /* Foundation.framework */, ); name = Frameworks; sourceTree = ""; }; 08C28B411D501D2C000AE997 /* tools */ = { isa = PBXGroup; children = ( 08C28B421D501D2C000AE997 /* radix_tree_main.m */, B6D2ED552007D91A007AF994 /* malloc_replay.cpp */, B6D2ED562007D91A007AF994 /* malloc_replay.h */, ); path = tools; sourceTree = ""; }; 3FE91FC816A90A8D00D1238A /* man */ = { isa = PBXGroup; children = ( 925383D31BD03B8F00F745DB /* manpages.lst */, 3FE91FC916A90A8D00D1238A /* malloc.3 */, 3FE91FCA16A90A8D00D1238A /* malloc_size.3 */, 3FE91FCB16A90A8D00D1238A /* malloc_zone_malloc.3 */, ); path = man; sourceTree = ""; }; 3FE91FCC16A90A8D00D1238A /* src */ = { isa = PBXGroup; children = ( 08C28B3A1D501ACC000AE997 /* radix_tree_debug.c */, 088C4D731D1AEFB5005C6B36 /* radix_tree_internal.h */, 088C4D741D1AEFB5005C6B36 /* radix_tree.c */, 088C4D751D1AEFB5005C6B36 /* radix_tree.h */, C95742891BF3FD290027269A /* base.h */, 3FE91FD116A90A8D00D1238A /* bitarray.c */, 3FE91FD216A90A8D00D1238A /* bitarray.h */, C957427B1BF2C8DE0027269A /* debug.h */, C932D2641D6B73270063B19E /* dtrace.h */, C9ABCA041CB6FC6800ECB399 /* empty.s */, C95742A41BF6842F0027269A /* frozen_malloc.c */, C95742A51BF6842F0027269A /* frozen_malloc.h */, C957426D1BF2C0C80027269A /* internal.h */, C95742AA1BF685CB0027269A /* legacy_malloc.c */, 3FE91FFB16A90E6C00D1238A /* legacy_malloc.h */, C957426E1BF2C1480027269A /* locking.h */, C95742921BF41C970027269A /* magazine_inline.h */, C957429B1BF672F80027269A /* magazine_large.c */, 0D468DCC1C7BEE56006FACF5 /* magazine_lite.c */, 3FE91FD616A90A8D00D1238A /* magazine_malloc.c */, C95742951BF41E480027269A /* magazine_malloc.h */, B65FBE2B2087AA2F00E21F59 /* malloc_printf.c */, C99E32091D6F7366005655A8 /* magazine_rack.c */, C99E320A1D6F7366005655A8 /* magazine_rack.h */, C95742981BF670D00027269A /* magazine_small.c */, C957428F1BF419DF0027269A /* magazine_tiny.c */, C95742861BF3F9550027269A /* magazine_zone.h */, 3FE91FD716A90A8D00D1238A /* magmallocProvider.d */, 3FE91FD816A90A8D00D1238A /* malloc.c */, B66C71D82034BFAE0047E265 /* malloc_common.c */, B66C71D72034BFAE0047E265 /* malloc_common.h */, B68B7F9D1FCDCBC600BAD1AA /* nano_malloc_common.c */, B68B7F9C1FCDCBC600BAD1AA /* nano_malloc_common.h */, 3FE91FDA16A90A8D00D1238A /* nano_malloc.c */, C95742791BF2C5F40027269A /* nano_malloc.h */, C957427E1BF33D130027269A /* nano_zone.h */, B6CA64511FCF1AAD00DEBA12 /* nano_zone_common.h */, B68B7FA41FCDD9A500BAD1AA /* nanov2_malloc.c */, B68B7FA21FCDD60F00BAD1AA /* nanov2_malloc.h */, B6CA644D1FCE2C0A00DEBA12 /* nanov2_zone.h */, C9F77BBA1BF2B84800812E13 /* platform.h */, 3FE91FD916A90A8D00D1238A /* printf.h */, C957429E1BF681B00027269A /* purgeable_malloc.c */, C957429F1BF681B00027269A /* purgeable_malloc.h */, 3FE91FDC16A90A8D00D1238A /* stack_logging_disk.c */, 0D468DCD1C7BEE65006FACF5 /* stack_logging_internal.h */, C957428C1BF411330027269A /* thresholds.h */, C938BBD21C74F7A400522BBD /* trace.h */, C932D2661D6B8D840063B19E /* vm.c */, C932D2671D6B8D840063B19E /* vm.h */, ); path = src; sourceTree = ""; }; 3FE91FDF16A90A8D00D1238A /* xcodeconfig */ = { isa = PBXGroup; children = ( 3FE91FE016A90A8D00D1238A /* libmalloc.xcconfig */, 3FE91FE116A90A8D00D1238A /* libmalloc_eos.xcconfig */, C0CE450E1C52B9E300C24048 /* libmalloc_static.xcconfig */, B66AA658202A70B00019D607 /* libmalloc_resolved.xcconfig */, B629CF29202BA3C2007719B9 /* libmalloc_resolver.xcconfig */, B671CFFD207578CC00EEAF20 /* libmalloc.dirty */, ); path = xcodeconfig; sourceTree = ""; }; 3FE91FE216A90A8D00D1238A /* xcodescripts */ = { isa = PBXGroup; children = ( 3FC452FF18E4ABFE003D6A38 /* manpages.sh */, 3FE91FE316A90A8D00D1238A /* sanitise_headers.sh */, C92853A01C767F08001FEAF3 /* install-codes.sh */, ); path = xcodescripts; sourceTree = ""; }; 3FE91FE916A90AEC00D1238A /* Products */ = { isa = PBXGroup; children = ( 3FE91FE816A90AEC00D1238A /* libsystem_malloc.dylib */, 3FE9201016A9109E00D1238A /* libmalloc_eOS.a */, 456E51C9197DF0D600A7E488 /* libmalloc_stress_test */, C0CE454C1C52C90500C24048 /* libmalloc.a */, 08C28B401D501D2C000AE997 /* radix-tree */, B6D2ED512007D76F007AF994 /* libmalloc_replay */, B6910F89202B630D00FF2EB0 /* libmalloc_mp.a */, B629CF42202BB337007719B9 /* libmalloc_alt.a */, ); name = Products; sourceTree = ""; }; 3FE91FF716A90BEF00D1238A /* include */ = { isa = PBXGroup; children = ( 3FE91FF816A90BEF00D1238A /* malloc */, ); path = include; sourceTree = ""; }; 3FE91FF816A90BEF00D1238A /* malloc */ = { isa = PBXGroup; children = ( 2B67B5672040B3A50003E78F /* _malloc.h */, 3FE91FF916A90BEF00D1238A /* malloc.h */, ); path = malloc; sourceTree = ""; }; 3FFC1BE416A908F800027192 = { isa = PBXGroup; children = ( 3FE91FF716A90BEF00D1238A /* include */, 3FE91FC816A90A8D00D1238A /* man */, C9571C381C18AA0A00A67EE3 /* private */, 3FE91FCC16A90A8D00D1238A /* src */, B629CF45202BBDCC007719B9 /* resolver */, 925383BD1BD03B4A00F745DB /* tests */, 3FE91FDF16A90A8D00D1238A /* xcodeconfig */, 3FE91FE216A90A8D00D1238A /* xcodescripts */, 08C28B411D501D2C000AE997 /* tools */, 3FE91FE916A90AEC00D1238A /* Products */, 084F5E821D50204F006CD296 /* Frameworks */, ); sourceTree = ""; tabWidth = 4; usesTabs = 1; }; 925383BD1BD03B4A00F745DB /* tests */ = { isa = PBXGroup; children = ( 8CB962B01F7E9F610046942E /* asan.c */, 8CB962B11F7E9FD00046942E /* tsan.c */, C931B58F1C81248100D0D230 /* madvise.c */, C9F8C2691D74C93A008C4044 /* magazine_rack.c */, B6A494971F9918DD0016A799 /* calloc_test.c */, B6A9C48C1F991716007D0853 /* malloc_free_test.c */, B6A414EA1FBDF01C0038DC53 /* malloc_claimed_address_tests.c */, C9F8C2681D70B521008C4044 /* magazine_small_test.c */, B64E100A205311DC004C4BA6 /* malloc_size_test.c */, C93F76D71D6B9F8C0088931B /* magazine_testing.h */, C932D2631D6B6ED40063B19E /* magazine_tiny_test.c */, B69B2B941FB3D00500FD5A8F /* magazine_malloc.c */, 925383D01BD03B4A00F745DB /* Makefile */, C9571C3B1C18AD4F00A67EE3 /* MallocBench */, C9571C661C18AD6A00A67EE3 /* MallocBench.cpp */, B6536A6320475BA4005FBE22 /* basic_malloc_free_perf.c */, B675F74520213D0A00B5038B /* nano_tests.c */, B6C1C9C720D9B70F002CCC0B /* nano_trace_replay.c */, B6536A62204754B6005FBE22 /* perf_contended_malloc_free.c */, B670DABD2072D0BB00139A1D /* perf_realloc.c */, 088C4D761D1AEFC5005C6B36 /* radix_tree_test.m */, 0D468DCE1C7BEE74006FACF5 /* stack_logging_test.c */, 925383D11BD03B4A00F745DB /* stress_test.c */, ); path = tests; sourceTree = ""; }; B629CF45202BBDCC007719B9 /* resolver */ = { isa = PBXGroup; children = ( B629CF48202BBE3B007719B9 /* resolver.h */, B629CF46202BBDEC007719B9 /* resolver_internal.h */, B6D5C7ED202E26CA0035E376 /* resolver.c */, ); path = resolver; sourceTree = ""; }; C9571C381C18AA0A00A67EE3 /* private */ = { isa = PBXGroup; children = ( C9571C391C18AA1D00A67EE3 /* stack_logging.h */, C0352EC61C3F3C3600DB5126 /* malloc_private.h */, ); path = private; sourceTree = ""; }; C9571C3B1C18AD4F00A67EE3 /* MallocBench */ = { isa = PBXGroup; children = ( C9571C3C1C18AD5F00A67EE3 /* balloon.cpp */, C9571C3D1C18AD5F00A67EE3 /* balloon.h */, C9571C3E1C18AD5F00A67EE3 /* Benchmark.cpp */, C9571C3F1C18AD5F00A67EE3 /* Benchmark.h */, C9571C401C18AD5F00A67EE3 /* big.cpp */, C9571C411C18AD5F00A67EE3 /* big.h */, C9571C421C18AD5F00A67EE3 /* churn.cpp */, C9571C431C18AD5F00A67EE3 /* churn.h */, C9571C441C18AD5F00A67EE3 /* CommandLine.cpp */, C9571C451C18AD5F00A67EE3 /* CommandLine.h */, C9571C461C18AD5F00A67EE3 /* CPUCount.cpp */, C9571C471C18AD5F00A67EE3 /* CPUCount.h */, C9571C4C1C18AD5F00A67EE3 /* fragment.cpp */, C9571C4D1C18AD5F00A67EE3 /* fragment.h */, C9571C4E1C18AD5F00A67EE3 /* Interpreter.cpp */, C9571C4F1C18AD5F00A67EE3 /* Interpreter.h */, C9571C501C18AD5F00A67EE3 /* list.cpp */, C9571C511C18AD5F00A67EE3 /* list.h */, C9571C521C18AD5F00A67EE3 /* mbmalloc.cpp */, C9571C531C18AD5F00A67EE3 /* mbmalloc.h */, C9571C541C18AD5F00A67EE3 /* medium.cpp */, C9571C551C18AD5F00A67EE3 /* medium.h */, C9571C561C18AD5F00A67EE3 /* memalign.cpp */, C9571C571C18AD5F00A67EE3 /* memalign.h */, C9571C581C18AD5F00A67EE3 /* message.cpp */, C9571C591C18AD5F00A67EE3 /* message.h */, C9571C5A1C18AD5F00A67EE3 /* realloc.cpp */, C9571C5B1C18AD5F00A67EE3 /* realloc.h */, C9571C5E1C18AD5F00A67EE3 /* stress_aligned.cpp */, C9571C5F1C18AD5F00A67EE3 /* stress_aligned.h */, C9571C601C18AD5F00A67EE3 /* stress.cpp */, C9571C611C18AD5F00A67EE3 /* stress.h */, C9571C641C18AD5F00A67EE3 /* tree.cpp */, C9571C651C18AD5F00A67EE3 /* tree.h */, ); path = MallocBench; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 3FE91FE616A90AEC00D1238A /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( C957428A1BF3FD290027269A /* base.h in Headers */, C0352EC71C3F3C4400DB5126 /* malloc_private.h in Headers */, 0D468DD01C7BEF71006FACF5 /* stack_logging_internal.h in Headers */, C95742731BF2C2880027269A /* internal.h in Headers */, B6CA644E1FCE2C1900DEBA12 /* nanov2_zone.h in Headers */, C99E320C1D6F7366005655A8 /* magazine_rack.h in Headers */, C95742721BF2C2880027269A /* bitarray.h in Headers */, C932D2691D6B8D840063B19E /* vm.h in Headers */, B6CA64521FCF1AD200DEBA12 /* nano_zone_common.h in Headers */, C938BBD31C74F7A400522BBD /* trace.h in Headers */, C95742A81BF6842F0027269A /* frozen_malloc.h in Headers */, C957428D1BF411330027269A /* thresholds.h in Headers */, C95742741BF2C2880027269A /* locking.h in Headers */, C95742931BF41C970027269A /* magazine_inline.h in Headers */, B68B7FA31FCDD67100BAD1AA /* nanov2_malloc.h in Headers */, C95742761BF2C2880027269A /* platform.h in Headers */, C957427A1BF2C67E0027269A /* nano_malloc.h in Headers */, B66C71D92034BFAE0047E265 /* malloc_common.h in Headers */, C957427C1BF2C8DE0027269A /* debug.h in Headers */, 2B67B5682040B3AF0003E78F /* _malloc.h in Headers */, C95742961BF41E480027269A /* magazine_malloc.h in Headers */, C9571C3A1C18AA1D00A67EE3 /* stack_logging.h in Headers */, 3FE91FFA16A90BEF00D1238A /* malloc.h in Headers */, C95742871BF3F9550027269A /* magazine_zone.h in Headers */, C95742771BF2C2880027269A /* legacy_malloc.h in Headers */, C95742A21BF681B00027269A /* purgeable_malloc.h in Headers */, B68B7F9E1FCDCBC600BAD1AA /* nano_malloc_common.h in Headers */, C957427F1BF33D130027269A /* nano_zone.h in Headers */, C95742751BF2C2880027269A /* printf.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 3FE9200816A9109E00D1238A /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( C95742941BF41C970027269A /* magazine_inline.h in Headers */, C95742801BF33D130027269A /* nano_zone.h in Headers */, B6CA644F1FCE2C1A00DEBA12 /* nanov2_zone.h in Headers */, C957428E1BF411330027269A /* thresholds.h in Headers */, C957427D1BF2C8DE0027269A /* debug.h in Headers */, C95742A91BF6842F0027269A /* frozen_malloc.h in Headers */, C95742881BF3F9550027269A /* magazine_zone.h in Headers */, C95742971BF41E480027269A /* magazine_malloc.h in Headers */, C95742A31BF681B00027269A /* purgeable_malloc.h in Headers */, C957428B1BF3FD290027269A /* base.h in Headers */, B6CA64531FCF1AD400DEBA12 /* nano_zone_common.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; C0CE453F1C52C90500C24048 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( C0CE45401C52C90500C24048 /* magazine_inline.h in Headers */, C0CE45411C52C90500C24048 /* nano_zone.h in Headers */, B6CA64501FCE2C1B00DEBA12 /* nanov2_zone.h in Headers */, C0CE45421C52C90500C24048 /* thresholds.h in Headers */, C0CE45431C52C90500C24048 /* debug.h in Headers */, C0CE45441C52C90500C24048 /* frozen_malloc.h in Headers */, C0CE45451C52C90500C24048 /* magazine_zone.h in Headers */, C0CE45461C52C90500C24048 /* magazine_malloc.h in Headers */, C0CE45471C52C90500C24048 /* purgeable_malloc.h in Headers */, C0CE45481C52C90500C24048 /* base.h in Headers */, B6CA64541FCF1AD400DEBA12 /* nano_zone_common.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXLegacyTarget section */ 925383D41BD03C0500F745DB /* darwintests */ = { isa = PBXLegacyTarget; buildArgumentsString = "$(ACTION)"; buildConfigurationList = 925383D51BD03C0500F745DB /* Build configuration list for PBXLegacyTarget "darwintests" */; buildPhases = ( ); buildToolPath = /usr/bin/make; buildWorkingDirectory = tests; dependencies = ( ); name = darwintests; passBuildSettingsInEnvironment = 1; productName = darwintests; }; /* End PBXLegacyTarget section */ /* Begin PBXNativeTarget section */ 08C28B3F1D501D2C000AE997 /* radix-tree */ = { isa = PBXNativeTarget; buildConfigurationList = 08C28B441D501D2C000AE997 /* Build configuration list for PBXNativeTarget "radix-tree" */; buildPhases = ( 08C28B3C1D501D2C000AE997 /* Sources */, 08C28B3D1D501D2C000AE997 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = "radix-tree"; productName = "radix-tree"; productReference = 08C28B401D501D2C000AE997 /* radix-tree */; productType = "com.apple.product-type.tool"; }; 3FE91FE716A90AEC00D1238A /* libsystem_malloc */ = { isa = PBXNativeTarget; buildConfigurationList = 3FE91FEA16A90AEC00D1238A /* Build configuration list for PBXNativeTarget "libsystem_malloc" */; buildPhases = ( 3FE91FE416A90AEC00D1238A /* Sources */, 3FE91FE516A90AEC00D1238A /* Frameworks */, 3FE91FE616A90AEC00D1238A /* Headers */, C92853A11C767F78001FEAF3 /* Install Codes File */, ); buildRules = ( ); dependencies = ( B676F4AC202B66EF00933F6D /* PBXTargetDependency */, B629CF44202BB389007719B9 /* PBXTargetDependency */, ); name = libsystem_malloc; productName = libmalloc; productReference = 3FE91FE816A90AEC00D1238A /* libsystem_malloc.dylib */; productType = "com.apple.product-type.library.dynamic"; }; 3FE91FFD16A9109E00D1238A /* libmalloc_eOS */ = { isa = PBXNativeTarget; buildConfigurationList = 3FE9200D16A9109E00D1238A /* Build configuration list for PBXNativeTarget "libmalloc_eOS" */; buildPhases = ( 3FE91FFE16A9109E00D1238A /* Sources */, 3FE9200716A9109E00D1238A /* Frameworks */, 3FE9200816A9109E00D1238A /* Headers */, ); buildRules = ( ); dependencies = ( ); name = libmalloc_eOS; productName = libmalloc; productReference = 3FE9201016A9109E00D1238A /* libmalloc_eOS.a */; productType = "com.apple.product-type.library.static"; }; 456E51C8197DF0D600A7E488 /* libmalloc_stress_test */ = { isa = PBXNativeTarget; buildConfigurationList = 456E51CF197DF0D600A7E488 /* Build configuration list for PBXNativeTarget "libmalloc_stress_test" */; buildPhases = ( 456E51C5197DF0D600A7E488 /* Sources */, 456E51C6197DF0D600A7E488 /* Frameworks */, 456E51C7197DF0D600A7E488 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = libmalloc_stress_test; productName = libmalloc_stress_test; productReference = 456E51C9197DF0D600A7E488 /* libmalloc_stress_test */; productType = "com.apple.product-type.tool"; }; B629CF2B202BB337007719B9 /* libmalloc_alt */ = { isa = PBXNativeTarget; buildConfigurationList = B629CF3F202BB337007719B9 /* Build configuration list for PBXNativeTarget "libmalloc_alt" */; buildPhases = ( B629CF2C202BB337007719B9 /* Sources */, B629CF3D202BB337007719B9 /* Frameworks */, B629CF3E202BB337007719B9 /* Symlink normal variant */, ); buildRules = ( ); dependencies = ( ); name = libmalloc_alt; productName = libmalloc; productReference = B629CF42202BB337007719B9 /* libmalloc_alt.a */; productType = "com.apple.product-type.library.static"; }; B6910F65202B630D00FF2EB0 /* libmalloc_mp */ = { isa = PBXNativeTarget; buildConfigurationList = B6910F86202B630D00FF2EB0 /* Build configuration list for PBXNativeTarget "libmalloc_mp" */; buildPhases = ( B6910F66202B630D00FF2EB0 /* Sources */, B6910F77202B630D00FF2EB0 /* Frameworks */, B629CF2A202BB226007719B9 /* Symlink normal variant */, ); buildRules = ( ); dependencies = ( ); name = libmalloc_mp; productName = libmalloc; productReference = B6910F89202B630D00FF2EB0 /* libmalloc_mp.a */; productType = "com.apple.product-type.library.static"; }; B6D2ED492007D76F007AF994 /* libmalloc_replay */ = { isa = PBXNativeTarget; buildConfigurationList = B6D2ED4E2007D76F007AF994 /* Build configuration list for PBXNativeTarget "libmalloc_replay" */; buildPhases = ( B6D2ED4A2007D76F007AF994 /* Sources */, B6D2ED4C2007D76F007AF994 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = libmalloc_replay; productName = libmalloc_stress_test; productReference = B6D2ED512007D76F007AF994 /* libmalloc_replay */; productType = "com.apple.product-type.tool"; }; C0CE452F1C52C90500C24048 /* libmalloc_static */ = { isa = PBXNativeTarget; buildConfigurationList = C0CE45491C52C90500C24048 /* Build configuration list for PBXNativeTarget "libmalloc_static" */; buildPhases = ( C0CE45301C52C90500C24048 /* Sources */, C0CE453E1C52C90500C24048 /* Frameworks */, C0CE453F1C52C90500C24048 /* Headers */, C0CE454D1C52C9D900C24048 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = libmalloc_static; productName = libmalloc; productReference = C0CE454C1C52C90500C24048 /* libmalloc.a */; productType = "com.apple.product-type.library.static"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3FFC1BE516A908F800027192 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; LastUpgradeCheck = 0900; TargetAttributes = { 08C28B3F1D501D2C000AE997 = { CreatedOnToolsVersion = 8.0; ProvisioningStyle = Automatic; }; 45039161198FFF73004EE2A3 = { CreatedOnToolsVersion = 6.0; }; 456E51C8197DF0D600A7E488 = { CreatedOnToolsVersion = 6.0; }; 925383D41BD03C0500F745DB = { CreatedOnToolsVersion = 7.1; }; B60A57932009307E006215CB = { CreatedOnToolsVersion = 9.3; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 3FFC1BE816A908F800027192 /* Build configuration list for PBXProject "libmalloc" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 3FFC1BE416A908F800027192; productRefGroup = 3FE91FE916A90AEC00D1238A /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 3FE9201116A9111000D1238A /* libmalloc */, 45039161198FFF73004EE2A3 /* libmalloc_test */, 3FE91FE716A90AEC00D1238A /* libsystem_malloc */, B6910F65202B630D00FF2EB0 /* libmalloc_mp */, B629CF2B202BB337007719B9 /* libmalloc_alt */, 3FE91FFD16A9109E00D1238A /* libmalloc_eOS */, C0CE452F1C52C90500C24048 /* libmalloc_static */, B6D2ED492007D76F007AF994 /* libmalloc_replay */, 456E51C8197DF0D600A7E488 /* libmalloc_stress_test */, 08C28B3F1D501D2C000AE997 /* radix-tree */, 925383D41BD03C0500F745DB /* darwintests */, B60A57932009307E006215CB /* executables */, ); }; /* End PBXProject section */ /* Begin PBXShellScriptBuildPhase section */ 3FC1927C16DD946500315C26 /* Install Man Pages */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( ); inputPaths = ( "$(SRCROOT)/xcodescripts/manpages.sh", ); name = "Install Man Pages"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 1; shellPath = "/bin/bash -e"; shellScript = ". \"${SCRIPT_INPUT_FILE_0}\""; }; 3FE9201D16A9143E00D1238A /* Sanitise Headers (rdar://problem/10241868) */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( ); inputPaths = ( "$(SRCROOT)/xcodescripts/sanitise_headers.sh", ); name = "Sanitise Headers (rdar://problem/10241868)"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = ". \"${SCRIPT_INPUT_FILE_0}\""; }; B629CF2A202BB226007719B9 /* Symlink normal variant */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Symlink normal variant"; outputPaths = ( "$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)_normal$(EXECUTABLE_SUFFIX)", ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/bin/bash -e -x"; shellScript = "ln -fs \"${EXECUTABLE_PREFIX}${PRODUCT_NAME}${EXECUTABLE_SUFFIX}\" \"${SCRIPT_OUTPUT_FILE_0}\""; showEnvVarsInLog = 0; }; B629CF3E202BB337007719B9 /* Symlink normal variant */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Symlink normal variant"; outputPaths = ( "$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)_normal$(EXECUTABLE_SUFFIX)", ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/bin/bash -e -x"; shellScript = "ln -fs \"${EXECUTABLE_PREFIX}${PRODUCT_NAME}${EXECUTABLE_SUFFIX}\" \"${SCRIPT_OUTPUT_FILE_0}\""; showEnvVarsInLog = 0; }; C92853A11C767F78001FEAF3 /* Install Codes File */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "$(SRCROOT)/src/trace.h", ); name = "Install Codes File"; outputPaths = ( "$(DSTROOT)/usr/local/share/misc/libmalloc.codes", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; shellScript = ". \"$PROJECT_DIR\"/xcodescripts/install-codes.sh"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 08C28B3C1D501D2C000AE997 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 084F5E851D502102006CD296 /* radix_tree_debug.c in Sources */, 08FEED021D501F6B00BE8A69 /* radix_tree_main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 3FE91FE416A90AEC00D1238A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 088C4D771D1AF049005C6B36 /* radix_tree.c in Sources */, 3FE91FED16A90B9200D1238A /* bitarray.c in Sources */, B66C71DA2034BFAE0047E265 /* malloc_common.c in Sources */, C95742A01BF681B00027269A /* purgeable_malloc.c in Sources */, C957429C1BF672F80027269A /* magazine_large.c in Sources */, 3FE91FF016A90B9200D1238A /* magazine_malloc.c in Sources */, C95742991BF670D00027269A /* magazine_small.c in Sources */, C99E320B1D6F7366005655A8 /* magazine_rack.c in Sources */, C95742AB1BF685CB0027269A /* legacy_malloc.c in Sources */, C932D2681D6B8D840063B19E /* vm.c in Sources */, 3FE91FF116A90B9200D1238A /* magmallocProvider.d in Sources */, 0D468DCF1C7BEF51006FACF5 /* magazine_lite.c in Sources */, B68B7FA51FCDD9A500BAD1AA /* nanov2_malloc.c in Sources */, B6D5C7F1202E26F80035E376 /* resolver.c in Sources */, B68B7F9F1FCDCBC600BAD1AA /* nano_malloc_common.c in Sources */, 3FE91FF216A90B9200D1238A /* malloc.c in Sources */, C95742A61BF6842F0027269A /* frozen_malloc.c in Sources */, 3FE91FF416A90B9200D1238A /* nano_malloc.c in Sources */, 3FE91FF616A90B9200D1238A /* stack_logging_disk.c in Sources */, C95742901BF419DF0027269A /* magazine_tiny.c in Sources */, B65FBE2C2087AA2F00E21F59 /* malloc_printf.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 3FE91FFE16A9109E00D1238A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B66C71DB2034BFD30047E265 /* malloc_common.c in Sources */, 3FE91FFF16A9109E00D1238A /* bitarray.c in Sources */, C95742A11BF681B00027269A /* purgeable_malloc.c in Sources */, C957429D1BF672F80027269A /* magazine_large.c in Sources */, 3FE9200116A9109E00D1238A /* magazine_malloc.c in Sources */, C957429A1BF670D00027269A /* magazine_small.c in Sources */, C95742AC1BF685CB0027269A /* legacy_malloc.c in Sources */, B68B7FA01FCDCBE700BAD1AA /* nano_malloc_common.c in Sources */, B68B7FA61FCDD9B200BAD1AA /* nanov2_malloc.c in Sources */, 3FE9200216A9109E00D1238A /* magmallocProvider.d in Sources */, 3FE9200316A9109E00D1238A /* malloc.c in Sources */, C95742A71BF6842F0027269A /* frozen_malloc.c in Sources */, 3FE9200416A9109E00D1238A /* nano_malloc.c in Sources */, 3FE9200616A9109E00D1238A /* stack_logging_disk.c in Sources */, C95742911BF419DF0027269A /* magazine_tiny.c in Sources */, B6D5C7F4202E26F90035E376 /* resolver.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 456E51C5197DF0D600A7E488 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 925383DA1BD03D5100F745DB /* stress_test.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B629CF2C202BB337007719B9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B629CF2D202BB337007719B9 /* radix_tree.c in Sources */, B629CF2E202BB337007719B9 /* bitarray.c in Sources */, B629CF2F202BB337007719B9 /* purgeable_malloc.c in Sources */, B6D5C7F3202E26F90035E376 /* resolver.c in Sources */, B629CF30202BB337007719B9 /* magazine_large.c in Sources */, B629CF31202BB337007719B9 /* magazine_malloc.c in Sources */, B629CF32202BB337007719B9 /* empty.s in Sources */, B629CF33202BB337007719B9 /* magazine_small.c in Sources */, B629CF34202BB337007719B9 /* legacy_malloc.c in Sources */, B629CF35202BB337007719B9 /* magmallocProvider.d in Sources */, B629CF36202BB337007719B9 /* malloc.c in Sources */, B629CF37202BB337007719B9 /* frozen_malloc.c in Sources */, B629CF38202BB337007719B9 /* nanov2_malloc.c in Sources */, B629CF39202BB337007719B9 /* nano_malloc.c in Sources */, B629CF3A202BB337007719B9 /* stack_logging_disk.c in Sources */, B629CF3B202BB337007719B9 /* magazine_tiny.c in Sources */, B629CF3C202BB337007719B9 /* nano_malloc_common.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B6910F66202B630D00FF2EB0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B6910F67202B630D00FF2EB0 /* radix_tree.c in Sources */, B6910F68202B630D00FF2EB0 /* bitarray.c in Sources */, B6910F69202B630D00FF2EB0 /* purgeable_malloc.c in Sources */, B6D5C7F2202E26F80035E376 /* resolver.c in Sources */, B6910F6A202B630D00FF2EB0 /* magazine_large.c in Sources */, B6910F6B202B630D00FF2EB0 /* magazine_malloc.c in Sources */, B6910F6C202B630D00FF2EB0 /* empty.s in Sources */, B6910F6D202B630D00FF2EB0 /* magazine_small.c in Sources */, B6910F6E202B630D00FF2EB0 /* legacy_malloc.c in Sources */, B6910F6F202B630D00FF2EB0 /* magmallocProvider.d in Sources */, B6910F70202B630D00FF2EB0 /* malloc.c in Sources */, B6910F71202B630D00FF2EB0 /* frozen_malloc.c in Sources */, B629CF28202BA149007719B9 /* nanov2_malloc.c in Sources */, B6910F73202B630D00FF2EB0 /* nano_malloc.c in Sources */, B6910F74202B630D00FF2EB0 /* stack_logging_disk.c in Sources */, B6910F75202B630D00FF2EB0 /* magazine_tiny.c in Sources */, B6910F76202B630D00FF2EB0 /* nano_malloc_common.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B6D2ED4A2007D76F007AF994 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B6D2ED572007D91A007AF994 /* malloc_replay.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; C0CE45301C52C90500C24048 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 088C4D841D1AF16F005C6B36 /* radix_tree.c in Sources */, C0CE45311C52C90500C24048 /* bitarray.c in Sources */, C0CE45321C52C90500C24048 /* purgeable_malloc.c in Sources */, B6D5C7F5202E26FA0035E376 /* resolver.c in Sources */, C0CE45331C52C90500C24048 /* magazine_large.c in Sources */, C0CE45341C52C90500C24048 /* magazine_malloc.c in Sources */, C9ABCA051CB6FC6800ECB399 /* empty.s in Sources */, C0CE45351C52C90500C24048 /* magazine_small.c in Sources */, C0CE45361C52C90500C24048 /* legacy_malloc.c in Sources */, C0CE45371C52C90500C24048 /* magmallocProvider.d in Sources */, C0CE45381C52C90500C24048 /* malloc.c in Sources */, C0CE45391C52C90500C24048 /* frozen_malloc.c in Sources */, B68B7FA71FCDD9B200BAD1AA /* nanov2_malloc.c in Sources */, B66C71DC2034BFD40047E265 /* malloc_common.c in Sources */, C0CE453A1C52C90500C24048 /* nano_malloc.c in Sources */, C0CE453C1C52C90500C24048 /* stack_logging_disk.c in Sources */, C0CE453D1C52C90500C24048 /* magazine_tiny.c in Sources */, B68B7FA11FCDCBE800BAD1AA /* nano_malloc_common.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 3FE9201616A9111400D1238A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3FE91FFD16A9109E00D1238A /* libmalloc_eOS */; targetProxy = 3FE9201516A9111400D1238A /* PBXContainerItemProxy */; }; 3FE9201816A9111600D1238A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3FE91FE716A90AEC00D1238A /* libsystem_malloc */; targetProxy = 3FE9201716A9111600D1238A /* PBXContainerItemProxy */; }; 45039168198FFFA6004EE2A3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 456E51C8197DF0D600A7E488 /* libmalloc_stress_test */; targetProxy = 45039167198FFFA6004EE2A3 /* PBXContainerItemProxy */; }; 925383D91BD03D0000F745DB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 925383D41BD03C0500F745DB /* darwintests */; targetProxy = 925383D81BD03D0000F745DB /* PBXContainerItemProxy */; }; B60A579820093093006215CB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B6D2ED492007D76F007AF994 /* libmalloc_replay */; targetProxy = B60A579720093093006215CB /* PBXContainerItemProxy */; }; B629CF44202BB389007719B9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B629CF2B202BB337007719B9 /* libmalloc_alt */; targetProxy = B629CF43202BB389007719B9 /* PBXContainerItemProxy */; }; B676F4AC202B66EF00933F6D /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B6910F65202B630D00FF2EB0 /* libmalloc_mp */; targetProxy = B676F4AB202B66EF00933F6D /* PBXContainerItemProxy */; }; C0CE45501C52CCBD00C24048 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C0CE452F1C52C90500C24048 /* libmalloc_static */; targetProxy = C0CE454F1C52CCBD00C24048 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 08C28B451D501D2C000AE997 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 08C28B461D501D2C000AE997 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 3FE91FEB16A90AEC00D1238A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = B629CF29202BA3C2007719B9 /* libmalloc_resolver.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; }; name = Debug; }; 3FE91FEC16A90AEC00D1238A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = B629CF29202BA3C2007719B9 /* libmalloc_resolver.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; }; name = Release; }; 3FE9200E16A9109E00D1238A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3FE91FE116A90A8D00D1238A /* libmalloc_eos.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; }; name = Debug; }; 3FE9200F16A9109E00D1238A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3FE91FE116A90A8D00D1238A /* libmalloc_eos.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; }; name = Release; }; 3FE9201316A9111000D1238A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 3FE9201416A9111000D1238A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 3FFC1BE916A908F800027192 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ONLY_ACTIVE_ARCH = YES; }; name = Debug; }; 3FFC1BEA16A908F800027192 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Release; }; 45039163198FFF73004EE2A3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 45039164198FFF73004EE2A3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 456E51CD197DF0D600A7E488 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_UNREACHABLE_CODE = YES; COPY_PHASE_STRIP = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; }; name = Debug; }; 456E51CE197DF0D600A7E488 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_UNREACHABLE_CODE = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; "SKIP_INSTALL[sdk=iphonesimulator*]" = YES; SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; }; name = Release; }; 925383D61BD03C0500F745DB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; }; name = Debug; }; 925383D71BD03C0500F745DB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; }; name = Release; }; B60A57942009307E006215CB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; B60A57952009307E006215CB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; B629CF40202BB337007719B9 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = B66AA658202A70B00019D607 /* libmalloc_resolved.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; PRODUCT_NAME = "$(PRODUCT_NAME)"; RESOLVED_VARIANT = alt; }; name = Debug; }; B629CF41202BB337007719B9 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = B66AA658202A70B00019D607 /* libmalloc_resolved.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; PRODUCT_NAME = "$(PRODUCT_NAME)"; RESOLVED_VARIANT = alt; }; name = Release; }; B6910F87202B630D00FF2EB0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = B66AA658202A70B00019D607 /* libmalloc_resolved.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; PRODUCT_NAME = "$(PRODUCT_NAME)"; RESOLVED_VARIANT = mp; }; name = Debug; }; B6910F88202B630D00FF2EB0 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = B66AA658202A70B00019D607 /* libmalloc_resolved.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; PRODUCT_NAME = "$(PRODUCT_NAME)"; RESOLVED_VARIANT = mp; }; name = Release; }; B6D2ED4F2007D76F007AF994 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_UNREACHABLE_CODE = YES; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/System/Library/PrivateFrameworks", "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", ); GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/src"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; VALID_ARCHS = "armv6 armv7 arm64 arm64_32 armv7k x86_64 x86_64h"; }; name = Debug; }; B6D2ED502007D76F007AF994 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_UNREACHABLE_CODE = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/System/Library/PrivateFrameworks", "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/src"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; "SKIP_INSTALL[sdk=iphonesimulator*]" = YES; SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; VALID_ARCHS = "armv6 armv7 arm64 arm64_32 armv7k x86_64 x86_64h"; }; name = Release; }; C0CE454A1C52C90500C24048 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = C0CE450E1C52B9E300C24048 /* libmalloc_static.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; PRODUCT_NAME = "$(PRODUCT_NAME)"; }; name = Debug; }; C0CE454B1C52C90500C24048 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = C0CE450E1C52B9E300C24048 /* libmalloc_static.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; PRODUCT_NAME = "$(PRODUCT_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 08C28B441D501D2C000AE997 /* Build configuration list for PBXNativeTarget "radix-tree" */ = { isa = XCConfigurationList; buildConfigurations = ( 08C28B451D501D2C000AE997 /* Debug */, 08C28B461D501D2C000AE997 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FE91FEA16A90AEC00D1238A /* Build configuration list for PBXNativeTarget "libsystem_malloc" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FE91FEB16A90AEC00D1238A /* Debug */, 3FE91FEC16A90AEC00D1238A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FE9200D16A9109E00D1238A /* Build configuration list for PBXNativeTarget "libmalloc_eOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FE9200E16A9109E00D1238A /* Debug */, 3FE9200F16A9109E00D1238A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FE9201216A9111000D1238A /* Build configuration list for PBXAggregateTarget "libmalloc" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FE9201316A9111000D1238A /* Debug */, 3FE9201416A9111000D1238A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FFC1BE816A908F800027192 /* Build configuration list for PBXProject "libmalloc" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FFC1BE916A908F800027192 /* Debug */, 3FFC1BEA16A908F800027192 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 45039162198FFF73004EE2A3 /* Build configuration list for PBXAggregateTarget "libmalloc_test" */ = { isa = XCConfigurationList; buildConfigurations = ( 45039163198FFF73004EE2A3 /* Debug */, 45039164198FFF73004EE2A3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 456E51CF197DF0D600A7E488 /* Build configuration list for PBXNativeTarget "libmalloc_stress_test" */ = { isa = XCConfigurationList; buildConfigurations = ( 456E51CD197DF0D600A7E488 /* Debug */, 456E51CE197DF0D600A7E488 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 925383D51BD03C0500F745DB /* Build configuration list for PBXLegacyTarget "darwintests" */ = { isa = XCConfigurationList; buildConfigurations = ( 925383D61BD03C0500F745DB /* Debug */, 925383D71BD03C0500F745DB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B60A57962009307E006215CB /* Build configuration list for PBXAggregateTarget "executables" */ = { isa = XCConfigurationList; buildConfigurations = ( B60A57942009307E006215CB /* Debug */, B60A57952009307E006215CB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B629CF3F202BB337007719B9 /* Build configuration list for PBXNativeTarget "libmalloc_alt" */ = { isa = XCConfigurationList; buildConfigurations = ( B629CF40202BB337007719B9 /* Debug */, B629CF41202BB337007719B9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B6910F86202B630D00FF2EB0 /* Build configuration list for PBXNativeTarget "libmalloc_mp" */ = { isa = XCConfigurationList; buildConfigurations = ( B6910F87202B630D00FF2EB0 /* Debug */, B6910F88202B630D00FF2EB0 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B6D2ED4E2007D76F007AF994 /* Build configuration list for PBXNativeTarget "libmalloc_replay" */ = { isa = XCConfigurationList; buildConfigurations = ( B6D2ED4F2007D76F007AF994 /* Debug */, B6D2ED502007D76F007AF994 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C0CE45491C52C90500C24048 /* Build configuration list for PBXNativeTarget "libmalloc_static" */ = { isa = XCConfigurationList; buildConfigurations = ( C0CE454A1C52C90500C24048 /* Debug */, C0CE454B1C52C90500C24048 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 3FFC1BE516A908F800027192 /* Project object */; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/man/malloc.3 ================================================ .\" Copyright (c) 2006 Apple Computer, Inc. All rights reserved. .\" .\" @APPLE_LICENSE_HEADER_START@ .\" .\" The contents of this file constitute Original Code as defined in and .\" are subject to the Apple Public Source License Version 1.1 (the .\" "License"). You may not use this file except in compliance with the .\" License. Please obtain a copy of the License at .\" http://www.apple.com/publicsource and read it before using this file. .\" .\" This Original Code and all software distributed under the License are .\" distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER .\" EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, .\" INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, .\" FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the .\" License for the specific language governing rights and limitations .\" under the License. .\" .\" @APPLE_LICENSE_HEADER_END@ .\" .Dd Aug 13, 2008 .Dt MALLOC 3 .Os .Sh NAME .Nm calloc , .Nm free , .Nm malloc , .Nm realloc , .Nm reallocf , .Nm valloc .Nd memory allocation .Sh SYNOPSIS .In stdlib.h .Ft void * .Fo calloc .Fa "size_t count" .Fa "size_t size" .Fc .Ft void .Fo free .Fa "void *ptr" .Fc .Ft void * .Fo malloc .Fa "size_t size" .Fc .Ft void * .Fo realloc .Fa "void *ptr" .Fa "size_t size" .Fc .Ft void * .Fo reallocf .Fa "void *ptr" .Fa "size_t size" .Fc .Ft void * .Fo valloc .Fa "size_t size" .Fc .Sh DESCRIPTION The .Fn malloc , .Fn calloc , .Fn valloc , .Fn realloc , and .Fn reallocf functions allocate memory. The allocated memory is aligned such that it can be used for any data type, including AltiVec- and SSE-related types. The .Fn free function frees allocations that were created via the preceding allocation functions. .Pp The .Fn malloc function allocates .Fa size bytes of memory and returns a pointer to the allocated memory. .Pp The .Fn calloc function contiguously allocates enough space for .Fa count objects that are .Fa size bytes of memory each and returns a pointer to the allocated memory. The allocated memory is filled with bytes of value zero. .Pp The .Fn valloc function allocates .Fa size bytes of memory and returns a pointer to the allocated memory. The allocated memory is aligned on a page boundary. .Pp The .Fn realloc function tries to change the size of the allocation pointed to by .Fa ptr to .Fa size , and returns .Fa ptr . If there is not enough room to enlarge the memory allocation pointed to by .Fa ptr , .Fn realloc creates a new allocation, copies as much of the old data pointed to by .Fa ptr as will fit to the new allocation, frees the old allocation, and returns a pointer to the allocated memory. If .Fa ptr is .Dv NULL , .Fn realloc is identical to a call to .Fn malloc for .Fa size bytes. If .Fa size is zero and .Fa ptr is not .Dv NULL , a new, minimum sized object is allocated and the original object is freed. When extending a region allocated with calloc(3), realloc(3) does not guarantee that the additional memory is also zero-filled. .Pp The .Fn reallocf function is identical to the .Fn realloc function, except that it will free the passed pointer when the requested memory cannot be allocated. This is a .Fx specific API designed to ease the problems with traditional coding styles for realloc causing memory leaks in libraries. .Pp The .Fn free function deallocates the memory allocation pointed to by .Fa ptr . If .Fa ptr is a NULL pointer, no operation is performed. .Sh RETURN VALUES If successful, .Fn calloc , .Fn malloc , .Fn realloc , .Fn reallocf , and .Fn valloc functions return a pointer to allocated memory. If there is an error, they return a .Dv NULL pointer and set .Va errno to .Er ENOMEM . .Pp For .Fn realloc , the input pointer is still valid if reallocation failed. For .Fn reallocf , the input pointer will have been freed if reallocation failed. .Pp The .Fn free function does not return a value. .Sh DEBUGGING ALLOCATION ERRORS A number of facilities are provided to aid in debugging allocation errors in applications. These facilities are primarily controlled via environment variables. The recognized environment variables and their meanings are documented below. .Sh ENVIRONMENT The following environment variables change the behavior of the allocation-related functions. .Bl -tag -width ".Ev MallocStackLoggingNoCompact" .It Ev MallocDebugReport If set, specifies where messages are written. Set to "stderr" to write messages to the standard error stream, "none" to discard all messages and "crash" to write messages to standard error only for a condition that is about to cause a crash. When not set, message are written to the standard error stream if it appears to be a terminal (that is, if isatty(STDERR_FILENO) returns a non-zero value) and are otherwise discarded. .It Ev MallocGuardEdges If set, add a guard page before and after each large block. .It Ev MallocDoNotProtectPrelude If set, do not add a guard page before large blocks, even if the .Ev MallocGuardEdges environment variable is set. .It Ev MallocDoNotProtectPostlude If set, do not add a guard page after large blocks, even if the .Ev MallocGuardEdges environment variable is set. .It Ev MallocStackLogging The default behavior if this is set is to record all allocation and deallocation events to an on-disk log, along with stacks, so that tools like .Xr leaks 1 and .Xr malloc_history 1 can be used. .Pp Set to "vm" to record only allocation of virtual memory regions allocated by system calls and mach traps, such as by .Xr mmap 1 . .Pp Set to "malloc" to record only allocations via .Xr malloc 3 and related interfaces, not virtual memory regions. .Pp Set to "lite" to record current allocations only, not history. These are recorded by in-memory data structures, instead of an on-disk log. .It Ev MallocStackLoggingNoCompact If set, record all stacks in a manner that is compatible with the .Nm malloc_history program. .It Ev MallocStackLoggingDirectory If set, records stack logs to the directory specified instead of saving them to the default location (/tmp). .It Ev MallocScribble If set, fill memory that has been allocated with 0xaa bytes. This increases the likelihood that a program making assumptions about the contents of freshly allocated memory will fail. Also if set, fill memory that has been deallocated with 0x55 bytes. This increases the likelihood that a program will fail due to accessing memory that is no longer allocated. Note that due to the way in which freed memory is managed internally, the 0x55 pattern may not appear in some parts of a deallocated memory block. .It Ev MallocCheckHeapStart If set, specifies the number of allocations .Fa to wait before begining periodic heap checks every .Fa as specified by .Ev MallocCheckHeapEach . If .Ev MallocCheckHeapStart is set but .Ev MallocCheckHeapEach is not specified, the default check repetition is 1000. .It Ev MallocCheckHeapEach If set, run a consistency check on the heap every .Fa operations. .Ev MallocCheckHeapEach is only meaningful if .Ev MallocCheckHeapStart is also set. .It Ev MallocCheckHeapSleep Sets the number of seconds to sleep (waiting for a debugger to attach) when .Ev MallocCheckHeapStart is set and a heap corruption is detected. The default is 100 seconds. Setting this to zero means not to sleep at all. Setting this to a negative number means to sleep (for the positive number of seconds) only the very first time a heap corruption is detected. .It Ev MallocCheckHeapAbort When .Ev MallocCheckHeapStart is set and this is set to a non-zero value, causes .Xr abort 3 to be called if a heap corruption is detected, instead of any sleeping. .It Ev MallocErrorAbort If set, causes .Xr abort 3 to be called if an error was encountered in .Xr malloc 3 or .Xr free 3 , such as a calling .Xr free 3 on a pointer previously freed. .It Ev MallocCorruptionAbort Similar to .Ev MallocErrorAbort but will not abort in out of memory conditions, making it more useful to catch only those errors which will cause memory corruption. MallocCorruptionAbort is always set on 64-bit processes. .It Ev MallocHelp If set, print a list of environment variables that are paid heed to by the allocation-related functions, along with short descriptions. The list should correspond to this documentation. .El .Sh DIAGNOSTIC MESSAGES .Sh SEE ALSO .Xr leaks 1 , .Xr malloc_history 1 , .Xr abort 3 , .Xr malloc_size 3 , .Xr malloc_zone_malloc 3 , .Xr posix_memalign 3 , .Xr libgmalloc 3 ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/man/malloc_size.3 ================================================ .\" Copyright (c) 2006 Apple Computer, Inc. All rights reserved. .\" .\" @APPLE_LICENSE_HEADER_START@ .\" .\" The contents of this file constitute Original Code as defined in and .\" are subject to the Apple Public Source License Version 1.1 (the .\" "License"). You may not use this file except in compliance with the .\" License. Please obtain a copy of the License at .\" http://www.apple.com/publicsource and read it before using this file. .\" .\" This Original Code and all software distributed under the License are .\" distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER .\" EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, .\" INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, .\" FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the .\" License for the specific language governing rights and limitations .\" under the License. .\" .\" @APPLE_LICENSE_HEADER_END@ .\" .Dd May 23, 2006 .Dt MALLOC_SIZE 3 .Os .Sh NAME .Nm malloc_good_size , .Nm malloc_size .Nd memory allocation information .Sh SYNOPSIS .In malloc/malloc.h .Ft size_t .Fo malloc_good_size .Fa "size_t size" .Fc .Ft size_t .Fo malloc_size .Fa "const void *ptr" .Fc .Sh DESCRIPTION The .Fn malloc_size function returns the size of the memory block that backs the allocation pointed to by .Fa ptr . The memory block size is always at least as large as the allocation it backs, and may be larger. .Pp The .Fn malloc_good_size function rounds .Fa size up to a value that the allocator implementation can allocate without adding any padding; it then returns that rounded-up value. .Sh SEE ALSO .Xr malloc 3 ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/man/malloc_zone_malloc.3 ================================================ .\" Copyright (c) 2008 Apple, Inc. All rights reserved. .\" .\" @APPLE_LICENSE_HEADER_START@ .\" .\" The contents of this file constitute Original Code as defined in and .\" are subject to the Apple Public Source License Version 1.1 (the .\" "License"). You may not use this file except in compliance with the .\" License. Please obtain a copy of the License at .\" http://www.apple.com/publicsource and read it before using this file. .\" .\" This Original Code and all software distributed under the License are .\" distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER .\" EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, .\" INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, .\" FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the .\" License for the specific language governing rights and limitations .\" under the License. .\" .\" @APPLE_LICENSE_HEADER_END@ .\" .Dd Aug 13, 2008 .Dt MALLOC_ZONE_MALLOC 3 .Os .Sh NAME .Nm malloc_create_zone , .Nm malloc_destroy_zone , .Nm malloc_default_zone , .Nm malloc_zone_from_ptr , .Nm malloc_zone_malloc , .Nm malloc_zone_calloc , .Nm malloc_zone_valloc , .Nm malloc_zone_realloc , .Nm malloc_zone_memalign , .Nm malloc_zone_free .Nd zone-based memory allocation .Sh SYNOPSIS .In malloc/malloc.h .Ft malloc_zone_t * .Fo malloc_create_zone .Fa "vm_size_t start_size" .Fa "unsigned flags" .Fc .Ft void .Fo malloc_destroy_zone .Fa "malloc_zone_t *zone" .Fc .Ft malloc_zone_t * .Fo malloc_default_zone .Fa void .Fc .Ft malloc_zone_t * .Fo malloc_zone_from_ptr .Fa "const void *ptr" .Fc .Ft void * .Fo malloc_zone_malloc .Fa "malloc_zone_t *zone" .Fa "size_t size" .Fc .Ft void * .Fo malloc_zone_calloc .Fa "malloc_zone_t *zone" .Fa "size_t num_items" .Fa "size_t size" .Fc .Ft void * .Fo malloc_zone_valloc .Fa "malloc_zone_t *zone" .Fa "size_t size" .Fc .Ft void * .Fo malloc_zone_realloc .Fa "malloc_zone_t *zone" .Fa "void *ptr" .Fa "size_t size" .Fc .Ft void * .Fo malloc_zone_memalign .Fa "malloc_zone_t *zone" .Fa "size_t alignment" .Fa "size_t size" .Fc .Ft void .Fo malloc_zone_free .Fa "malloc_zone_t *zone" .Fa "void *ptr" .Fc .Sh DESCRIPTION The .Fn malloc_create_zone function creates a malloc zone, advising an initial allocation of .Fa start_size bytes, and specifying .Fa flags The returned malloc zone can be used to provide custom allocation and deallocation behavior, and to retrieve additional information about the allocations in that zone. At present there are no client settable flag values recognized by malloc_create_zone(), the flags argument should always be passed as zero. .Pp The .Fn malloc_destroy_zone function deallocates all memory associated with objects in .Fa zone as well as .Fa zone itself. .Pp The .Fn malloc_default_zone function returns the default system malloc zone, used by .Xr malloc 3 , and .Xr free 3 . .Pp The .Fn malloc_zone_from_ptr function returns a pointer to the malloc zone which contains .Fa ptr or NULL, if the pointer does not point to an allocated object in any current malloc zone. .Pp The .Fn malloc_zone_malloc , .Fn malloc_zone_calloc , .Fn malloc_zone_valloc , .Fn malloc_zone_realloc , .Fn malloc_zone_memalign , and .Fn malloc_zone_free perform the same task on .Fa zone as their non-prefixed variants, .Xr malloc 3 , .Xr calloc 3 , .Xr valloc 3 , .Xr realloc 3 , .Xr posix_memalign 3 , and .Xr free 3 perform on the default system malloc zone. .Sh RETURN VALUES The .Fn malloc_create_zone , .Fn malloc_default_zone , and .Fn malloc_zone_from_ptr functions return a pointer to a malloc_zone_t structure, or NULL if there was an error. .Pp The .Fn malloc_zone_malloc , .Fn malloc_zone_calloc , .Fn malloc_zone_valloc , .Fn malloc_zone_realloc , and .Fn malloc_zone_memalign functions return a pointer to allocated memory. If there is an error, they return a NULL pointer. They are not required to set .Va errno . .Sh SEE ALSO .Xr malloc 3 , .Xr posix_memalign 3 ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/man/manpages.lst ================================================ # manpage tables # [ ...] # man3 malloc.3 malloc.3 calloc.3 free.3 realloc.3 reallocf.3 valloc.3 malloc_size.3 malloc_size.3 malloc_good_size.3 malloc_zone_malloc.3 malloc_zone_malloc.3 malloc_create_zone.3 malloc_destroy_zone.3 malloc_default_zone.3 malloc_zone_from_ptr.3 malloc_zone_calloc.3 malloc_zone_valloc.3 malloc_zone_realloc.3 malloc_zone_memalign.3 malloc_zone_free.3 ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/private/malloc_private.h ================================================ /* * Copyright (c) 1999-2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _MALLOC_PRIVATE_H_ #define _MALLOC_PRIVATE_H_ /* Here be dragons (SPIs) */ #include #include #include #include #include /********* Callbacks ************/ API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0)) void malloc_enter_process_memory_limit_warn_mode(void); /* A callback invoked once the process receives a warning for approaching * memory limit. */ __OSX_AVAILABLE(10.12) __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) void malloc_memory_event_handler(unsigned long); /* A function invoked when malloc needs to handle any flavor of * memory pressure notification or process memory limit notification. */ API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0)) void * reallocarray(void * in_ptr, size_t nmemb, size_t size) __DARWIN_EXTSN(reallocarray) __result_use_check; API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0)) void * reallocarrayf(void * in_ptr, size_t nmemb, size_t size) __DARWIN_EXTSN(reallocarrayf) __result_use_check; /* * Checks whether an address might belong to any registered zone. False positives * are allowed (e.g. the memory was freed, or it's in a part of the address * space used by malloc that has not yet been allocated.) False negatives are * not allowed. */ API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) boolean_t malloc_claimed_address(void *ptr) __result_use_check; /* * Checks whether an address might belong to a given zone. False positives are * allowed (e.g. the memory was freed, or it's in a part of the address space * used by malloc that has not yet been allocated.) False negatives are not * allowed. */ API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) boolean_t malloc_zone_claimed_address(malloc_zone_t *zone, void *ptr) __result_use_check; /** * Returns whether the nano allocator is engaged. The return value is 0 if Nano * is not engaged and the allocator version otherwise. */ API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) int malloc_engaged_nano(void) __result_use_check; #endif /* _MALLOC_PRIVATE_H_ */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/private/stack_logging.h ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #import #import #import #import #import #define STACK_LOGGING_MAX_STACK_SIZE 512 #define stack_logging_type_free 0 #define stack_logging_type_generic 1 /* anything that is not allocation/deallocation */ #define stack_logging_type_alloc 2 /* malloc, realloc, etc... */ #define stack_logging_type_dealloc 4 /* free, realloc, etc... */ #define stack_logging_type_vm_allocate 16 /* vm_allocate or mmap */ #define stack_logging_type_vm_deallocate 32 /* vm_deallocate or munmap */ #define stack_logging_type_mapped_file_or_shared_mem 128 // The valid flags include those from VM_FLAGS_ALIAS_MASK, which give the user_tag of allocated VM regions. #define stack_logging_valid_type_flags ( \ stack_logging_type_generic | \ stack_logging_type_alloc | \ stack_logging_type_dealloc | \ stack_logging_type_vm_allocate | \ stack_logging_type_vm_deallocate | \ stack_logging_type_mapped_file_or_shared_mem | \ VM_FLAGS_ALIAS_MASK); // Following flags are absorbed by stack_logging_log_stack() #define stack_logging_flag_zone 8 /* NSZoneMalloc, etc... */ #define stack_logging_flag_cleared 64 /* for NewEmptyHandle */ #define STACK_LOGGING_VM_USER_TAG(flags) (((flags) & VM_FLAGS_ALIAS_MASK) >> 24) /* Macro used to disguise addresses so that leak finding can work */ #define STACK_LOGGING_DISGUISE(address) ((address) ^ 0x00005555) /* nicely idempotent */ typedef enum { stack_logging_mode_none = 0, stack_logging_mode_all, stack_logging_mode_malloc, stack_logging_mode_vm, stack_logging_mode_lite, stack_logging_mode_vmlite } stack_logging_mode_type; extern boolean_t turn_on_stack_logging(stack_logging_mode_type mode); extern void turn_off_stack_logging(); /* constants for enabling/disabling malloc stack logging via the memorystatus_vm_pressure_send sysctl */ #define MEMORYSTATUS_ENABLE_MSL_MALLOC 0x10000000 #define MEMORYSTATUS_ENABLE_MSL_VM 0x20000000 #define MEMORYSTATUS_ENABLE_MSL_LITE 0x40000000 #define MEMORYSTATUS_DISABLE_MSL 0x80000000 #define MEMORYSTATUS_ENABLE_MSL_LITE_FULL (MEMORYSTATUS_ENABLE_MSL_LITE | MEMORYSTATUS_ENABLE_MSL_VM | MEMORYSTATUS_ENABLE_MSL_MALLOC) #define MEMORYSTATUS_ENABLE_MSL_LITE_VM (MEMORYSTATUS_ENABLE_MSL_LITE | MEMORYSTATUS_ENABLE_MSL_VM) extern void __disk_stack_logging_log_stack(uint32_t type_flags, uintptr_t zone_ptr, uintptr_t size, uintptr_t ptr_arg, uintptr_t return_val, uint32_t num_hot_to_skip); /* Fits as the malloc_logger; logs malloc/free/realloc events and can log custom events if called directly */ /* 64-bit-aware stack log access. As new SPI, these routines are prefixed with double-underscore to avoid conflict with Libsystem clients. */ typedef struct mach_stack_logging_record { uint32_t type_flags; uint64_t stack_identifier; uint64_t argument; mach_vm_address_t address; } mach_stack_logging_record_t; extern kern_return_t __mach_stack_logging_start_reading(task_t task, vm_address_t shared_memory_address, boolean_t *uses_lite_mode); extern kern_return_t __mach_stack_logging_stop_reading(task_t task); /* Clients *should* call these start/stop functions to properly initialize stack logging data * structures and fully clean them up when they're done looking at a process. If the client does *not* * call these then currently it should still work but some data structures will still remain after * reading the stack logs (e.g., an extra shared memory segment, an open stack log file, etc). * NULL can be passed for uses_lite_mode if the client doesn’t need them. * * It is recommended that the client suspend the task before actually reading the stacks, and resume the task when done, * if the task uses lite mode. */ extern kern_return_t __mach_stack_logging_set_file_path(task_t task, char* file_path); extern kern_return_t __mach_stack_logging_get_frames(task_t task, mach_vm_address_t address, mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count); /* Gets the last allocation record (malloc, realloc, or free) about address */ extern kern_return_t __mach_stack_logging_enumerate_records(task_t task, mach_vm_address_t address, void enumerator(mach_stack_logging_record_t, void *), void *context); /* Applies enumerator to all records involving address sending context as enumerator's second parameter; if !address, applies enumerator to all records */ extern kern_return_t __mach_stack_logging_frames_for_uniqued_stack(task_t task, uint64_t stack_identifier, mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count) API_DEPRECATED("use __mach_stack_logging_get_frames_for_stackid instead", macos(10.9, 10.13), ios(7.0, 11.0), watchos(1.0, 4.0), tvos(9.0, 11.0)); /* Given a uniqued_stack fills stack_frames_buffer. */ extern kern_return_t __mach_stack_logging_get_frames_for_stackid(task_t task, uint64_t stack_identifier, mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count, bool *last_frame_is_threadid) API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)); /* Given a uniqued_stack fills stack_frames_buffer. */ extern uint64_t __mach_stack_logging_stackid_for_vm_region(task_t task, mach_vm_address_t address); /* given the address of a vm region, lookup it's stackid */ struct backtrace_uniquing_table; extern kern_return_t __mach_stack_logging_uniquing_table_read_stack(struct backtrace_uniquing_table *uniquing_table, uint64_t stackid, mach_vm_address_t *out_frames_buffer, uint32_t *out_frames_count, uint32_t max_frames); extern struct backtrace_uniquing_table * __mach_stack_logging_copy_uniquing_table(task_t task); /* returns a retained pointer to copy of the task's uniquing table */ extern void __mach_stack_logging_uniquing_table_release(struct backtrace_uniquing_table *); extern void __mach_stack_logging_uniquing_table_retain(struct backtrace_uniquing_table *); extern size_t __mach_stack_logging_uniquing_table_sizeof(struct backtrace_uniquing_table *); /* returns the serialized size of a uniquing talbe in bytes */ extern void * __mach_stack_logging_uniquing_table_serialize(struct backtrace_uniquing_table *table, mach_vm_size_t *size); /* Writes out a serialized representation of the table. Free it with mach_vm_deallocate. */ extern struct backtrace_uniquing_table * __mach_stack_logging_uniquing_table_copy_from_serialized(void *buffer, size_t size); /* creates a malloc uniquing table from a serialized representation */ extern void thread_stack_pcs(vm_address_t *buffer, unsigned max, unsigned *num); /* Convenience to fill buffer with the PCs of the frames, starting with the hot frames; num: returned number of frames */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/resolver/resolver.c ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_APACHE_LICENSE_HEADER_START@ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @APPLE_APACHE_LICENSE_HEADER_END@ */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/resolver/resolver.h ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_APACHE_LICENSE_HEADER_START@ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @APPLE_APACHE_LICENSE_HEADER_END@ */ #ifndef __MALLOC_RESOLVER_H__ #define __MALLOC_RESOLVER_H__ #include "resolver_internal.h" #endif // __MALLOC_RESOLVER_H__ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/resolver/resolver_internal.h ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_APACHE_LICENSE_HEADER_START@ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @APPLE_APACHE_LICENSE_HEADER_END@ */ #ifndef __MALLOC_RESOLVER_INTERNAL_H__ #define __MALLOC_RESOLVER_INTERNAL_H__ #define OS_RESOLVED_VARIANT_ADDR(s) (void *)(&s) #endif // __MALLOC_RESOLVER_INTERNAL_H__ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/base.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __BASE_H #define __BASE_H #ifndef __has_extension #define __has_extension(x) 0 #endif #if __has_extension(c_static_assert) #define MALLOC_STATIC_ASSERT(x, y) _Static_assert((x), y) #else #define MALLOC_STATIC_ASSERT(x, y) #endif #define MALLOC_ASSERT(e) ({ \ if (__builtin_expect(!(e), 0)) { \ __asm__ __volatile__ (""); \ __builtin_trap(); \ } \ }) #define MALLOC_FATAL_ERROR(cause, message) ({ \ _os_set_crash_log_cause_and_message((cause), "FATAL ERROR - " message); \ __asm__ __volatile__ (""); \ __builtin_trap(); \ }) #define MALLOC_REPORT_FATAL_ERROR(cause, message) ({ \ malloc_report(ASL_LEVEL_ERR, "*** FATAL ERROR - " message ".\n"); \ MALLOC_FATAL_ERROR((cause), message); \ }) #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__arm64__) # define __APPLE_API_PRIVATE # include # if defined(__i386__) || defined(__x86_64__) # define _COMM_PAGE_VERSION_REQD 9 # else # define _COMM_PAGE_VERSION_REQD 3 # endif # undef __APPLE_API_PRIVATE #else # include #endif #if defined(__i386__) || defined(__x86_64__) // nano vs. magazine have different definitions // for this cache-line size. # define MALLOC_CACHE_LINE 128 # define MALLOC_NANO_CACHE_LINE 64 #elif defined(__arm__) || defined(__arm64__) # define MALLOC_CACHE_LINE 64 # define MALLOC_NANO_CACHE_LINE 64 #else # define MALLOC_CACHE_LINE 32 # define MALLOC_NANO_CACHE_LINE 32 #endif #define MALLOC_CACHE_ALIGN __attribute__ ((aligned (MALLOC_CACHE_LINE) )) #define MALLOC_NANO_CACHE_ALIGN __attribute__ ((aligned (MALLOC_NANO_CACHE_LINE) )) #define MALLOC_EXPORT extern __attribute__((visibility("default"))) #define MALLOC_NOEXPORT __attribute__((visibility("hidden"))) #define MALLOC_NOINLINE __attribute__((noinline)) #define MALLOC_INLINE __inline__ #define MALLOC_ALWAYS_INLINE __attribute__((always_inline)) #define MALLOC_PACKED __attribute__((packed)) #define MALLOC_USED __attribute__((used)) #define MALLOC_UNUSED __attribute__((unused)) #define CHECK_MAGAZINE_PTR_LOCKED(szone, mag_ptr, fun) {} #define SCRIBBLE_BYTE 0xaa /* allocated scribble */ #define SCRABBLE_BYTE 0x55 /* free()'d scribble */ #define SCRUBBLE_BYTE 0xdd /* madvise(..., MADV_FREE) scriblle */ #define NDEBUG 1 #define trunc_page_quanta(x) trunc_page((x)) #define round_page_quanta(x) round_page((x)) #define vm_page_quanta_size (vm_page_size) #define vm_page_quanta_shift (vm_page_shift) // add a guard page before and after each VM region to help debug #define MALLOC_ADD_GUARD_PAGES (1 << 0) // do not protect prelude page #define MALLOC_DONT_PROTECT_PRELUDE (1 << 1) // do not protect postlude page #define MALLOC_DONT_PROTECT_POSTLUDE (1 << 2) // write 0x55 onto free blocks #define MALLOC_DO_SCRIBBLE (1 << 3) // call abort() on any malloc error, such as double free or out of memory. #define MALLOC_ABORT_ON_ERROR (1 << 4) // allocate objects such that they may be used with VM purgability APIs #define MALLOC_PURGEABLE (1 << 5) // call abort() on malloc errors, but not on out of memory. #define MALLOC_ABORT_ON_CORRUPTION (1 << 6) // expanded small-zone free list size (256 slots) #define MALLOC_EXTENDED_SMALL_SLOTS (1 << 7) /* * msize - a type to refer to the number of quanta of a tiny or small * allocation. A tiny block with an msize of 3 would be 3 << SHIFT_TINY_QUANTUM * bytes in size. */ typedef unsigned short msize_t; typedef unsigned int grain_t; // N.B. wide enough to index all free slots typedef struct large_entry_s large_entry_t; typedef struct szone_s szone_t; typedef struct rack_s rack_t; typedef struct magazine_s magazine_t; typedef int mag_index_t; typedef void *region_t; #endif // __BASE_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/bitarray.c ================================================ /* * Copyright (c) 1999, 2000, 2003, 2005, 2008, 2012 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ // // bitarray.c // bitarray // // Created by Bertrand Serlet on 9/26/10. // Copyright (c) 2010 Apple. All rights reserved. // #include "internal.h" /******************************** Utilities ***************************/ #define STATIC_INLINE static __inline STATIC_INLINE unsigned __ffsll(uint64_t xx) { #if defined(__LP64__) return __builtin_ffsl(xx); #else return __builtin_ffsll(xx); #endif } #define BIT_SET(old, bit) ((old) | (1ULL << (bit))) #define BIT_GET(old, bit) ((old) & (1ULL << (bit))) #define BIT_ZAP(old, bit) ((old) & ~(1ULL << (bit))) // several variants below of bit setting or zapping to generate minimal code // All these do 1 memory read and (maybe) 1 memory write STATIC_INLINE bool word_get_bit_simple(uint64_t *word, unsigned bit) { uint64_t old = *word; return BIT_GET(old, bit) != 0; } STATIC_INLINE void word_set_bit_simple(uint64_t *word, unsigned bit) { uint64_t old = *word; *word = BIT_SET(old, bit); } STATIC_INLINE bool word_set_bit_changed(uint64_t *word, unsigned bit) { // returns 1 iff word has changed uint64_t old = *word; uint64_t new = BIT_SET(old, bit); if (old == new) { return 0; } *word = new; return 1; } STATIC_INLINE bool word_set_bit_changed_go_down(uint64_t *word, unsigned bit, bool *was_non_zero) { // returns 1 iff word changed // sets was_non_zero (when something changed) uint64_t old = *word; uint64_t new = BIT_SET(old, bit); if (old == new) { return 0; } *word = new; *was_non_zero = old != 0; return 1; } STATIC_INLINE bool word_set_bit_go_down(uint64_t *word, unsigned bit) { // returns 1 iff level below should be set too uint64_t old = *word; uint64_t new = BIT_SET(old, bit); if (old == new) { return 0; } *word = new; return !old; } STATIC_INLINE void word_zap_bit_simple(uint64_t *word, unsigned bit) { uint64_t old = *word; *word = BIT_ZAP(old, bit); } STATIC_INLINE bool word_zap_bit_changed(uint64_t *word, unsigned bit) { // returns 1 iff word changed uint64_t old = *word; uint64_t new = BIT_ZAP(old, bit); if (old == new) { return 0; } *word = new; return 1; } STATIC_INLINE bool word_zap_bit_changed_go_down(uint64_t *word, unsigned bit, bool *is_now_zero) { // returns 1 iff word changed // sets is_now_zero (when something changed) uint64_t old = *word; uint64_t new = BIT_ZAP(old, bit); if (old == new) { return 0; } *word = new; *is_now_zero = !new; return 1; } STATIC_INLINE bool word_zap_bit_go_down(uint64_t *word, unsigned bit) { // returns 1 iff level below might require a bit-zeroing uint64_t old = *word; uint64_t new = BIT_ZAP(old, bit); if (old == new) { return 0; } *word = new; return !new; } /******************************** Helpers ***************************/ #define NB 9 // number of bits we process at once // must be at least 6 (64-bit) and 9 seems the best on x86 #define MASKNB ((1 << NB) - 1) // to just keep these bits #define NUM_64b (1 << (NB - 6)) // number of 64-bit words we process at once // number of uint64_t of summaries #define LEVEL0 (NUM_64b) #define LEVEL1 (LEVEL0 + (1 << NB) * NUM_64b) #define LEVEL2 (LEVEL1 + (1 << (NB + NB)) * NUM_64b) #define LEVEL3 (LEVEL2 + (1 << (NB + NB + NB)) * NUM_64b) #define MAX_LEVEL 5 static const unsigned levels_num_words[] = { LEVEL0, LEVEL1, LEVEL2, LEVEL3}; // this encodes the number of words reserved for the bitmap summaries at various levels STATIC_INLINE bool GET_SIMPLE(uint64_t *word, unsigned bit) { return word_get_bit_simple(word + (bit >> 6), bit & 63); } STATIC_INLINE void SET_SIMPLE(uint64_t *word, unsigned bit) { word_set_bit_simple(word + (bit >> 6), bit & 63); } STATIC_INLINE bool SET_CHANGED(uint64_t *word, unsigned bit) { // returns 1 iff word changed return word_set_bit_changed(word + (bit >> 6), bit & 63); } STATIC_INLINE bool SET_CHANGED_GO_DOWN(uint64_t *word, unsigned bit, bool *was_non_zero) { // returns 1 iff word changed // sets was_non_zero (when something changed) return word_set_bit_changed_go_down(word + (bit >> 6), bit & 63, was_non_zero); } STATIC_INLINE bool SET_GO_DOWN(uint64_t *word, unsigned bit) { // returns 1 iff level below should be set too return word_set_bit_go_down(word + (bit >> 6), bit & 63); } STATIC_INLINE void ZAP_SIMPLE(uint64_t *word, unsigned bit) { return word_zap_bit_simple(word + (bit >> 6), bit & 63); } STATIC_INLINE bool ZAP_CHANGED(uint64_t *word, unsigned bit) { // returns 1 iff word changed return word_zap_bit_changed(word + (bit >> 6), bit & 63); } STATIC_INLINE bool all_zeros(uint64_t *words) { for (unsigned w = 0; w < NUM_64b; w++) { if (words[w]) { return 0; } } return 1; } STATIC_INLINE bool ZAP_CHANGED_GO_DOWN(uint64_t *word, unsigned bit, bool *is_now_zero) { // returns 1 iff word changed // sets is_now_zero (when something changed) bool changed = word_zap_bit_changed_go_down(word + (bit >> 6), bit & 63, is_now_zero); if (changed && (NUM_64b != 1)) { // One component went entirely zero, now examine all components in the level if (!all_zeros(word)) { *is_now_zero = 0; } } return changed; } STATIC_INLINE bool ZAP_GO_DOWN(uint64_t *word, unsigned bit) { // returns 1 iff level below should be changed too bool changed = word_zap_bit_go_down(word + (bit >> 6), bit & 63); if (changed && (NUM_64b != 1)) { // One component went entirely zero, now examine all components in the level if (!all_zeros(word)) { return 0; } } return changed; } STATIC_INLINE unsigned FFS(uint64_t *word) { // does NUM_64b memory reads, at most #if NB == 6 return __ffsll(*word); #else for (unsigned w = 0; w < NUM_64b; w++) { unsigned f = __ffsll(word[w]); if (f) { return f + (w << 6); } } return 0; #endif } /******************************** Entry Points ***************************/ size_t bitarray_size(unsigned log_size) { assert(log_size <= MAX_LEVEL * NB); unsigned num = NUM_64b; if (log_size > NB) { unsigned level = (log_size - NB - 1) / NB; num = levels_num_words[level] + (1 << (log_size - 6)); } return num * sizeof(uint64_t); } bitarray_t bitarray_create(unsigned log_size) { return calloc(1, bitarray_size(log_size)); } bool bitarray_get(bitarray_t bits, unsigned log_size, index_t index) { assert(log_size <= MAX_LEVEL * NB); assert(index < (1 << log_size)); if (log_size <= NB) { return GET_SIMPLE(bits, index); } unsigned level = (log_size - NB - 1) / NB; unsigned bit; bit = index & MASKNB; index >>= NB; return GET_SIMPLE(bits + levels_num_words[level] + index * NUM_64b, bit); } bool bitarray_set(bitarray_t bits, unsigned log_size, index_t index) { // returns whether changed assert(log_size <= MAX_LEVEL * NB); assert(index < (1 << log_size)); if (log_size <= NB) { return SET_CHANGED(bits, index); } unsigned level = (log_size - NB - 1) / NB; bool was_non_zero; unsigned bit; bit = index & MASKNB; index >>= NB; // printf("SET_CHANGED_GO_DOWN(bits + %d, %d,…)\n", levels_num_words[level] + index, bit); if (!SET_CHANGED_GO_DOWN(bits + levels_num_words[level] + index * NUM_64b, bit, &was_non_zero)) { return 0; } if (was_non_zero) { return 1; } switch (level) { case 3: bit = index & MASKNB; index >>= NB; if (!SET_GO_DOWN(bits + LEVEL2 + index * NUM_64b, bit)) { return 1; } /* no break */ case 2: bit = index & MASKNB; index >>= NB; if (!SET_GO_DOWN(bits + LEVEL1 + index * NUM_64b, bit)) { return 1; } /* no break */ case 1: bit = index & MASKNB; index >>= NB; if (!SET_GO_DOWN(bits + LEVEL0 + index * NUM_64b, bit)) { return 1; } /* no break */ case 0: SET_SIMPLE(bits, index & MASKNB); return 1; default: MALLOC_FATAL_ERROR(level, "invalid bitarray level"); } } bool bitarray_zap(bitarray_t bits, unsigned log_size, index_t index) { assert(log_size <= MAX_LEVEL * NB); assert(index < (1 << log_size)); if (log_size <= NB) { return ZAP_CHANGED(bits, index); } unsigned level = (log_size - NB - 1) / NB; bool is_now_zero; unsigned bit; bit = index & MASKNB; index >>= NB; if (!ZAP_CHANGED_GO_DOWN(bits + levels_num_words[level] + index * NUM_64b, bit, &is_now_zero)) { return 0; } if (!is_now_zero) { return 1; } switch (level) { case 3: bit = index & MASKNB; index >>= NB; if (!ZAP_GO_DOWN(bits + LEVEL2 + index * NUM_64b, bit)) { return 1; } /* no break */ case 2: bit = index & MASKNB; index >>= NB; if (!ZAP_GO_DOWN(bits + LEVEL1 + index * NUM_64b, bit)) { return 1; } /* no break */ case 1: bit = index & MASKNB; index >>= NB; if (!ZAP_GO_DOWN(bits + LEVEL0 + index * NUM_64b, bit)) { return 1; } /* no break */ case 0: ZAP_SIMPLE(bits, index & MASKNB); return 1; default: MALLOC_FATAL_ERROR(level, "invalid bitarray level"); } } // Note in the following macro that "words" and "base" are variables being written #define ADJUST_OFFSET_FOR_FFS(words, base, current_level) \ { \ words += (1 << (NB * current_level)) * NUM_64b; \ base = (base << NB) + FFS(words + base * NUM_64b) - 1; \ } // Note in the following macro that "words" and "base" are variables being written #define ADJUST_OFFSET_FOR_FFS_ACROSS_SUMMARIES(words, base, level) \ { \ switch (level) { \ case 4: \ ADJUST_OFFSET_FOR_FFS(words, base, 0); \ ADJUST_OFFSET_FOR_FFS(words, base, 1); \ ADJUST_OFFSET_FOR_FFS(words, base, 2); \ break; \ case 3: \ ADJUST_OFFSET_FOR_FFS(words, base, 0); \ ADJUST_OFFSET_FOR_FFS(words, base, 1); \ break; \ case 2: \ ADJUST_OFFSET_FOR_FFS(words, base, 0); \ break; \ case 1: \ break; \ default: \ MALLOC_FATAL_ERROR(level, "invalid bitarray level"); \ } \ } // Note in the following macro that "ix" and "bit" are variables being written #define ZAP_SUMMARIES(bits, ix, level) \ { \ unsigned bit; \ switch (level) { \ case 3: \ bit = ix & MASKNB; \ ix >>= NB; \ if (!ZAP_GO_DOWN(bits + LEVEL2 + ix * NUM_64b, bit)) { \ break; \ } \ case 2: \ bit = ix & MASKNB; \ ix >>= NB; \ if (!ZAP_GO_DOWN(bits + LEVEL1 + ix * NUM_64b, bit)) { \ break; \ } \ case 1: \ bit = ix & MASKNB; \ ix >>= NB; \ if (!ZAP_GO_DOWN(bits + LEVEL0 + ix * NUM_64b, bit)) { \ break; \ } \ case 0: \ ZAP_SIMPLE(bits, ix &MASKNB); \ break; \ default: \ MALLOC_FATAL_ERROR(level, "invalid bitarray level"); \ } \ } index_t bitarray_first_set(const bitarray_t bits, unsigned log_size) { // return 0 if none set assert(log_size <= MAX_LEVEL * NB); uint64_t *words = bits; unsigned bit = FFS(words); if (log_size <= NB) { return bit; } if (!bit) { return 0; } unsigned level = (log_size - 1) / NB; index_t base = bit - 1; // offset, in number of uin64_t words ADJUST_OFFSET_FOR_FFS_ACROSS_SUMMARIES(words, base, level); words += (1 << (NB * (level - 1))) * NUM_64b; base = (base << NB) + FFS(words + base * NUM_64b) - 1; return base + 1; //+1 because bit N is encoded as N+1 } bool bitarray_zap_first_set(bitarray_t bits, unsigned log_size, index_t *index) { assert(log_size <= MAX_LEVEL * NB); uint64_t *words = bits; index_t ix = FFS(words); if (!ix) { return 0; } unsigned level = (log_size - 1) / NB; if (!level) { ix--; *index = ix; ZAP_SIMPLE(bits, ix); return 1; } index_t base = ix - 1; // offset, in number of uin64_t words ADJUST_OFFSET_FOR_FFS_ACROSS_SUMMARIES(words, base, level); words += (1 << (NB * (level - 1))) * NUM_64b; base = (base << NB) + FFS(words + base * NUM_64b) - 1; ix = base; *index = ix; assert(ix < (1 << log_size)); level--; bool is_now_zero; unsigned bit; bit = ix & MASKNB; ix >>= NB; if (!ZAP_CHANGED_GO_DOWN(bits + levels_num_words[level] + ix * NUM_64b, bit, &is_now_zero)) { return 1; } if (!is_now_zero) { return 1; } ZAP_SUMMARIES(bits, ix, level); return 1; } static unsigned FFS_and_zap_word(uint64_t *words, unsigned max, index_t *indices, index_t to_be_added) { // returns the number of bits zapped unsigned zapped = 0; for (unsigned w = 0; w < NUM_64b; w++) { uint64_t word = words[w]; if (!word) { continue; } while (1) { unsigned f = __ffsll(word); assert(f); f--; // printf("%d ", f); indices[zapped++] = f + (w << 6) + to_be_added; word = BIT_ZAP(word, f); if (!word) { break; } if (zapped >= max) { break; } } words[w] = word; // printf("word=%lld \n", word); if (zapped >= max) { break; } } return zapped; } unsigned bitarray_zap_first_set_multiple(bitarray_t bits, unsigned log_size, unsigned max, index_t *indices) { assert(log_size <= MAX_LEVEL * NB); if (log_size <= NB) { return FFS_and_zap_word(bits, max, indices, 0); } unsigned zapped = 0; unsigned level = (log_size - 1) / NB; while (zapped < max) { /* * the lines in loop could be written just as: * if (! bitarray_zap_first_set(bits, log_size, indices + zapped)) break; * zapped++; * but the code is faster because it wont go up and down in the summaries */ uint64_t *words = bits; index_t ix = FFS(words); if (!ix) { return zapped; // if the top level summary is 0, no bit is set } index_t base = ix - 1; // offset, in number of uin64_t words ADJUST_OFFSET_FOR_FFS_ACROSS_SUMMARIES(words, base, level); words += (1 << (NB * (level - 1))) * NUM_64b; // the beginning of the non-summarized bitarray uint64_t *word = words + base * NUM_64b; // the first non-zero word ix = base; // the idea here is that we zap a whole bunch of bits at once unsigned z = FFS_and_zap_word(word, max - zapped, indices + zapped, base << NB); assert(z); zapped += z; if ((zapped < max) /* entire word was zapped */ || all_zeros(word) /* partial zap, a priori */) { // adjust summaries to reflect all zeros in the bitarray ZAP_SUMMARIES(bits, ix, level - 1); } } return zapped; } #if 0 /******************************** Test and debug utilities ***************************/ static void print_ones(const uint64_t *bits, unsigned num_big_words) { unsigned base = 0; unsigned num = num_big_words * NUM_64b; // printf("In print_ones; num=%d, num_big=%d \n", num, num_big_words); while (num--) { uint64_t word = *(bits++); if (word) { for (unsigned bit = 0; bit < 64; bit++) { if (word & (1ULL << bit)) { printf("%d ", base + bit); } } } base += 64; } } void bitarray_print(bitarray_t bits, unsigned log_size) { assert(log_size <= MAX_LEVEL * NB); printf("bitarray %p log_size=%d\n", bits, log_size); if (log_size > 4 * NB) { printf("Level 4: "); print_ones(bits, 1); printf("\n"); printf("Level 3: "); print_ones(bits + LEVEL0, 1 << NB); printf("\n"); printf("Level 2: "); print_ones(bits + LEVEL1, 1 << NB); printf("\n"); printf("Level 1: "); print_ones(bits + LEVEL2, 1 << NB); printf("\n"); printf("Level 0: "); print_ones(bits + LEVEL3, 1 << (log_size - NB)); printf("\n"); } else if (log_size > 3 * NB) { printf("Level 3: "); print_ones(bits, 1); printf("\n"); printf("Level 2: "); print_ones(bits + LEVEL0, 1 << NB); printf("\n"); printf("Level 1: "); print_ones(bits + LEVEL1, 1 << NB); printf("\n"); printf("Level 0: "); print_ones(bits + LEVEL2, 1 << (log_size - NB)); printf("\n"); } else if (log_size > 2 * NB) { printf("Level 2: "); print_ones(bits, 1); printf("\n"); printf("Level 1: "); print_ones(bits + LEVEL0, 1 << NB); printf("\n"); printf("Level 0: "); print_ones(bits + LEVEL1, 1 << (log_size - NB)); printf("\n"); } else if (log_size > NB) { printf("Level 1: "); print_ones(bits, 1); printf("\n"); printf("Level 0: "); print_ones(bits + LEVEL0, 1 << (log_size - NB)); printf("\n"); } else { printf("Level 0: "); print_ones(bits, 1); printf("\n"); } } bool compare_to_truth(bitarray_t bits, unsigned nbits, const bool *truth) { uint64_t *start = bits; if (nbits > NB) { unsigned level = (nbits - NB - 1) / NB; start += levels_num_words[level]; } bool ok = 1; for (unsigned bit = 0; bit < (1 << nbits); bit++) { bool expected = truth[bit]; uint64_t word = start[bit >> 6]; bool actual = (word >> (bit & 63)) & 1; if (actual != expected) { printf("*** # for bit %d, expected=%d actual=%d\n", bit, expected, actual); ok = 0; } } return ok; } unsigned first_set_in_truth(const bool *truth, unsigned log_size) { for (unsigned bit = 0; bit < (1 << log_size); bit++) { if (truth[bit]) { return bit + 1; } } return 0; } void truth_print(const bool *truth, unsigned log_size) { printf("Truth: "); for (unsigned bit = 0; bit < (1 << log_size); bit++) { if (truth[bit]) { printf("%d ", bit); } } printf("\n"); } #endif /* vim: set noet:ts=4:sw=4:cindent: */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/bitarray.h ================================================ /* * Copyright (c) 1999, 2000, 2003, 2005, 2008, 2012 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __BITARRAY_H #define __BITARRAY_H typedef uint64_t *bitarray_t; // array of bits, assumed to be mostly 0 typedef uint32_t index_t; // we limit the number of bits to be a 32-bit quantity /* A bitarray uses a summarization to be able to quickly say what's the first bit that is set to 1; All together each of the entry points will do a very small number of memory access (exact number depends on log_size) */ extern size_t bitarray_size(unsigned log_size); // For a bitarray with 1<> 5 identifies the uint32_t to manipulate in the conceptually contiguous bits array // (index >> 5) << 1 identifies the uint32_t allowing for the actual interleaving bits[(index >> 5) << 1] |= (1 << (index & 31)); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE void BITARRAY_CLR(uint32_t *bits, msize_t index) { bits[(index >> 5) << 1] &= ~(1 << (index & 31)); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE boolean_t BITARRAY_BIT(uint32_t *bits, msize_t index) { return ((bits[(index >> 5) << 1]) >> (index & 31)) & 1; } /* Macros used to manipulate the uint32_t quantity mag_bitmap. */ /* BITMAPV variants are used by tiny. */ #if defined(__LP64__) // assert(NUM_SLOTS == 64) in which case (slot >> 5) is either 0 or 1 #define BITMAPV_SET(bitmap, slot) (bitmap[(slot) >> 5] |= 1 << ((slot)&31)) #define BITMAPV_CLR(bitmap, slot) (bitmap[(slot) >> 5] &= ~(1 << ((slot)&31))) #define BITMAPV_BIT(bitmap, slot) ((bitmap[(slot) >> 5] >> ((slot)&31)) & 1) #define BITMAPV_CTZ(bitmap) (__builtin_ctzl(bitmap)) #else // assert(NUM_SLOTS == 32) in which case (slot >> 5) is always 0, so code it that way #define BITMAPV_SET(bitmap, slot) (bitmap[0] |= 1 << (slot)) #define BITMAPV_CLR(bitmap, slot) (bitmap[0] &= ~(1 << (slot))) #define BITMAPV_BIT(bitmap, slot) ((bitmap[0] >> (slot)) & 1) #define BITMAPV_CTZ(bitmap) (__builtin_ctz(bitmap)) #endif /* BITMAPN is used by small. (slot >> 5) takes on values from 0 to 7. */ #define BITMAPN_SET(bitmap, slot) (bitmap[(slot) >> 5] |= 1 << ((slot)&31)) #define BITMAPN_CLR(bitmap, slot) (bitmap[(slot) >> 5] &= ~(1 << ((slot)&31))) #define BITMAPN_BIT(bitmap, slot) ((bitmap[(slot) >> 5] >> ((slot)&31)) & 1) /* returns bit # of least-significant one bit, starting at 0 (undefined if !bitmap) */ #define BITMAP32_CTZ(bitmap) (__builtin_ctz(bitmap[0])) #endif // __BITARRAY_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/debug.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __DEBUG_H #define __DEBUG_H // set to one to debug malloc itself #define DEBUG_MALLOC 0 // set to one to debug malloc client #define DEBUG_CLIENT 0 #define DEBUG_MADVISE 0 #if DEBUG_MALLOC # warning DEBUG_MALLOC ENABLED # undef MALLOC_INLINE # undef MALLOC_UNUSED # undef MALLOC_ALWAYS_INLINE # undef CHECK_MAGAZINE_PTR_LOCKED # define MALLOC_INLINE # define MALLOC_UNUSED # define MALLOC_ALWAYS_INLINE # define CHECK_MAGAZINE_PTR_LOCKED(szone, mag_ptr, fun) \ do { \ if (TRY_LOCK(mag_ptr->magazine_lock)) { \ malloc_report(ASL_LEVEL_ERR, "*** magazine_lock was not set %p in %s\n", \ mag_ptr->magazine_lock, fun); \ } \ } while (0) #endif // DEBUG_MALLOC #if DEBUG_MALLOC || DEBUG_CLIENT # define CHECK(szone, fun) \ if ((szone)->debug_flags & CHECK_REGIONS) { \ szone_check_all(szone, fun) \ } #else // DEBUG_MALLOC || DEBUG_CLIENT # define CHECK(szone, fun) \ do {} while (0) #endif // DEBUG_MALLOC || DEBUG_CLIENT #endif // __DEBUG_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/dtrace.h ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __DTRACE_H #define __DTRACE_H #ifndef DARWINTEST #include "magmallocProvider.h" #else #define MAGMALLOC_ALLOCREGION(arg0, arg1, arg2, arg3) #define MAGMALLOC_ALLOCREGION_ENABLED() (0) #define MAGMALLOC_DEALLOCREGION(arg0, arg1, arg2) #define MAGMALLOC_DEALLOCREGION_ENABLED() (0) #define MAGMALLOC_DEPOTREGION(arg0, arg1, arg2, arg3, arg4) #define MAGMALLOC_DEPOTREGION_ENABLED() (0) #define MAGMALLOC_MADVFREEREGION(arg0, arg1, arg2, arg3) #define MAGMALLOC_MADVFREEREGION_ENABLED() (0) #define MAGMALLOC_MALLOCERRORBREAK() #define MAGMALLOC_MALLOCERRORBREAK_ENABLED() (0) #define MAGMALLOC_PRESSURERELIEFBEGIN(arg0, arg1, arg2) #define MAGMALLOC_PRESSURERELIEFBEGIN_ENABLED() (0) #define MAGMALLOC_PRESSURERELIEFEND(arg0, arg1, arg2, arg3) #define MAGMALLOC_PRESSURERELIEFEND_ENABLED() (0) #define MAGMALLOC_RECIRCREGION(arg0, arg1, arg2, arg3, arg4) #define MAGMALLOC_RECIRCREGION_ENABLED() (0) #define MAGMALLOC_REFRESHINDEX(arg0, arg1, arg2) #define MAGMALLOC_REFRESHINDEX_ENABLED() (0) #endif #endif // __DTRACE_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/empty.s ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * This file exists to force clang+ld to produce an link-time optimised * master object file that contains no bitcode when it is laid down on * disk. * * Adding a non-LTO assembly file to the link step forces ld to perform * LTO and produce the master object file. */ empty: nop .subsections_via_symbols ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/frozen_malloc.c ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" /********* Support code for emacs unexec ************/ /* History of freezedry version numbers: * * 1) Old malloc (before the scalable malloc implementation in this file * existed). * 2) Original freezedrying code for scalable malloc. This code was apparently * based on the old freezedrying code and was fundamentally flawed in its * assumption that tracking allocated memory regions was adequate to fake * operations on freezedried memory. This doesn't work, since scalable * malloc does not store flags in front of large page-aligned allocations. * 3) Original szone-based freezedrying code. * 4) Fresher malloc with tiny zone * 5) 32/64bit compatible malloc * 6) Metadata within 1MB and 8MB region for tiny and small * * No version backward compatibility is provided, but the version number does * make it possible for malloc_jumpstart() to return an error if the application * was freezedried with an older version of malloc. */ #define MALLOC_FREEZEDRY_VERSION 6 typedef struct { unsigned version; unsigned nszones; szone_t *szones; } malloc_frozen; static void * frozen_malloc(szone_t *zone, size_t new_size) { return malloc(new_size); } static void * frozen_calloc(szone_t *zone, size_t num_items, size_t size) { return calloc(num_items, size); } static void * frozen_valloc(szone_t *zone, size_t new_size) { return valloc(new_size); } static void * frozen_realloc(szone_t *zone, void *ptr, size_t new_size) { size_t old_size = szone_size(zone, ptr); void *new_ptr; if (new_size <= old_size) { return ptr; } new_ptr = malloc(new_size); if (old_size > 0) { memcpy(new_ptr, ptr, old_size); } return new_ptr; } static void frozen_free(szone_t *zone, void *ptr) { } static void frozen_destroy(szone_t *zone) { } /********* Pseudo-private API for emacs unexec ************/ /* * malloc_freezedry() records all of the szones in use, so that they can be * partially reconstituted by malloc_jumpstart(). Due to the differences * between reconstituted memory regions and those created by the szone code, * care is taken not to reallocate from the freezedried memory, except in the * case of a non-growing realloc(). * * Due to the flexibility provided by the zone registration mechanism, it is * impossible to implement generic freezedrying for any zone type. This code * only handles applications that use the szone allocator, so malloc_freezedry() * returns 0 (error) if any non-szone zones are encountered. */ uintptr_t malloc_freezedry(void) { extern unsigned malloc_num_zones; extern malloc_zone_t **malloc_zones; malloc_frozen *data; unsigned i; /* Allocate space in which to store the freezedry state. */ data = (malloc_frozen *)malloc(sizeof(malloc_frozen)); /* Set freezedry version number so that malloc_jumpstart() can check for compatibility. */ data->version = MALLOC_FREEZEDRY_VERSION; /* Allocate the array of szone pointers. */ data->nszones = malloc_num_zones; data->szones = (szone_t *)calloc(malloc_num_zones, sizeof(szone_t)); /* * Fill in the array of szone structures. They are copied rather than * referenced, since the originals are likely to be clobbered during malloc * initialization. */ for (i = 0; i < malloc_num_zones; i++) { if (strcmp(malloc_zones[i]->zone_name, "DefaultMallocZone")) { /* Unknown zone type. */ free(data->szones); free(data); return 0; } memcpy(&data->szones[i], malloc_zones[i], sizeof(szone_t)); } return ((uintptr_t)data); } int malloc_jumpstart(uintptr_t cookie) { malloc_frozen *data = (malloc_frozen *)cookie; unsigned i; if (data->version != MALLOC_FREEZEDRY_VERSION) { /* Unsupported freezedry version. */ return 1; } for (i = 0; i < data->nszones; i++) { /* Set function pointers. Even the functions that stay the same must be * set, since there are no guarantees that they will be mapped to the * same addresses. */ data->szones[i].basic_zone.size = (void *)szone_size; data->szones[i].basic_zone.malloc = (void *)frozen_malloc; data->szones[i].basic_zone.calloc = (void *)frozen_calloc; data->szones[i].basic_zone.valloc = (void *)frozen_valloc; data->szones[i].basic_zone.free = (void *)frozen_free; data->szones[i].basic_zone.realloc = (void *)frozen_realloc; data->szones[i].basic_zone.destroy = (void *)frozen_destroy; data->szones[i].basic_zone.introspect = (struct malloc_introspection_t *)&szone_introspect; /* Register the freezedried zone. */ malloc_zone_register(&data->szones[i].basic_zone); } return 0; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/frozen_malloc.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __FROZEN_MALLOC_H #define __FROZEN_MALLOC_H MALLOC_EXPORT uintptr_t malloc_freezedry(void); MALLOC_EXPORT int malloc_jumpstart(uintptr_t cookie); #endif // __FROZEN_MALLOC_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/internal.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __INTERNAL_H #define __INTERNAL_H #define __OS_EXPOSE_INTERNALS__ 1 #include #include #include <_simple.h> #include #undef memcpy #define memcpy _platform_memmove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dtrace.h" #include "base.h" #include "trace.h" #include "platform.h" #include "debug.h" #include "locking.h" #include "bitarray.h" #include "malloc.h" #include "printf.h" #include "frozen_malloc.h" #include "legacy_malloc.h" #include "magazine_malloc.h" #include "malloc_common.h" #include "nano_malloc_common.h" #include "nano_malloc.h" #include "nanov2_malloc.h" #include "purgeable_malloc.h" #include "malloc_private.h" #include "stack_logging.h" #include "stack_logging_internal.h" #include "thresholds.h" #include "vm.h" #include "magazine_rack.h" #include "magazine_zone.h" #include "nano_zone_common.h" #include "nano_zone.h" #include "nanov2_zone.h" #include "magazine_inline.h" extern uint64_t malloc_entropy[2]; MALLOC_NOEXPORT extern boolean_t malloc_tracing_enabled; MALLOC_NOEXPORT extern unsigned malloc_debug_flags; MALLOC_NOEXPORT MALLOC_NOINLINE void malloc_error_break(void); MALLOC_NOEXPORT MALLOC_NOINLINE MALLOC_USED int malloc_gdb_po_unsafe(void); MALLOC_NOEXPORT extern uint64_t max_lite_mallocs; #endif // __INTERNAL_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/legacy_malloc.c ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" /* * For use by CheckFix: create a new zone whose behavior is, apart from * the use of death-row and per-CPU magazines, that of Leopard. */ static MALLOC_NOINLINE void * legacy_valloc(szone_t *szone, size_t size) { void *ptr; size_t num_kernel_pages; num_kernel_pages = round_page_quanta(size) >> vm_page_quanta_shift; ptr = large_malloc(szone, num_kernel_pages, 0, TRUE); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "legacy_valloc returned %p\n", ptr); } #endif return ptr; } malloc_zone_t * create_legacy_scalable_zone(size_t initial_size, unsigned debug_flags) { // legacy always uses 32 small slots malloc_zone_t *mzone = create_scalable_zone(initial_size, debug_flags & ~MALLOC_EXTENDED_SMALL_SLOTS); szone_t *szone = (szone_t *)mzone; if (!szone) { return NULL; } szone->is_largemem = 0; szone->large_threshold = (15 * 1024); szone->vm_copy_threshold = (40 * 1024); mprotect(szone, sizeof(szone->basic_zone), PROT_READ | PROT_WRITE); szone->basic_zone.valloc = (void *)legacy_valloc; szone->basic_zone.free_definite_size = NULL; mprotect(szone, sizeof(szone->basic_zone), PROT_READ); return mzone; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/legacy_malloc.h ================================================ /* * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __LEGACY_MALLOC_H #define __LEGACY_MALLOC_H MALLOC_NOEXPORT malloc_zone_t * create_legacy_scalable_zone(size_t initial_size, unsigned debug_flags); #endif // __LEGACY_MALLOC_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/locking.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __LOCKING_H #define __LOCKING_H #if OS_UNFAIR_LOCK_INLINE #define os_unfair_lock_lock_with_options(lock, options) \ os_unfair_lock_lock_with_options_inline(lock, options) #define os_unfair_lock_trylock(lock) \ os_unfair_lock_trylock_inline(lock) #define os_unfair_lock_unlock(lock) \ os_unfair_lock_unlock_inline(lock) #endif // OS_UNFAIR_LOCK_INLINE typedef os_unfair_lock _malloc_lock_s; #define _MALLOC_LOCK_INIT OS_UNFAIR_LOCK_INIT __attribute__((always_inline)) static inline void _malloc_lock_init(_malloc_lock_s *lock) { *lock = OS_UNFAIR_LOCK_INIT; } MALLOC_ALWAYS_INLINE static inline void _malloc_lock_lock(_malloc_lock_s *lock) { return os_unfair_lock_lock_with_options(lock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION); } MALLOC_ALWAYS_INLINE static inline bool _malloc_lock_trylock(_malloc_lock_s *lock) { return os_unfair_lock_trylock(lock); } MALLOC_ALWAYS_INLINE static inline void _malloc_lock_unlock(_malloc_lock_s *lock) { return os_unfair_lock_unlock(lock); } MALLOC_ALWAYS_INLINE static inline void _malloc_lock_assert_owner(_malloc_lock_s *lock) { os_unfair_lock_assert_owner(lock); } #endif // __LOCKING_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_inline.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __MAGAZINE_INLINE_H #define __MAGAZINE_INLINE_H extern unsigned int _os_cpu_number_override; /* * MALLOC_ABSOLUTE_MAX_SIZE - There are many instances of addition to a * user-specified size_t, which can cause overflow (and subsequent crashes) * for values near SIZE_T_MAX. Rather than add extra "if" checks everywhere * this occurs, it is easier to just set an absolute maximum request size, * and immediately return an error if the requested size exceeds this maximum. * Of course, values less than this absolute max can fail later if the value * is still too large for the available memory. The largest value added * seems to be PAGE_SIZE (in the macro round_page()), so to be safe, we set * the maximum to be 2 * PAGE_SIZE less than SIZE_T_MAX. */ #define MALLOC_ABSOLUTE_MAX_SIZE (SIZE_T_MAX - (2 * PAGE_SIZE)) // Gets the allocation size for a calloc(). Multiples size by num_items and adds // extra_size, storing the result in *total_size. Returns 0 on success, -1 (with // errno set to ENOMEM) on overflow. static int MALLOC_INLINE MALLOC_ALWAYS_INLINE calloc_get_size(size_t num_items, size_t size, size_t extra_size, size_t *total_size) { size_t alloc_size = size; if (num_items != 1 && (os_mul_overflow(num_items, size, &alloc_size) || alloc_size > MALLOC_ABSOLUTE_MAX_SIZE)) { errno = ENOMEM; return -1; } if (extra_size && (os_add_overflow(alloc_size, extra_size, &alloc_size) || alloc_size > MALLOC_ABSOLUTE_MAX_SIZE)) { errno = ENOMEM; return -1; } *total_size = alloc_size; return 0; } /********************* FREE LIST UTILITIES ************************/ // A free list entry is comprised of a pair of pointers, previous and next. // These are used to implement a doubly-linked list, which permits efficient // extraction. // // Because the free list entries are previously freed objects, a misbehaved // program may write to a pointer after it has called free() on that pointer, // either by dereference or buffer overflow from an adjacent pointer. This write // would then corrupt the free list's previous and next pointers, leading to a // crash. In order to detect this case, we take advantage of the fact that // malloc'd pointers are known to be at least 16 byte aligned, and thus have // at least 4 trailing zero bits. // // When an entry is added to the free list, a checksum of the previous and next // pointers is calculated and written to the high four bits of the respective // pointers. Upon detection of an invalid checksum, an error is logged and NULL // is returned. Since all code which un-checksums pointers checks for a NULL // return, a potentially crashing or malicious dereference is avoided at the // cost of leaking the corrupted block, and any subsequent blocks on the free // list of that size. #pragma mark forward decls static MALLOC_INLINE uintptr_t free_list_gen_checksum(uintptr_t ptr) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE uintptr_t free_list_checksum_ptr(rack_t *rack, void *p) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE void *free_list_unchecksum_ptr(rack_t *rack, inplace_union *ptr) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE unsigned free_list_count(rack_t *rack, free_list_t ptr); static MALLOC_INLINE void recirc_list_extract(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE void recirc_list_splice_last(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE void recirc_list_splice_first(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE void yield(void) { thread_switch(MACH_PORT_NULL, SWITCH_OPTION_DEPRESS, 1); } static MALLOC_INLINE kern_return_t _szone_default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr) { *ptr = (void *)address; return 0; } #pragma mark helpers static MALLOC_INLINE MALLOC_ALWAYS_INLINE uint64_t platform_hw_memsize(void) { #if CONFIG_HAS_COMMPAGE_MEMSIZE return *(uint64_t *)(uintptr_t)_COMM_PAGE_MEMORY_SIZE; #else uint64_t hw_memsize = 0; size_t uint64_t_size = sizeof(hw_memsize); // hw_memsize was always 0 if sysctlbyname failed, so preserve that behaviour (void)sysctlbyname("hw.memsize", &hw_memsize, &uint64_t_size, 0, 0); return hw_memsize; #endif } static MALLOC_INLINE MALLOC_ALWAYS_INLINE uint32_t platform_cpu_count(void) { #if CONFIG_HAS_COMMPAGE_NCPUS return *(uint8_t *)(uintptr_t)_COMM_PAGE_NCPUS; #else return sysconf(_SC_NPROCESSORS_CONF); #endif } #pragma mark szone locking static MALLOC_INLINE MALLOC_ALWAYS_INLINE void SZONE_LOCK(szone_t *szone) { _malloc_lock_lock(&szone->large_szone_lock); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE void SZONE_UNLOCK(szone_t *szone) { _malloc_lock_unlock(&szone->large_szone_lock); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE bool SZONE_TRY_LOCK(szone_t *szone) { return _malloc_lock_trylock(&szone->large_szone_lock); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE void SZONE_REINIT_LOCK(szone_t *szone) { _malloc_lock_init(&szone->large_szone_lock); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE void SZONE_MAGAZINE_PTR_LOCK(magazine_t *mag_ptr) { _malloc_lock_lock(&mag_ptr->magazine_lock); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE void SZONE_MAGAZINE_PTR_UNLOCK(magazine_t *mag_ptr) { _malloc_lock_unlock(&mag_ptr->magazine_lock); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE bool SZONE_MAGAZINE_PTR_TRY_LOCK(magazine_t *mag_ptr) { return _malloc_lock_trylock(&mag_ptr->magazine_lock); } static MALLOC_INLINE MALLOC_ALWAYS_INLINE void SZONE_MAGAZINE_PTR_REINIT_LOCK(magazine_t *mag_ptr) { _malloc_lock_init(&mag_ptr->magazine_lock); } #pragma mark free list static MALLOC_NOINLINE void free_list_checksum_botch(rack_t *rack, void *ptr, void *value) { malloc_zone_error(rack->debug_flags, true, "Incorrect checksum for freed object %p: " "probably modified after being freed.\n" "Corrupt value: %p\n", ptr, value); } static MALLOC_INLINE uintptr_t free_list_gen_checksum(uintptr_t ptr) { uint8_t chk; chk = (unsigned char)(ptr >> 0); chk += (unsigned char)(ptr >> 8); chk += (unsigned char)(ptr >> 16); chk += (unsigned char)(ptr >> 24); #if __LP64__ chk += (unsigned char)(ptr >> 32); chk += (unsigned char)(ptr >> 40); chk += (unsigned char)(ptr >> 48); chk += (unsigned char)(ptr >> 56); #endif return chk; } static unsigned free_list_count(rack_t *rack, free_list_t ptr) { unsigned count = 0; while (ptr.p) { count++; ptr.p = free_list_unchecksum_ptr(rack, &ptr.inplace->next); } return count; } #define NYBBLE 4 #if __LP64__ #define ANTI_NYBBLE (64 - NYBBLE) #else #define ANTI_NYBBLE (32 - NYBBLE) #endif static MALLOC_INLINE uintptr_t free_list_checksum_ptr(rack_t *rack, void *ptr) { uintptr_t p = (uintptr_t)ptr; return (p >> NYBBLE) | ((free_list_gen_checksum(p ^ rack->cookie) & (uintptr_t)0xF) << ANTI_NYBBLE); // compiles to rotate instruction } static MALLOC_INLINE void * free_list_unchecksum_ptr(rack_t *rack, inplace_union *ptr) { inplace_union p; uintptr_t t = ptr->u; t = (t << NYBBLE) | (t >> ANTI_NYBBLE); // compiles to rotate instruction p.u = t & ~(uintptr_t)0xF; if ((t ^ free_list_gen_checksum(p.u ^ rack->cookie)) & (uintptr_t)0xF) { free_list_checksum_botch(rack, ptr, (void *)ptr->u); __builtin_trap(); } return p.p; } #undef ANTI_NYBBLE #undef NYBBLE #pragma mark recirc helpers static MALLOC_INLINE void recirc_list_extract(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) { // excise node from list if (NULL == node->prev) { mag_ptr->firstNode = node->next; } else { node->prev->next = node->next; } if (NULL == node->next) { mag_ptr->lastNode = node->prev; } else { node->next->prev = node->prev; } mag_ptr->recirculation_entries--; } static MALLOC_INLINE void recirc_list_splice_last(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) { if (NULL == mag_ptr->lastNode) { mag_ptr->firstNode = node; node->prev = NULL; } else { node->prev = mag_ptr->lastNode; mag_ptr->lastNode->next = node; } mag_ptr->lastNode = node; node->next = NULL; node->recirc_suitable = FALSE; mag_ptr->recirculation_entries++; } static MALLOC_INLINE void recirc_list_splice_first(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) { if (NULL == mag_ptr->firstNode) { mag_ptr->lastNode = node; node->next = NULL; } else { node->next = mag_ptr->firstNode; mag_ptr->firstNode->prev = node; } mag_ptr->firstNode = node; node->prev = NULL; node->recirc_suitable = FALSE; mag_ptr->recirculation_entries++; } /******************************************************************************* * Region hash implementation * * This is essentially a duplicate of the existing Large allocator hash, minus * the ability to remove entries. The two should be combined eventually. ******************************************************************************/ #pragma mark region hash /* * hash_lookup_region_no_lock - Scan a hash ring looking for an entry for a * given region. * * FIXME: If consecutive queries of the same region are likely, a one-entry * cache would likely be a significant performance win here. */ static MALLOC_INLINE rgnhdl_t hash_lookup_region_no_lock(region_t *regions, size_t num_entries, size_t shift, region_t r) { size_t index, hash_index; rgnhdl_t entry; if (!num_entries) { return 0; } // Multiplicative hash where the multiplier is a prime near (ULONG_MAX / phi). [phi = 1.618033...] // Since the values of (((uintptr_t)r >> HASH_BLOCKS_ALIGN) are (roughly) an ascending sequence of integers, // this hash works really well. See Knuth TAOCP, Vol. 3. #if __LP64__ index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 11400714819323198549ULL) >> (64 - shift); #else index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 2654435761UL) >> (32 - shift); #endif do { entry = regions + index; if (*entry == 0) { return 0; } if (*entry == r) { return entry; } if (++index == num_entries) { index = 0; } } while (index != hash_index); return 0; } /* * hash_region_insert_no_lock - Insert a region into the hash ring. */ static void hash_region_insert_no_lock(region_t *regions, size_t num_entries, size_t shift, region_t r) { size_t index, hash_index; rgnhdl_t entry; // Multiplicative hash where the multiplier is a prime near (ULONG_MAX / phi). [phi = 1.618033...] // Since the values of (((uintptr_t)r >> HASH_BLOCKS_ALIGN) are (roughly) an ascending sequence of integers, // this hash works really well. See Knuth TAOCP, Vol. 3. #if __LP64__ index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 11400714819323198549ULL) >> (64 - shift); #else index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 2654435761UL) >> (32 - shift); #endif do { entry = regions + index; if (*entry == HASHRING_OPEN_ENTRY || *entry == HASHRING_REGION_DEALLOCATED) { *entry = r; return; } if (++index == num_entries) { index = 0; } } while (index != hash_index); } /* * hash_regions_alloc_no_lock - Allocate space for a number of entries. This * must be a VM allocation as to avoid recursing between allocating a new small * region, and asking the small region to allocate space for the new list of * regions. */ static region_t * hash_regions_alloc_no_lock(size_t num_entries) { size_t size = num_entries * sizeof(region_t); return mvm_allocate_pages(round_page_quanta(size), 0, 0, VM_MEMORY_MALLOC); } /* * hash_regions_grow_no_lock - Grow the hash ring, and rehash the entries. * Return the new region and new size to update the szone. Do not deallocate * the old entries since someone may still be allocating them. */ static MALLOC_INLINE region_t * hash_regions_grow_no_lock(region_t *regions, size_t old_size, size_t *mutable_shift, size_t *new_size) { // double in size and allocate memory for the regions *new_size = old_size + old_size; *mutable_shift = *mutable_shift + 1; region_t *new_regions = hash_regions_alloc_no_lock(*new_size); // rehash the entries into the new list size_t index; for (index = 0; index < old_size; ++index) { region_t r = regions[index]; if (r != HASHRING_OPEN_ENTRY && r != HASHRING_REGION_DEALLOCATED) { hash_region_insert_no_lock(new_regions, *new_size, *mutable_shift, r); } } return new_regions; } #pragma mark mag index /* * These commpage routines provide fast access to the logical cpu number * of the calling processor assuming no pre-emption occurs. */ extern unsigned int hyper_shift; extern unsigned int phys_ncpus; extern unsigned int logical_ncpus; static MALLOC_INLINE MALLOC_ALWAYS_INLINE unsigned int mag_max_magazines(void) { return max_magazines; } #pragma mark mag lock static MALLOC_INLINE magazine_t * mag_lock_zine_for_region_trailer(magazine_t *magazines, region_trailer_t *trailer, mag_index_t mag_index) { mag_index_t refreshed_index; magazine_t *mag_ptr = &(magazines[mag_index]); // Take the lock on entry. SZONE_MAGAZINE_PTR_LOCK(mag_ptr); // Now in the time it took to acquire the lock, the region may have migrated // from one magazine to another. In which case the magazine lock we obtained // (namely magazines[mag_index].mag_lock) is stale. If so, keep on tryin' ... while (mag_index != (refreshed_index = trailer->mag_index)) { // Note assignment SZONE_MAGAZINE_PTR_UNLOCK(mag_ptr); mag_index = refreshed_index; mag_ptr = &(magazines[mag_index]); SZONE_MAGAZINE_PTR_LOCK(mag_ptr); } return mag_ptr; } #pragma mark tiny allocator /* * tiny_region_for_ptr_no_lock - Returns the tiny region containing the pointer, * or NULL if not found. */ static MALLOC_INLINE region_t tiny_region_for_ptr_no_lock(rack_t *rack, const void *ptr) { rgnhdl_t r = hash_lookup_region_no_lock(rack->region_generation->hashed_regions, rack->region_generation->num_regions_allocated, rack->region_generation->num_regions_allocated_shift, TINY_REGION_FOR_PTR(ptr)); return r ? *r : r; } /* * Obtain the size of a free tiny block (in msize_t units). */ static msize_t get_tiny_free_size(const void *ptr) { void *next_block = (void *)((uintptr_t)ptr + TINY_QUANTUM); void *region_end = TINY_REGION_END(TINY_REGION_FOR_PTR(ptr)); // check whether the next block is outside the tiny region or a block header // if so, then the size of this block is one, and there is no stored size. if (next_block < region_end) { uint32_t *next_header = TINY_BLOCK_HEADER_FOR_PTR(next_block); msize_t next_index = TINY_INDEX_FOR_PTR(next_block); if (!BITARRAY_BIT(next_header, next_index)) { return TINY_FREE_SIZE(ptr); } } return 1; } static MALLOC_INLINE msize_t get_tiny_meta_header(const void *ptr, boolean_t *is_free) { // returns msize and is_free // may return 0 for the msize component (meaning 65536) uint32_t *block_header; msize_t index; block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr); index = TINY_INDEX_FOR_PTR(ptr); msize_t midx = (index >> 5) << 1; uint32_t mask = 1 << (index & 31); *is_free = 0; if (0 == (block_header[midx] & mask)) { // if (!BITARRAY_BIT(block_header, index)) return 0; } if (0 == (block_header[midx + 1] & mask)) { // if (!BITARRAY_BIT(in_use, index)) *is_free = 1; return get_tiny_free_size(ptr); } // index >> 5 identifies the uint32_t to manipulate in the conceptually contiguous bits array // (index >> 5) << 1 identifies the uint32_t allowing for the actual interleaving #if defined(__LP64__) // The return value, msize, is computed as the distance to the next 1 bit in block_header. // That's guaranteed to be somewhere in the next 64 bits. And those bits could span three // uint32_t block_header elements. Collect the bits into a single uint64_t and measure up with ffsl. uint32_t *addr = ((uint32_t *)block_header) + ((index >> 5) << 1); uint32_t bitidx = index & 31; uint64_t word_lo = addr[0]; uint64_t word_mid = addr[2]; uint64_t word_hi = addr[4]; uint64_t word_lomid = (word_lo >> bitidx) | (word_mid << (32 - bitidx)); uint64_t word = bitidx ? word_lomid | (word_hi << (64 - bitidx)) : word_lomid; uint32_t result = __builtin_ffsl(word >> 1); #else // The return value, msize, is computed as the distance to the next 1 bit in block_header. // That's guaranteed to be somewhere in the next 32 bits. And those bits could span two // uint32_t block_header elements. Collect the bits into a single uint32_t and measure up with ffs. uint32_t *addr = ((uint32_t *)block_header) + ((index >> 5) << 1); uint32_t bitidx = index & 31; uint32_t word = bitidx ? (addr[0] >> bitidx) | (addr[2] << (32 - bitidx)) : addr[0]; uint32_t result = __builtin_ffs(word >> 1); #endif return result; } #pragma mark small allocator /* * small_region_for_ptr_no_lock - Returns the small region containing the pointer, * or NULL if not found. */ static MALLOC_INLINE region_t small_region_for_ptr_no_lock(rack_t *rack, const void *ptr) { rgnhdl_t r = hash_lookup_region_no_lock(rack->region_generation->hashed_regions, rack->region_generation->num_regions_allocated, rack->region_generation->num_regions_allocated_shift, SMALL_REGION_FOR_PTR(ptr)); return r ? *r : r; } #endif // __MAGAZINE_INLINE_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_large.c ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" #if DEBUG_MALLOC static void large_debug_print(szone_t *szone) { unsigned index; large_entry_t *range; _SIMPLE_STRING b = _simple_salloc(); if (b) { for (index = 0, range = szone->large_entries; index < szone->num_large_entries; index++, range++) { if (range->address) { _simple_sprintf(b, "%d: %p(%y); ", index, range->address, range->size); } } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%s\n", _simple_string(b)); _simple_sfree(b); } } #endif /* * Scan the hash ring looking for an entry containing a given pointer. */ static large_entry_t * large_entry_containing_pointer_no_lock(szone_t *szone, const void *ptr) { // result only valid with lock held unsigned num_large_entries = szone->num_large_entries; unsigned hash_index; unsigned index; large_entry_t *range; if (!num_large_entries) { return NULL; } hash_index = ((uintptr_t)ptr >> vm_page_quanta_shift) % num_large_entries; index = hash_index; do { range = szone->large_entries + index; if (range->address == (vm_address_t)ptr) { return range; } else if ((vm_address_t)ptr >= range->address && (vm_address_t)ptr < range->address + range->size) { return range; } // Since we may be looking for an inner pointer, we might not get an // exact match on the address, so we need to scan further and to skip // over empty entries. It will usually be faster to scan backwards. index = index == 0 ? num_large_entries - 1 : index - 1; } while (index != hash_index); return NULL; } /* * Scan the hash ring looking for an entry for the given pointer. */ large_entry_t * large_entry_for_pointer_no_lock(szone_t *szone, const void *ptr) { // result only valid with lock held unsigned num_large_entries = szone->num_large_entries; unsigned hash_index; unsigned index; large_entry_t *range; if (!num_large_entries) { return NULL; } hash_index = ((uintptr_t)ptr >> vm_page_quanta_shift) % num_large_entries; index = hash_index; do { range = szone->large_entries + index; if (range->address == (vm_address_t)ptr) { return range; } if (0 == range->address) { return NULL; // end of chain } index++; if (index == num_large_entries) { index = 0; } } while (index != hash_index); return NULL; } static void large_entry_insert_no_lock(szone_t *szone, large_entry_t range) { unsigned num_large_entries = szone->num_large_entries; unsigned hash_index = (((uintptr_t)(range.address)) >> vm_page_quanta_shift) % num_large_entries; unsigned index = hash_index; large_entry_t *entry; // assert(szone->num_large_objects_in_use < szone->num_large_entries); /* must be called with room to spare */ do { entry = szone->large_entries + index; if (0 == entry->address) { *entry = range; return; // end of chain } index++; if (index == num_large_entries) { index = 0; } } while (index != hash_index); // assert(0); /* must not fallthrough! */ } // FIXME: can't we simply swap the (now empty) entry with the last entry on the collision chain for this hash slot? static MALLOC_INLINE void large_entries_rehash_after_entry_no_lock(szone_t *szone, large_entry_t *entry) { unsigned num_large_entries = szone->num_large_entries; uintptr_t hash_index = entry - szone->large_entries; uintptr_t index = hash_index; large_entry_t range; // assert(entry->address == 0) /* caller must have cleared *entry */ do { index++; if (index == num_large_entries) { index = 0; } range = szone->large_entries[index]; if (0 == range.address) { return; } szone->large_entries[index].address = (vm_address_t)0; szone->large_entries[index].size = 0; szone->large_entries[index].did_madvise_reusable = FALSE; large_entry_insert_no_lock(szone, range); // this will reinsert in the // proper place } while (index != hash_index); // assert(0); /* since entry->address == 0, must not fallthrough! */ } // FIXME: num should probably be a size_t, since you can theoretically allocate // more than 2^32-1 large_threshold objects in 64 bit. static MALLOC_INLINE large_entry_t * large_entries_alloc_no_lock(unsigned num) { size_t size = num * sizeof(large_entry_t); // Note that we allocate memory (via a system call) under a spin lock // That is certainly evil, however it's very rare in the lifetime of a process // The alternative would slow down the normal case return mvm_allocate_pages(round_page_quanta(size), 0, 0, VM_MEMORY_MALLOC_LARGE); } void large_entries_free_no_lock(szone_t *szone, large_entry_t *entries, unsigned num, vm_range_t *range_to_deallocate) { size_t size = num * sizeof(large_entry_t); range_to_deallocate->address = (vm_address_t)entries; range_to_deallocate->size = round_page_quanta(size); } static large_entry_t * large_entries_grow_no_lock(szone_t *szone, vm_range_t *range_to_deallocate) { // sets range_to_deallocate unsigned old_num_entries = szone->num_large_entries; large_entry_t *old_entries = szone->large_entries; // always an odd number for good hashing unsigned new_num_entries = (old_num_entries) ? old_num_entries * 2 + 1 : (unsigned)((vm_page_quanta_size / sizeof(large_entry_t)) - 1); large_entry_t *new_entries = large_entries_alloc_no_lock(new_num_entries); unsigned index = old_num_entries; large_entry_t oldRange; // if the allocation of new entries failed, bail if (new_entries == NULL) { return NULL; } szone->num_large_entries = new_num_entries; szone->large_entries = new_entries; /* rehash entries into the new list */ while (index--) { oldRange = old_entries[index]; if (oldRange.address) { large_entry_insert_no_lock(szone, oldRange); } } if (old_entries) { large_entries_free_no_lock(szone, old_entries, old_num_entries, range_to_deallocate); } else { range_to_deallocate->address = (vm_address_t)0; range_to_deallocate->size = 0; } return new_entries; } // frees the specific entry in the size table // returns a range to truly deallocate static vm_range_t large_entry_free_no_lock(szone_t *szone, large_entry_t *entry) { vm_range_t range; MALLOC_TRACE(TRACE_large_free, (uintptr_t)szone, (uintptr_t)entry->address, entry->size, 0); range.address = entry->address; range.size = entry->size; if (szone->debug_flags & MALLOC_ADD_GUARD_PAGES) { mvm_protect((void *)range.address, range.size, PROT_READ | PROT_WRITE, szone->debug_flags); range.address -= vm_page_quanta_size; range.size += 2 * vm_page_quanta_size; } entry->address = 0; entry->size = 0; entry->did_madvise_reusable = FALSE; large_entries_rehash_after_entry_no_lock(szone, entry); #if DEBUG_MALLOC if (large_entry_for_pointer_no_lock(szone, (void *)range.address)) { malloc_report(ASL_LEVEL_ERR, "*** freed entry %p still in use; num_large_entries=%d\n", range.address, szone->num_large_entries); large_debug_print(szone); szone_sleep(); } #endif return range; } kern_return_t large_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t large_entries_address, unsigned num_entries, memory_reader_t reader, vm_range_recorder_t recorder) { unsigned index = 0; vm_range_t buffer[MAX_RECORDER_BUFFER]; unsigned count = 0; large_entry_t *entries; kern_return_t err; vm_range_t range; large_entry_t entry; err = reader(task, large_entries_address, sizeof(large_entry_t) * num_entries, (void **)&entries); if (err) { return err; } index = num_entries; if (type_mask & MALLOC_ADMIN_REGION_RANGE_TYPE) { range.address = large_entries_address; range.size = round_page_quanta(num_entries * sizeof(large_entry_t)); recorder(task, context, MALLOC_ADMIN_REGION_RANGE_TYPE, &range, 1); } if (type_mask & (MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE)) { while (index--) { entry = entries[index]; if (entry.address) { range.address = entry.address; range.size = entry.size; buffer[count++] = range; if (count >= MAX_RECORDER_BUFFER) { recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE, buffer, count); count = 0; } } } } if (count) { recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE, buffer, count); } return 0; } void * large_malloc(szone_t *szone, size_t num_kernel_pages, unsigned char alignment, boolean_t cleared_requested) { void *addr; vm_range_t range_to_deallocate; size_t size; large_entry_t large_entry; MALLOC_TRACE(TRACE_large_malloc, (uintptr_t)szone, num_kernel_pages, alignment, cleared_requested); if (!num_kernel_pages) { num_kernel_pages = 1; // minimal allocation size for this szone } size = (size_t)num_kernel_pages << vm_page_quanta_shift; range_to_deallocate.size = 0; range_to_deallocate.address = 0; #if CONFIG_LARGE_CACHE if (size < LARGE_CACHE_SIZE_ENTRY_LIMIT) { // Look for a large_entry_t on the death-row cache? SZONE_LOCK(szone); int i, best = -1, idx = szone->large_entry_cache_newest, stop_idx = szone->large_entry_cache_oldest; size_t best_size = SIZE_T_MAX; while (1) { // Scan large_entry_cache for best fit, starting with most recent entry size_t this_size = szone->large_entry_cache[idx].size; addr = (void *)szone->large_entry_cache[idx].address; if (0 == alignment || 0 == (((uintptr_t)addr) & (((uintptr_t)1 << alignment) - 1))) { if (size == this_size) { // size match! best = idx; best_size = this_size; break; } if (size <= this_size && this_size < best_size) { // improved fit? best = idx; best_size = this_size; } } if (idx == stop_idx) { // exhausted live ring? break; } if (idx) { idx--; // bump idx down } else { idx = LARGE_ENTRY_CACHE_SIZE - 1; // wrap idx } } if (best > -1 && (best_size - size) < size) { // limit fragmentation to 50% addr = (void *)szone->large_entry_cache[best].address; boolean_t was_madvised_reusable = szone->large_entry_cache[best].did_madvise_reusable; // Compact live ring to fill entry now vacated at large_entry_cache[best] // while preserving time-order if (szone->large_entry_cache_oldest < szone->large_entry_cache_newest) { // Ring hasn't wrapped. Fill in from right. for (i = best; i < szone->large_entry_cache_newest; ++i) { szone->large_entry_cache[i] = szone->large_entry_cache[i + 1]; } szone->large_entry_cache_newest--; // Pull in right endpoint. } else if (szone->large_entry_cache_newest < szone->large_entry_cache_oldest) { // Ring has wrapped. Arrange to fill in from the contiguous side. if (best <= szone->large_entry_cache_newest) { // Fill from right. for (i = best; i < szone->large_entry_cache_newest; ++i) { szone->large_entry_cache[i] = szone->large_entry_cache[i + 1]; } if (0 < szone->large_entry_cache_newest) { szone->large_entry_cache_newest--; } else { szone->large_entry_cache_newest = LARGE_ENTRY_CACHE_SIZE - 1; } } else { // Fill from left. for (i = best; i > szone->large_entry_cache_oldest; --i) { szone->large_entry_cache[i] = szone->large_entry_cache[i - 1]; } if (szone->large_entry_cache_oldest < LARGE_ENTRY_CACHE_SIZE - 1) { szone->large_entry_cache_oldest++; } else { szone->large_entry_cache_oldest = 0; } } } else { // By trichotomy, large_entry_cache_newest == large_entry_cache_oldest. // That implies best == large_entry_cache_newest == large_entry_cache_oldest // and the ring is now empty. szone->large_entry_cache[best].address = 0; szone->large_entry_cache[best].size = 0; szone->large_entry_cache[best].did_madvise_reusable = FALSE; } if ((szone->num_large_objects_in_use + 1) * 4 > szone->num_large_entries) { // density of hash table too high; grow table // we do that under lock to avoid a race large_entry_t *entries = large_entries_grow_no_lock(szone, &range_to_deallocate); if (entries == NULL) { SZONE_UNLOCK(szone); return NULL; } } large_entry.address = (vm_address_t)addr; large_entry.size = best_size; large_entry.did_madvise_reusable = FALSE; large_entry_insert_no_lock(szone, large_entry); szone->num_large_objects_in_use++; szone->num_bytes_in_large_objects += best_size; if (!was_madvised_reusable) { szone->large_entry_cache_reserve_bytes -= best_size; } szone->large_entry_cache_bytes -= best_size; if (szone->flotsam_enabled && szone->large_entry_cache_bytes < SZONE_FLOTSAM_THRESHOLD_LOW) { szone->flotsam_enabled = FALSE; } SZONE_UNLOCK(szone); if (range_to_deallocate.size) { // we deallocate outside the lock mvm_deallocate_pages((void *)range_to_deallocate.address, range_to_deallocate.size, 0); } if (cleared_requested) { memset(addr, 0, size); } return addr; } else { SZONE_UNLOCK(szone); } } range_to_deallocate.size = 0; range_to_deallocate.address = 0; #endif /* CONFIG_LARGE_CACHE */ addr = mvm_allocate_pages(size, alignment, szone->debug_flags, VM_MEMORY_MALLOC_LARGE); if (addr == NULL) { return NULL; } SZONE_LOCK(szone); if ((szone->num_large_objects_in_use + 1) * 4 > szone->num_large_entries) { // density of hash table too high; grow table // we do that under lock to avoid a race large_entry_t *entries = large_entries_grow_no_lock(szone, &range_to_deallocate); if (entries == NULL) { SZONE_UNLOCK(szone); return NULL; } } large_entry.address = (vm_address_t)addr; large_entry.size = size; large_entry.did_madvise_reusable = FALSE; large_entry_insert_no_lock(szone, large_entry); szone->num_large_objects_in_use++; szone->num_bytes_in_large_objects += size; SZONE_UNLOCK(szone); if (range_to_deallocate.size) { // we deallocate outside the lock mvm_deallocate_pages((void *)range_to_deallocate.address, range_to_deallocate.size, 0); } return addr; } void free_large(szone_t *szone, void *ptr) { // We have established ptr is page-aligned and neither tiny nor small large_entry_t *entry; vm_range_t vm_range_to_deallocate; SZONE_LOCK(szone); entry = large_entry_for_pointer_no_lock(szone, ptr); if (entry) { #if CONFIG_LARGE_CACHE if (entry->size < LARGE_CACHE_SIZE_ENTRY_LIMIT && -1 != madvise((void *)(entry->address), entry->size, MADV_CAN_REUSE)) { // Put the large_entry_t on the death-row cache? int idx = szone->large_entry_cache_newest, stop_idx = szone->large_entry_cache_oldest; large_entry_t this_entry = *entry; // Make a local copy, "entry" is volatile when lock is let go. boolean_t reusable = TRUE; boolean_t should_madvise = szone->large_entry_cache_reserve_bytes + this_entry.size > szone->large_entry_cache_reserve_limit; // Already freed? // [Note that repeated entries in death-row risk vending the same entry subsequently // to two different malloc() calls. By checking here the (illegal) double free // is accommodated, matching the behavior of the previous implementation.] while (1) { // Scan large_entry_cache starting with most recent entry if (szone->large_entry_cache[idx].address == entry->address) { malloc_zone_error(szone->debug_flags, true, "pointer %p being freed already on death-row\n", ptr); SZONE_UNLOCK(szone); return; } if (idx == stop_idx) { // exhausted live ring? break; } if (idx) { idx--; // bump idx down } else { idx = LARGE_ENTRY_CACHE_SIZE - 1; // wrap idx } } SZONE_UNLOCK(szone); if (szone->debug_flags & MALLOC_PURGEABLE) { // Are we a purgable zone? int state = VM_PURGABLE_NONVOLATILE; // restore to default condition if (KERN_SUCCESS != vm_purgable_control(mach_task_self(), this_entry.address, VM_PURGABLE_SET_STATE, &state)) { malloc_report(ASL_LEVEL_ERR, "*** can't vm_purgable_control(..., VM_PURGABLE_SET_STATE) for large freed block at %p\n", (void *)this_entry.address); reusable = FALSE; } } if (szone->large_legacy_reset_mprotect) { // Linked for Leopard? // Accomodate Leopard apps that (illegally) mprotect() their own guard pages on large malloc'd allocations int err = mprotect((void *)(this_entry.address), this_entry.size, PROT_READ | PROT_WRITE); if (err) { malloc_report(ASL_LEVEL_ERR, "*** can't reset protection for large freed block at %p\n", (void *)this_entry.address); reusable = FALSE; } } // madvise(..., MADV_REUSABLE) death-row arrivals if hoarding would exceed large_entry_cache_reserve_limit if (should_madvise) { // Issue madvise to avoid paging out the dirtied free()'d pages in "entry" MAGMALLOC_MADVFREEREGION((void *)szone, (void *)0, (void *)(this_entry.address), (int)this_entry.size); // DTrace USDT Probe // Ok to do this madvise on embedded because we won't call MADV_FREE_REUSABLE on a large // cache block twice without MADV_FREE_REUSE in between. if (-1 == madvise((void *)(this_entry.address), this_entry.size, MADV_FREE_REUSABLE)) { /* -1 return: VM map entry change makes this unfit for reuse. */ #if DEBUG_MADVISE malloc_zone_error(szone->debug_flags, false, "free_large madvise(..., MADV_FREE_REUSABLE) failed for %p, length=%d\n", (void *)this_entry.address, this_entry.size); #endif reusable = FALSE; } } SZONE_LOCK(szone); // Re-acquire "entry" after interval just above where we let go the lock. entry = large_entry_for_pointer_no_lock(szone, ptr); if (NULL == entry) { malloc_zone_error(szone->debug_flags, true, "entry for pointer %p being freed from death-row vanished\n", ptr); SZONE_UNLOCK(szone); return; } // Add "entry" to death-row ring if (reusable) { int idx = szone->large_entry_cache_newest; // Most recently occupied vm_address_t addr; size_t adjsize; if (szone->large_entry_cache_newest == szone->large_entry_cache_oldest && 0 == szone->large_entry_cache[idx].address) { // Ring is empty, idx is good as it stands addr = 0; adjsize = 0; } else { // Extend the queue to the "right" by bumping up large_entry_cache_newest if (idx == LARGE_ENTRY_CACHE_SIZE - 1) { idx = 0; // Wrap index } else { idx++; // Bump index } if (idx == szone->large_entry_cache_oldest) { // Fully occupied // Drop this entry from the cache and deallocate the VM addr = szone->large_entry_cache[idx].address; adjsize = szone->large_entry_cache[idx].size; szone->large_entry_cache_bytes -= adjsize; if (!szone->large_entry_cache[idx].did_madvise_reusable) { szone->large_entry_cache_reserve_bytes -= adjsize; } } else { // Using an unoccupied cache slot addr = 0; adjsize = 0; } } if ((szone->debug_flags & MALLOC_DO_SCRIBBLE)) { memset((void *)(entry->address), should_madvise ? SCRUBBLE_BYTE : SCRABBLE_BYTE, entry->size); } entry->did_madvise_reusable = should_madvise; // Was madvise()'d above? if (!should_madvise) { // Entered on death-row without madvise() => up the hoard total szone->large_entry_cache_reserve_bytes += entry->size; } szone->large_entry_cache_bytes += entry->size; if (!szone->flotsam_enabled && szone->large_entry_cache_bytes > SZONE_FLOTSAM_THRESHOLD_HIGH) { szone->flotsam_enabled = TRUE; } szone->large_entry_cache[idx] = *entry; szone->large_entry_cache_newest = idx; szone->num_large_objects_in_use--; szone->num_bytes_in_large_objects -= entry->size; (void)large_entry_free_no_lock(szone, entry); if (0 == addr) { SZONE_UNLOCK(szone); return; } // Fall through to drop large_entry_cache_oldest from the cache, // and then deallocate its pages. // Trim the queue on the "left" by bumping up large_entry_cache_oldest if (szone->large_entry_cache_oldest == LARGE_ENTRY_CACHE_SIZE - 1) { szone->large_entry_cache_oldest = 0; } else { szone->large_entry_cache_oldest++; } // we deallocate_pages, including guard pages, outside the lock SZONE_UNLOCK(szone); mvm_deallocate_pages((void *)addr, (size_t)adjsize, 0); return; } else { /* fall through to discard an allocation that is not reusable */ } } #endif /* CONFIG_LARGE_CACHE */ szone->num_large_objects_in_use--; szone->num_bytes_in_large_objects -= entry->size; vm_range_to_deallocate = large_entry_free_no_lock(szone, entry); } else { #if DEBUG_MALLOC large_debug_print(szone); #endif malloc_zone_error(szone->debug_flags, true, "pointer %p being freed was not allocated\n", ptr); SZONE_UNLOCK(szone); return; } SZONE_UNLOCK(szone); // we release the lock asap CHECK(szone, __PRETTY_FUNCTION__); // we deallocate_pages, including guard pages, outside the lock if (vm_range_to_deallocate.address) { #if DEBUG_MALLOC // FIXME: large_entry_for_pointer_no_lock() needs the lock held ... if (large_entry_for_pointer_no_lock(szone, (void *)vm_range_to_deallocate.address)) { malloc_report(ASL_LEVEL_ERR, "*** invariant broken: %p still in use num_large_entries=%d\n", vm_range_to_deallocate.address, szone->num_large_entries); large_debug_print(szone); szone_sleep(); } #endif mvm_deallocate_pages((void *)vm_range_to_deallocate.address, (size_t)vm_range_to_deallocate.size, 0); } } void * large_try_shrink_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_good_size) { size_t shrinkage = old_size - new_good_size; if (shrinkage) { SZONE_LOCK(szone); /* contract existing large entry */ large_entry_t *large_entry = large_entry_for_pointer_no_lock(szone, ptr); if (!large_entry) { malloc_zone_error(szone->debug_flags, true, "large entry %p reallocated is not properly in table\n", ptr); SZONE_UNLOCK(szone); return ptr; } large_entry->address = (vm_address_t)ptr; large_entry->size = new_good_size; szone->num_bytes_in_large_objects -= shrinkage; boolean_t guarded = szone->debug_flags & MALLOC_ADD_GUARD_PAGES; SZONE_UNLOCK(szone); // we release the lock asap if (guarded) { // Keep the page above the new end of the allocation as the // postlude guard page. kern_return_t err; err = mprotect((void *)((uintptr_t)ptr + new_good_size), vm_page_quanta_size, 0); if (err) { malloc_report(ASL_LEVEL_ERR, "*** can't mvm_protect(0x0) region for new postlude guard page at %p\n", ptr + new_good_size); } new_good_size += vm_page_quanta_size; shrinkage -= vm_page_quanta_size; } mvm_deallocate_pages((void *)((uintptr_t)ptr + new_good_size), shrinkage, 0); } return ptr; } int large_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size) { vm_address_t addr = (vm_address_t)ptr + old_size; large_entry_t *large_entry; kern_return_t err; SZONE_LOCK(szone); large_entry = large_entry_for_pointer_no_lock(szone, (void *)addr); SZONE_UNLOCK(szone); if (large_entry) { // check if "addr = ptr + old_size" is already spoken for return 0; // large pointer already exists in table - extension is not going to work } new_size = round_page_quanta(new_size); /* * Ask for allocation at a specific address, and mark as realloc * to request coalescing with previous realloc'ed extensions. */ err = vm_allocate(mach_task_self(), &addr, new_size - old_size, VM_MAKE_TAG(VM_MEMORY_REALLOC)); if (err != KERN_SUCCESS) { return 0; } SZONE_LOCK(szone); /* extend existing large entry */ large_entry = large_entry_for_pointer_no_lock(szone, ptr); if (!large_entry) { malloc_zone_error(szone->debug_flags, true, "large entry %p reallocated is not properly in table\n", ptr); SZONE_UNLOCK(szone); return 0; // Bail, leaking "addr" } large_entry->address = (vm_address_t)ptr; large_entry->size = new_size; szone->num_bytes_in_large_objects += new_size - old_size; SZONE_UNLOCK(szone); // we release the lock asap return 1; } boolean_t large_claimed_address(szone_t *szone, void *ptr) { SZONE_LOCK(szone); boolean_t result = large_entry_containing_pointer_no_lock(szone, (void *)trunc_page((uintptr_t)ptr)) != NULL; SZONE_UNLOCK(szone); return result; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_lite.c ================================================ /* * Copyright (c) 2016, Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" typedef uint64_t malloc_stack_id; uint64_t max_lite_mallocs = 0; static malloc_stack_id get_stack_id_from_ptr(void *ptr, size_t ptr_size) { void *idptr = ptr + ptr_size - sizeof(malloc_stack_id); return * (malloc_stack_id *) idptr; } static void set_stack_id_in_ptr(void *ptr, size_t requested_size, size_t ptr_size, malloc_stack_id stack_id) { void *idptr = ptr + ptr_size - sizeof(malloc_stack_id); * (malloc_stack_id *) idptr = stack_id; void *padding = ptr + requested_size; bzero(padding, ptr_size - requested_size - sizeof(malloc_stack_id)); } static void add_stack_to_ptr(szone_t *szone, size_t requested_size, void *ptr) { // enter the stack into the unique table vm_address_t self_thread = (vm_address_t)_os_tsd_get_direct(__TSD_THREAD_SELF); size_t ptr_size = szone_size(szone, ptr); __malloc_lock_stack_logging(); // if stack logging was turned off behind our backs if (!is_stack_logging_lite_enabled()) { __malloc_unlock_stack_logging(); return; } malloc_stack_id stack_id = __enter_stack_into_table_while_locked(self_thread, 0, false, ptr_size); __malloc_unlock_stack_logging(); if (stack_id == __invalid_stack_id) { malloc_report(ASL_LEVEL_ERR, "bad stack id. turning off stack logging\n"); turn_off_stack_logging(); } else { set_stack_id_in_ptr(ptr, requested_size, ptr_size, stack_id); } } static boolean_t stack_logging_lite_enabled = false; boolean_t is_stack_logging_lite_enabled(void) { return stack_logging_lite_enabled; } void enable_stack_logging_lite() { stack_logging_lite_enabled = true; } void disable_stack_logging_lite() { stack_logging_lite_enabled = false; } static void * stack_logging_lite_malloc(malloc_zone_t *zone, size_t size) { szone_t *szone = (szone_t *) zone; void* p = NULL; static uint64_t num_mallocs = 0; if (stack_logging_lite_enabled) { __prepare_to_log_stacks(true); // do this again in case stack logging was postponed p = szone_malloc(szone, size + sizeof(malloc_stack_id)); if (p) { add_stack_to_ptr(szone, size, p); } // this value doesn't need to be exact, so no need for atomic operations num_mallocs++; if (max_lite_mallocs > 0 && num_mallocs > max_lite_mallocs) { malloc_report(ASL_LEVEL_ERR, "lite allocations exceeded limit. disabling lite mode\n"); disable_stack_logging_lite(); } } else { p = szone->helper_zone->basic_zone.malloc((malloc_zone_t *) szone->helper_zone, size); } return p; } static void * stack_logging_lite_calloc(struct _malloc_zone_t *zone, size_t num_items, size_t size) { szone_t *szone = (szone_t *) zone; void *p = NULL; if (stack_logging_lite_enabled) { size_t total_bytes; if (calloc_get_size(num_items, size, sizeof(malloc_stack_id), &total_bytes)) { return NULL; } p = szone_malloc_should_clear(szone, total_bytes, 1); if (p) { add_stack_to_ptr(szone, total_bytes - sizeof(malloc_stack_id), p); } } else { p = szone->helper_zone->basic_zone.calloc((malloc_zone_t *) szone->helper_zone, num_items, size); } return p; } static void * stack_logging_lite_valloc(malloc_zone_t *zone, size_t size) { szone_t *szone = (szone_t *) zone; void *p = NULL; if (stack_logging_lite_enabled) { p = szone_valloc(szone, size + sizeof(malloc_stack_id)); if (p) { add_stack_to_ptr(szone, size, p); } } else { p = szone->helper_zone->basic_zone.valloc((malloc_zone_t *) szone->helper_zone, size); } return p; } static void stack_logging_lite_free(malloc_zone_t *zone, void *ptr) { szone_t *szone = (szone_t *) zone; size_t size = szone_size(szone, ptr); // see if it's in our zone if (size) { malloc_stack_id stack_id = get_stack_id_from_ptr(ptr, size); __decrement_table_slot_refcount(stack_id, size); szone_free(szone, ptr); } else { szone->helper_zone->basic_zone.free((malloc_zone_t *) szone->helper_zone, ptr); } } static void stack_logging_lite_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size) { // because we've messed with the size, don't try to be fancy and just call free stack_logging_lite_free(zone, ptr); } // Three paths: // 1. do a szone_realloc with padding and add stack id // 2. do a szone_realloc on the helper zone // 3. do a manual free / malloc static void * stack_logging_lite_realloc(malloc_zone_t *zone, void *ptr, size_t new_size) { szone_t *szone = (szone_t *) zone; void *new_ptr = NULL; size_t old_size = szone_size(szone, ptr); // if we own the ptr and lite enabled do our thing if (old_size && stack_logging_lite_enabled) { // need to get the old stackid and decrement malloc_stack_id stack_id = get_stack_id_from_ptr(ptr, old_size); new_ptr = szone_realloc(szone, ptr, new_size + sizeof(malloc_stack_id)); if (new_ptr) { __decrement_table_slot_refcount(stack_id, old_size); add_stack_to_ptr(szone, new_size, new_ptr); } } else if (!old_size && !stack_logging_lite_enabled) { // we don't own the pointer and lite mode is disabled, so just pass the realloc on to the helper zone return szone->helper_zone->basic_zone.realloc((malloc_zone_t *) szone->helper_zone, ptr, new_size); } else { // otherwise perform the realloc by hand: // 1. malloc new ptr // 2. copy existing data to new ptr // 3. free old ptr // this will add the stack id if needed new_ptr = stack_logging_lite_malloc(zone, new_size); if (new_ptr) { size_t old_size = malloc_size(ptr); size_t new_size = malloc_size(new_ptr); // copy as much old data as possible size_t copy_size = MIN(old_size, new_size); memcpy(new_ptr, ptr, copy_size); } stack_logging_lite_free(zone, ptr); } return new_ptr; } static void * MALLOC_NOINLINE stack_logging_lite_memalign(malloc_zone_t *zone, size_t alignment, size_t size) { szone_t *szone = (szone_t *) zone; void *ptr = NULL; if (stack_logging_lite_enabled) { ptr = szone_memalign(szone, alignment, size + sizeof(malloc_stack_id)); if (ptr) { add_stack_to_ptr(szone, size, ptr); } } else { ptr = szone->helper_zone->basic_zone.memalign((malloc_zone_t *) szone->helper_zone, alignment, size); } return ptr; } static size_t stack_logging_lite_size(malloc_zone_t *zone, const void *ptr) { szone_t *szone = (szone_t *) zone; size_t size = szone_size(szone, ptr); if (size) { size -= sizeof(malloc_stack_id); } else { size = szone->helper_zone->basic_zone.size((malloc_zone_t *) szone->helper_zone, ptr); } return size; } unsigned stack_logging_lite_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count) { unsigned num_allocated = 0; if (stack_logging_lite_enabled) { num_allocated = szone_batch_malloc(szone, size + sizeof(malloc_stack_id), results, count); for (unsigned i = 0; i < num_allocated; i++) { add_stack_to_ptr(szone, size, results[i]); } } else { num_allocated = szone->helper_zone->basic_zone.batch_malloc((malloc_zone_t *) szone->helper_zone, size, results, count); } return num_allocated; } void stack_logging_lite_batch_free(szone_t *szone, void **to_be_freed, unsigned count) { for (unsigned i = 0; i < count; i++) { void *p = to_be_freed[i]; if (p) { size_t size = szone_size(szone, p); // see if it's in our zone if (size) { malloc_stack_id stack_id = get_stack_id_from_ptr(p, size); __decrement_table_slot_refcount(stack_id, size); szone_free(szone, p); } else { szone->helper_zone->basic_zone.free((malloc_zone_t *) szone->helper_zone, p); } } } } malloc_zone_t * create_stack_logging_lite_zone(size_t initial_size, malloc_zone_t *helper_zone, unsigned debug_flags) { szone_t* zone = create_scalable_szone(initial_size, debug_flags); // unprotect function pointers mprotect(zone, sizeof(zone->basic_zone), PROT_READ | PROT_WRITE); // set the function pointers zone->basic_zone.malloc = stack_logging_lite_malloc; zone->basic_zone.calloc = stack_logging_lite_calloc; zone->basic_zone.valloc = stack_logging_lite_valloc; zone->basic_zone.realloc = stack_logging_lite_realloc; zone->basic_zone.batch_malloc = (void *) stack_logging_lite_batch_malloc; zone->basic_zone.batch_free = (void *) stack_logging_lite_batch_free; zone->basic_zone.memalign = stack_logging_lite_memalign; zone->basic_zone.free = stack_logging_lite_free; zone->basic_zone.free_definite_size = stack_logging_lite_free_definite_size; zone->basic_zone.size = stack_logging_lite_size; // protect function pointers mprotect(zone, sizeof(zone->basic_zone), PROT_READ); // set helper zone zone->helper_zone = (szone_t*) helper_zone; return (malloc_zone_t*) zone; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_malloc.c ================================================ /* * Copyright (c) 1999, 2006, 2008 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* Author: Bertrand Serlet, August 1999 */ /* * Multithread enhancements for "tiny" allocations introduced February 2008. * These are in the spirit of "Hoard". See: * Berger, E.D.; McKinley, K.S.; Blumofe, R.D.; Wilson, P.R. (2000). * "Hoard: a scalable memory allocator for multithreaded applications". * ACM SIGPLAN Notices 35 (11): 117-128. Berger2000. * * Retrieved on 2008-02-22. */ #include "internal.h" #if DEBUG_MALLOC #define LOG(szone, ptr) (szone->log_address && (((uintptr_t)szone->log_address == -1) || (szone->log_address == (void *)(ptr)))) #else #define LOG(szone, ptr) 0 #endif // Maximum number of magazines, set from the number of logical CPUS and // possibly limited by the MallocMaxMagazines environment variable. int max_magazines; // Number of regions to retain in a recirc depot. #if CONFIG_RECIRC_DEPOT int recirc_retained_regions = DEFAULT_RECIRC_RETAINED_REGIONS; #endif // CONFIG_RECIRC_DEPOT /********************* Zone call backs ************************/ /* * Mark these MALLOC_NOINLINE to avoid bloating the purgeable zone call backs */ void szone_free(szone_t *szone, void *ptr) { region_t tiny_region; region_t small_region; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in szone_free with %p\n", ptr); } #endif if (!ptr) { return; } /* * Try to free to a tiny region. */ if ((uintptr_t)ptr & (TINY_QUANTUM - 1)) { malloc_zone_error(szone->debug_flags, true, "Non-aligned pointer %p being freed\n", ptr); return; } if ((tiny_region = tiny_region_for_ptr_no_lock(&szone->tiny_rack, ptr)) != NULL) { if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS) { malloc_zone_error(szone->debug_flags, true, "Pointer %p to metadata being freed\n", ptr); return; } free_tiny(&szone->tiny_rack, ptr, tiny_region, 0); return; } /* * Try to free to a small region. */ if ((uintptr_t)ptr & (SMALL_QUANTUM - 1)) { malloc_zone_error(szone->debug_flags, true, "Non-aligned pointer %p being freed (2)\n", ptr); return; } if ((small_region = small_region_for_ptr_no_lock(&szone->small_rack, ptr)) != NULL) { if (SMALL_META_INDEX_FOR_PTR(ptr) >= NUM_SMALL_BLOCKS) { malloc_zone_error(szone->debug_flags, true, "Pointer %p to metadata being freed (2)\n", ptr); return; } free_small(&szone->small_rack, ptr, small_region, 0); return; } /* check that it's a legal large allocation */ if ((uintptr_t)ptr & (vm_page_quanta_size - 1)) { malloc_zone_error(szone->debug_flags, true, "non-page-aligned, non-allocated pointer %p being freed\n", ptr); return; } free_large(szone, ptr); } void szone_free_definite_size(szone_t *szone, void *ptr, size_t size) { #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in szone_free_definite_size with %p\n", ptr); } if (0 == size) { malloc_zone_error(szone->debug_flags, true, "pointer %p of size zero being freed\n", ptr); return; } #endif if (!ptr) { return; } /* * Try to free to a tiny region. */ if ((uintptr_t)ptr & (TINY_QUANTUM - 1)) { malloc_zone_error(szone->debug_flags, true, "Non-aligned pointer %p being freed\n", ptr); return; } if (size <= SMALL_THRESHOLD) { if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS) { malloc_zone_error(szone->debug_flags, true, "Pointer %p to metadata being freed\n", ptr); return; } free_tiny(&szone->tiny_rack, ptr, TINY_REGION_FOR_PTR(ptr), size); return; } /* * Try to free to a small region. */ if ((uintptr_t)ptr & (SMALL_QUANTUM - 1)) { malloc_zone_error(szone->debug_flags, true, "Non-aligned pointer %p being freed (2)\n", ptr); return; } if (size <= szone->large_threshold) { if (SMALL_META_INDEX_FOR_PTR(ptr) >= NUM_SMALL_BLOCKS) { malloc_zone_error(szone->debug_flags, true, "Pointer %p to metadata being freed (2)\n", ptr); return; } free_small(&szone->small_rack, ptr, SMALL_REGION_FOR_PTR(ptr), size); return; } /* check that it's a legal large allocation */ if ((uintptr_t)ptr & (vm_page_quanta_size - 1)) { malloc_zone_error(szone->debug_flags, true, "non-page-aligned, non-allocated pointer %p being freed\n", ptr); return; } free_large(szone, ptr); } MALLOC_NOINLINE void * szone_malloc_should_clear(szone_t *szone, size_t size, boolean_t cleared_requested) { void *ptr; msize_t msize; if (size <= SMALL_THRESHOLD) { // tiny size: <=1008 bytes (64-bit), <=496 bytes (32-bit) // think tiny msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1); if (!msize) { msize = 1; } ptr = tiny_malloc_should_clear(&szone->tiny_rack, msize, cleared_requested); } else if (size <= szone->large_threshold) { // small size: <=15k (iOS), <=64k (large iOS), <=128k (macOS) // think small msize = SMALL_MSIZE_FOR_BYTES(size + SMALL_QUANTUM - 1); if (!msize) { msize = 1; } ptr = small_malloc_should_clear(&szone->small_rack, msize, cleared_requested); } else { // large: all other allocations size_t num_kernel_pages = round_page_quanta(size) >> vm_page_quanta_shift; if (num_kernel_pages == 0) { /* Overflowed */ ptr = 0; } else { ptr = large_malloc(szone, num_kernel_pages, 0, cleared_requested); } } #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "szone_malloc returned %p\n", ptr); } #endif /* * If requested, scribble on allocated memory. */ if ((szone->debug_flags & MALLOC_DO_SCRIBBLE) && ptr && !cleared_requested && size) { memset(ptr, SCRIBBLE_BYTE, szone_size(szone, ptr)); } return ptr; } void * szone_malloc(szone_t *szone, size_t size) { return szone_malloc_should_clear(szone, size, 0); } void * szone_calloc(szone_t *szone, size_t num_items, size_t size) { size_t total_bytes; if (calloc_get_size(num_items, size, 0, &total_bytes)) { return NULL; } return szone_malloc_should_clear(szone, total_bytes, 1); } void * szone_valloc(szone_t *szone, size_t size) { void *ptr; if (size <= szone->large_threshold) { ptr = szone_memalign(szone, vm_page_quanta_size, size); } else { size_t num_kernel_pages; num_kernel_pages = round_page_quanta(size) >> vm_page_quanta_shift; ptr = large_malloc(szone, num_kernel_pages, 0, 0); } #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "szone_valloc returned %p\n", ptr); } #endif return ptr; } /* Isolate PIC-base load here. */ size_t szone_size_try_large(szone_t *szone, const void *ptr) { size_t size = 0; large_entry_t *entry; SZONE_LOCK(szone); entry = large_entry_for_pointer_no_lock(szone, ptr); if (entry) { size = entry->size; } SZONE_UNLOCK(szone); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "szone_size for %p returned %d\n", ptr, (unsigned)size); } #endif return size; } size_t szone_size(szone_t *szone, const void *ptr) { size_t sz = 0; if (!ptr) { return 0; } #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in szone_size for %p (szone=%p)\n", ptr, szone); } #endif /* * Look for it in a tiny region. */ if ((uintptr_t)ptr & (TINY_QUANTUM - 1)) { return 0; } sz = tiny_size(&szone->tiny_rack, ptr); if (sz) { return sz; } /* * Look for it in a small region. */ if ((uintptr_t)ptr & (SMALL_QUANTUM - 1)) { return 0; } sz = small_size(&szone->small_rack, ptr); if (sz) { return sz; } /* * If not page-aligned, it cannot have come from a large allocation. */ if ((uintptr_t)ptr & (vm_page_quanta_size - 1)) { return 0; } /* * Look for it in a large entry. */ return szone_size_try_large(szone, ptr); } void * szone_realloc(szone_t *szone, void *ptr, size_t new_size) { size_t old_size, new_good_size, valid_size; void *new_ptr; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in szone_realloc for %p, %d\n", ptr, (unsigned)new_size); } #endif if (NULL == ptr) { // If ptr is a null pointer, realloc() shall be equivalent to malloc() for the specified size. return szone_malloc(szone, new_size); } else if (0 == new_size) { // If size is 0 and ptr is not a null pointer, the object pointed to is freed. szone_free(szone, ptr); // If size is 0, either a null pointer or a unique pointer that can be successfully passed // to free() shall be returned. return szone_malloc(szone, 1); } old_size = szone_size(szone, ptr); if (!old_size) { malloc_zone_error(szone->debug_flags, true, "pointer %p being reallocated was not allocated\n", ptr); return NULL; } new_good_size = szone_good_size(szone, new_size); if (new_good_size == old_size) { // Existing allocation is best fit evar? return ptr; } /* * If the new size suits the tiny allocator and the pointer being resized * belongs to a tiny region, try to reallocate in-place. */ if (new_good_size <= SMALL_THRESHOLD) { if (old_size <= SMALL_THRESHOLD) { if (new_good_size <= (old_size >> 1)) { /* * Serious shrinkage (more than half). free() the excess. */ return tiny_try_shrink_in_place(&szone->tiny_rack, ptr, old_size, new_good_size); } else if (new_good_size <= old_size) { /* * new_good_size smaller than old_size but not by much (less than half). * Avoid thrashing at the expense of some wasted storage. */ if (szone->debug_flags & MALLOC_DO_SCRIBBLE) { memset(ptr + new_size, SCRIBBLE_BYTE, old_size - new_size); } return ptr; } else if (tiny_try_realloc_in_place(&szone->tiny_rack, ptr, old_size, new_good_size)) { // try to grow the allocation if (szone->debug_flags & MALLOC_DO_SCRIBBLE) { memset(ptr + old_size, SCRIBBLE_BYTE, new_good_size - old_size); } return ptr; } } /* * Else if the new size suits the small allocator and the pointer being resized * belongs to a small region, and we're not protecting the small allocations * try to reallocate in-place. */ } else if (new_good_size <= szone->large_threshold) { if (SMALL_THRESHOLD < old_size && old_size <= szone->large_threshold) { if (new_good_size <= (old_size >> 1)) { return small_try_shrink_in_place(&szone->small_rack, ptr, old_size, new_good_size); } else if (new_good_size <= old_size) { if (szone->debug_flags & MALLOC_DO_SCRIBBLE) { memset(ptr + new_size, SCRIBBLE_BYTE, old_size - new_size); } return ptr; } else if (small_try_realloc_in_place(&szone->small_rack, ptr, old_size, new_good_size)) { if (szone->debug_flags & MALLOC_DO_SCRIBBLE) { memset(ptr + old_size, SCRIBBLE_BYTE, new_good_size - old_size); } return ptr; } } /* * Else if the allocation's a large allocation, try to reallocate in-place there. */ } else if (!(szone->debug_flags & MALLOC_PURGEABLE) && // purgeable needs fresh allocation (old_size > szone->large_threshold) && (new_good_size > szone->large_threshold)) { if (new_good_size <= (old_size >> 1)) { return large_try_shrink_in_place(szone, ptr, old_size, new_good_size); } else if (new_good_size <= old_size) { if (szone->debug_flags & MALLOC_DO_SCRIBBLE) { memset(ptr + new_size, SCRIBBLE_BYTE, old_size - new_size); } return ptr; } else if (large_try_realloc_in_place(szone, ptr, old_size, new_good_size)) { if (szone->debug_flags & MALLOC_DO_SCRIBBLE) { memset(ptr + old_size, SCRIBBLE_BYTE, new_good_size - old_size); } return ptr; } } /* * Can't reallocate in place for whatever reason; allocate a new buffer and copy. */ if (new_good_size <= (old_size >> 1)) { /* Serious shrinkage (more than half). FALL THROUGH to alloc/copy/free. */ } else if (new_good_size <= old_size) { if (szone->debug_flags & MALLOC_DO_SCRIBBLE) { memset(ptr + new_size, SCRIBBLE_BYTE, old_size - new_size); } return ptr; } new_ptr = szone_malloc(szone, new_size); if (new_ptr == NULL) { return NULL; } /* * If the allocation's large enough, try to copy using VM. If that fails, or * if it's too small, just copy by hand. */ valid_size = MIN(old_size, new_size); if ((valid_size <= szone->vm_copy_threshold) || vm_copy(mach_task_self(), (vm_address_t)ptr, valid_size, (vm_address_t)new_ptr)) { memcpy(new_ptr, ptr, valid_size); } szone_free(szone, ptr); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "szone_realloc returned %p for %d\n", new_ptr, (unsigned)new_size); } #endif return new_ptr; } void * szone_memalign(szone_t *szone, size_t alignment, size_t size) { if (size == 0) { size = 1; // Ensures we'll return an aligned free()-able pointer } if ((size + alignment) < size) { // size_t arithmetic wrapped! return NULL; } // alignment is gauranteed a power of 2 at least as large as sizeof(void *), hence non-zero. // Since size + alignment didn't wrap, 0 <= size + alignment - 1 < size + alignment size_t span = size + alignment - 1; if (alignment <= TINY_QUANTUM) { return szone_malloc(szone, size); // Trivially satisfied by tiny, small, or large } if (span <= SMALL_THRESHOLD) { return tiny_memalign(szone, alignment, size, span); } if (SMALL_THRESHOLD < size && alignment <= SMALL_QUANTUM) { return szone_malloc(szone, size); // Trivially satisfied by small or large } if (size <= SMALL_THRESHOLD) { // ensure block allocated by small does not have a tiny-possible size size = SMALL_THRESHOLD + TINY_QUANTUM; span = size + alignment - 1; } if (span <= szone->large_threshold) { return small_memalign(szone, alignment, size, span); } if (szone->large_threshold < size && alignment <= vm_page_quanta_size) { return szone_malloc(szone, size); // Trivially satisfied by large } // ensure block allocated by large does not have a small-possible size size_t num_kernel_pages = round_page_quanta(MAX(szone->large_threshold + 1, size)) >> vm_page_quanta_shift; if (num_kernel_pages == 0) { /* Overflowed */ return NULL; } else { return large_malloc(szone, num_kernel_pages, MAX(vm_page_quanta_shift, __builtin_ctz((unsigned)alignment)), 0); } /* NOTREACHED */ } // Given a size, returns the number of pointers allocated capable of holding // that size, up to the limit specified by the 'count' argument. These pointers // are stored in the 'results' array, which must be allocated by the caller. // May return zero, since this function is only a best attempt at allocating // the pointers. Clients should be prepared to call malloc for any additional // blocks they need. unsigned szone_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count) { // only bother implementing this for tiny if (size <= SMALL_THRESHOLD) { return tiny_batch_malloc(szone, size, results, count); } return 0; } void szone_batch_free(szone_t *szone, void **to_be_freed, unsigned count) { // frees all the pointers in to_be_freed // note that to_be_freed may be overwritten during the process if (!count) { return; } CHECK(szone, __PRETTY_FUNCTION__); // We only support batch malloc in tiny. Let it free all of the pointers // that belong to it, then let the standard free deal with the rest. tiny_batch_free(szone, to_be_freed, count); CHECK(szone, __PRETTY_FUNCTION__); while (count--) { void *ptr = to_be_freed[count]; if (ptr) { szone_free(szone, ptr); } } } // FIXME: Suppose one of the locks is held? static void szone_destroy(szone_t *szone) { size_t index; large_entry_t *large; vm_range_t range_to_deallocate; #if CONFIG_LARGE_CACHE SZONE_LOCK(szone); /* disable any memory pressure responder */ szone->flotsam_enabled = FALSE; // stack allocated copy of the death-row cache int idx = szone->large_entry_cache_oldest, idx_max = szone->large_entry_cache_newest; large_entry_t local_entry_cache[LARGE_ENTRY_CACHE_SIZE]; memcpy((void *)local_entry_cache, (void *)szone->large_entry_cache, sizeof(local_entry_cache)); szone->large_entry_cache_oldest = szone->large_entry_cache_newest = 0; szone->large_entry_cache[0].address = 0x0; szone->large_entry_cache[0].size = 0; szone->large_entry_cache_bytes = 0; szone->large_entry_cache_reserve_bytes = 0; SZONE_UNLOCK(szone); // deallocate the death-row cache outside the zone lock while (idx != idx_max) { mvm_deallocate_pages((void *)local_entry_cache[idx].address, local_entry_cache[idx].size, 0); if (++idx == LARGE_ENTRY_CACHE_SIZE) { idx = 0; } } if (0 != local_entry_cache[idx].address && 0 != local_entry_cache[idx].size) { mvm_deallocate_pages((void *)local_entry_cache[idx].address, local_entry_cache[idx].size, 0); } #endif /* destroy large entries */ index = szone->num_large_entries; while (index--) { large = szone->large_entries + index; if (large->address) { // we deallocate_pages, including guard pages mvm_deallocate_pages((void *)(large->address), large->size, szone->debug_flags); } } large_entries_free_no_lock(szone, szone->large_entries, szone->num_large_entries, &range_to_deallocate); if (range_to_deallocate.size) { mvm_deallocate_pages((void *)range_to_deallocate.address, (size_t)range_to_deallocate.size, 0); } /* destroy allocator regions */ rack_destroy_regions(&szone->tiny_rack, TINY_REGION_SIZE); rack_destroy_regions(&szone->small_rack, SMALL_REGION_SIZE); /* destroy rack region hash rings and racks themselves */ rack_destroy(&szone->tiny_rack); rack_destroy(&szone->small_rack); mvm_deallocate_pages((void *)szone, SZONE_PAGED_SIZE, 0); } size_t szone_good_size(szone_t *szone, size_t size) { msize_t msize; // Find a good size for this tiny allocation. if (size <= SMALL_THRESHOLD) { msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1); if (!msize) { msize = 1; } return TINY_BYTES_FOR_MSIZE(msize); } // Find a good size for this small allocation. if (size <= szone->large_threshold) { msize = SMALL_MSIZE_FOR_BYTES(size + SMALL_QUANTUM - 1); if (!msize) { msize = 1; } return SMALL_BYTES_FOR_MSIZE(msize); } // Check for integer overflow on the size, since unlike the two cases above, // there is no upper bound on allocation size at this point. if (size > round_page_quanta(size)) { return (size_t)(-1LL); } #if DEBUG_MALLOC // It is not acceptable to see a size of zero here, since that means we // failed to catch a request for zero bytes in the tiny check, or the size // overflowed to zero during some arithmetic. if (size == 0) { malloc_report(ASL_LEVEL_INFO, "szone_good_size() invariant broken %y\n", size); } #endif return round_page_quanta(size); } boolean_t szone_claimed_address(szone_t *szone, void *ptr) { return tiny_claimed_address(&szone->tiny_rack, ptr) || small_claimed_address(&szone->small_rack, ptr) || large_claimed_address(szone, ptr); } unsigned szone_check_counter = 0; unsigned szone_check_start = 0; unsigned szone_check_modulo = 1; static MALLOC_NOINLINE boolean_t szone_check_all(szone_t *szone, const char *function) { size_t index; /* check tiny regions - chould check region count */ for (index = 0; index < szone->tiny_rack.region_generation->num_regions_allocated; ++index) { region_t tiny = szone->tiny_rack.region_generation->hashed_regions[index]; if (HASHRING_REGION_DEALLOCATED == tiny) { continue; } if (tiny) { magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone->tiny_rack.magazines, REGION_TRAILER_FOR_TINY_REGION(tiny), MAGAZINE_INDEX_FOR_TINY_REGION(tiny)); if (!tiny_check_region(&szone->tiny_rack, tiny, index, szone_check_counter)) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); szone->debug_flags &= ~CHECK_REGIONS; return 0; } SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); } } /* check tiny free lists */ for (index = 0; index < NUM_TINY_SLOTS; ++index) { if (!tiny_free_list_check(&szone->tiny_rack, (grain_t)index, szone_check_counter)) { szone->debug_flags &= ~CHECK_REGIONS; return 0; } } /* check small regions - could check region count */ for (index = 0; index < szone->small_rack.region_generation->num_regions_allocated; ++index) { region_t small = szone->small_rack.region_generation->hashed_regions[index]; if (HASHRING_REGION_DEALLOCATED == small) { continue; } if (small) { magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(szone->small_rack.magazines, REGION_TRAILER_FOR_SMALL_REGION(small), MAGAZINE_INDEX_FOR_SMALL_REGION(small)); if (!small_check_region(&szone->small_rack, small, index, szone_check_counter)) { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); szone->debug_flags &= ~CHECK_REGIONS; return 0; } SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); } } /* check small free lists */ for (index = 0; index < SMALL_FREE_SLOT_COUNT(&szone->small_rack); ++index) { if (!small_free_list_check(&szone->small_rack, (grain_t)index, szone_check_counter)) { szone->debug_flags &= ~CHECK_REGIONS; return 0; } } return 1; } static boolean_t szone_check(szone_t *szone) { if ((++szone_check_counter % 10000) == 0) { malloc_report(ASL_LEVEL_NOTICE, "at szone_check counter=%d\n", szone_check_counter); } if (szone_check_counter < szone_check_start) { return 1; } if (szone_check_counter % szone_check_modulo) { return 1; } return szone_check_all(szone, ""); } static kern_return_t szone_ptr_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder) { szone_t *szone; kern_return_t err; if (!reader) { reader = _szone_default_reader; } err = reader(task, zone_address, sizeof(szone_t), (void **)&szone); if (err) { return err; } err = tiny_in_use_enumerator(task, context, type_mask, szone, reader, recorder); if (err) { return err; } err = small_in_use_enumerator(task, context, type_mask, szone, reader, recorder); if (err) { return err; } err = large_in_use_enumerator( task, context, type_mask, (vm_address_t)szone->large_entries, szone->num_large_entries, reader, recorder); return err; } // Following method is deprecated: use scalable_zone_statistics instead void scalable_zone_info(malloc_zone_t *zone, unsigned *info_to_fill, unsigned count) { szone_t *szone = (void *)zone; unsigned info[13]; // We do not lock to facilitate debug size_t s = 0; unsigned t = 0; size_t u = 0; mag_index_t mag_index; for (mag_index = -1; mag_index < szone->tiny_rack.num_magazines; mag_index++) { s += szone->tiny_rack.magazines[mag_index].mag_bytes_free_at_start; s += szone->tiny_rack.magazines[mag_index].mag_bytes_free_at_end; t += szone->tiny_rack.magazines[mag_index].mag_num_objects; u += szone->tiny_rack.magazines[mag_index].mag_num_bytes_in_objects; } info[4] = (unsigned)t; info[5] = (unsigned)u; for (t = 0, u = 0, mag_index = -1; mag_index < szone->small_rack.num_magazines; mag_index++) { s += szone->small_rack.magazines[mag_index].mag_bytes_free_at_start; s += szone->small_rack.magazines[mag_index].mag_bytes_free_at_end; t += szone->small_rack.magazines[mag_index].mag_num_objects; u += szone->small_rack.magazines[mag_index].mag_num_bytes_in_objects; } info[6] = (unsigned)t; info[7] = (unsigned)u; info[8] = (unsigned)szone->num_large_objects_in_use; info[9] = (unsigned)szone->num_bytes_in_large_objects; info[10] = 0; // DEPRECATED szone->num_huge_entries; info[11] = 0; // DEPRECATED szone->num_bytes_in_huge_objects; info[12] = szone->debug_flags; info[0] = info[4] + info[6] + info[8] + info[10]; info[1] = info[5] + info[7] + info[9] + info[11]; info[3] = (unsigned)(szone->tiny_rack.num_regions - szone->tiny_rack.num_regions_dealloc) * TINY_REGION_SIZE + (unsigned)(szone->small_rack.num_regions - szone->small_rack.num_regions_dealloc) * SMALL_REGION_SIZE + info[9] + info[11]; info[2] = info[3] - (unsigned)s; memcpy(info_to_fill, info, sizeof(unsigned) * count); } // FIXME: consistent picture requires locking! static MALLOC_NOINLINE void szone_print(szone_t *szone, boolean_t verbose) { unsigned info[13]; size_t index; region_t region; scalable_zone_info((void *)szone, info, 13); malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Scalable zone %p: inUse=%u(%y) touched=%y allocated=%y flags=%d\n", szone, info[0], info[1], info[2], info[3], info[12]); malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "\ttiny=%u(%y) small=%u(%y) large=%u(%y) huge=%u(%y)\n", info[4], info[5], info[6], info[7], info[8], info[9], info[10], info[11]); // tiny malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%lu tiny regions:\n", szone->tiny_rack.num_regions); if (szone->tiny_rack.num_regions_dealloc) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "[%lu tiny regions have been vm_deallocate'd]\n", szone->tiny_rack.num_regions_dealloc); } for (index = 0; index < szone->tiny_rack.region_generation->num_regions_allocated; ++index) { region = szone->tiny_rack.region_generation->hashed_regions[index]; if (HASHRING_OPEN_ENTRY != region && HASHRING_REGION_DEALLOCATED != region) { mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(region); print_tiny_region(verbose, region, (region == szone->tiny_rack.magazines[mag_index].mag_last_region) ? szone->tiny_rack.magazines[mag_index].mag_bytes_free_at_start : 0, (region == szone->tiny_rack.magazines[mag_index].mag_last_region) ? szone->tiny_rack.magazines[mag_index].mag_bytes_free_at_end : 0); } } if (verbose) { print_tiny_free_list(&szone->tiny_rack); } // small malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%lu small regions:\n", szone->small_rack.num_regions); if (szone->small_rack.num_regions_dealloc) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "[%lu small regions have been vm_deallocate'd]\n", szone->small_rack.num_regions_dealloc); } for (index = 0; index < szone->small_rack.region_generation->num_regions_allocated; ++index) { region = szone->small_rack.region_generation->hashed_regions[index]; if (HASHRING_OPEN_ENTRY != region && HASHRING_REGION_DEALLOCATED != region) { mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(region); print_small_region(szone, verbose, region, (region == szone->small_rack.magazines[mag_index].mag_last_region) ? szone->small_rack.magazines[mag_index].mag_bytes_free_at_start : 0, (region == szone->small_rack.magazines[mag_index].mag_last_region) ? szone->small_rack.magazines[mag_index].mag_bytes_free_at_end : 0); } } if (verbose) { print_small_free_list(&szone->small_rack); } } static void szone_log(malloc_zone_t *zone, void *log_address) { szone_t *szone = (szone_t *)zone; szone->log_address = log_address; } // // When forcing the lock on the entire zone, make sure we are out of the critical section in each magazine static MALLOC_INLINE void szone_force_lock_magazine(szone_t *szone, magazine_t *mag) { while (1) { SZONE_MAGAZINE_PTR_LOCK(mag); if (!mag->alloc_underway) { return; } SZONE_MAGAZINE_PTR_UNLOCK(mag); yield(); } } static void szone_force_lock(szone_t *szone) { mag_index_t i; for (i = 0; i < szone->tiny_rack.num_magazines; ++i) { szone_force_lock_magazine(szone, &szone->tiny_rack.magazines[i]); } szone_force_lock_magazine(szone, &szone->tiny_rack.magazines[DEPOT_MAGAZINE_INDEX]); for (i = 0; i < szone->small_rack.num_magazines; ++i) { szone_force_lock_magazine(szone, &szone->small_rack.magazines[i]); } szone_force_lock_magazine(szone, &szone->small_rack.magazines[DEPOT_MAGAZINE_INDEX]); SZONE_LOCK(szone); } static void szone_force_unlock(szone_t *szone) { mag_index_t i; SZONE_UNLOCK(szone); for (i = -1; i < szone->small_rack.num_magazines; ++i) { SZONE_MAGAZINE_PTR_UNLOCK((&(szone->small_rack.magazines[i]))); } for (i = -1; i < szone->tiny_rack.num_magazines; ++i) { SZONE_MAGAZINE_PTR_UNLOCK((&(szone->tiny_rack.magazines[i]))); } } static void szone_reinit_lock(szone_t *szone) { mag_index_t i; SZONE_REINIT_LOCK(szone); for (i = -1; i < szone->small_rack.num_magazines; ++i) { SZONE_MAGAZINE_PTR_REINIT_LOCK((&(szone->small_rack.magazines[i]))); } for (i = -1; i < szone->tiny_rack.num_magazines; ++i) { SZONE_MAGAZINE_PTR_REINIT_LOCK((&(szone->tiny_rack.magazines[i]))); } } static boolean_t szone_locked(szone_t *szone) { mag_index_t i; int tookLock; tookLock = SZONE_TRY_LOCK(szone); if (tookLock == 0) { return 1; } SZONE_UNLOCK(szone); for (i = -1; i < szone->small_rack.num_magazines; ++i) { tookLock = SZONE_MAGAZINE_PTR_TRY_LOCK((&(szone->small_rack.magazines[i]))); if (tookLock == 0) { return 1; } SZONE_MAGAZINE_PTR_UNLOCK((&(szone->small_rack.magazines[i]))); } for (i = -1; i < szone->tiny_rack.num_magazines; ++i) { tookLock = SZONE_MAGAZINE_PTR_TRY_LOCK((&(szone->tiny_rack.magazines[i]))); if (tookLock == 0) { return 1; } SZONE_MAGAZINE_PTR_UNLOCK((&(szone->tiny_rack.magazines[i]))); } return 0; } size_t szone_pressure_relief(szone_t *szone, size_t goal) { size_t total = 0; MAGMALLOC_PRESSURERELIEFBEGIN((void *)szone, szone->basic_zone.zone_name, (int)goal); // DTrace USDT Probe MALLOC_TRACE(TRACE_malloc_memory_pressure | DBG_FUNC_START, (uint64_t)szone, goal, 0, 0); #if CONFIG_MADVISE_PRESSURE_RELIEF mag_index_t mag_index; magazine_t *tiny_depot_ptr = (&szone->tiny_rack.magazines[DEPOT_MAGAZINE_INDEX]); magazine_t *small_depot_ptr = (&szone->small_rack.magazines[DEPOT_MAGAZINE_INDEX]); for (mag_index = 0; mag_index < szone->tiny_rack.num_magazines; mag_index++) { size_t index; for (index = 0; index < szone->tiny_rack.region_generation->num_regions_allocated; ++index) { SZONE_LOCK(szone); region_t tiny = szone->tiny_rack.region_generation->hashed_regions[index]; if (!tiny || tiny == HASHRING_REGION_DEALLOCATED) { SZONE_UNLOCK(szone); continue; } magazine_t *mag_ptr = mag_lock_zine_for_region_trailer(szone->tiny_rack.magazines, REGION_TRAILER_FOR_TINY_REGION(tiny), MAGAZINE_INDEX_FOR_TINY_REGION(tiny)); SZONE_UNLOCK(szone); /* Ordering is important here, the magazine of a region may potentially change * during mag_lock_zine_for_region_trailer, so src_mag_index must be taken * after we've obtained the lock. */ mag_index_t src_mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(tiny); /* We can (and must) ignore magazines that are already in the recirc depot. */ if (src_mag_index == DEPOT_MAGAZINE_INDEX) { SZONE_MAGAZINE_PTR_UNLOCK(mag_ptr); continue; } if (tiny == mag_ptr->mag_last_region && (mag_ptr->mag_bytes_free_at_end || mag_ptr->mag_bytes_free_at_start)) { tiny_finalize_region(&szone->tiny_rack, mag_ptr); } /* Because this region is currently in use, we can't safely madvise it while * it's attached to the magazine. For this operation we have to remove it from * the current mag, attach it to the depot and then madvise. */ recirc_list_extract(&szone->tiny_rack, mag_ptr, REGION_TRAILER_FOR_TINY_REGION(tiny)); int objects_in_use = tiny_free_detach_region(&szone->tiny_rack, mag_ptr, tiny); SZONE_MAGAZINE_PTR_LOCK(tiny_depot_ptr); MAGAZINE_INDEX_FOR_TINY_REGION(tiny) = DEPOT_MAGAZINE_INDEX; REGION_TRAILER_FOR_TINY_REGION(tiny)->pinned_to_depot = 0; size_t bytes_inplay = tiny_free_reattach_region(&szone->tiny_rack, tiny_depot_ptr, tiny); /* Fix up the metadata of the target magazine while the region is in the depot. */ mag_ptr->mag_num_bytes_in_objects -= bytes_inplay; mag_ptr->num_bytes_in_magazine -= TINY_REGION_PAYLOAD_BYTES; mag_ptr->mag_num_objects -= objects_in_use; /* Now we can drop the magazine lock of the source mag. */ SZONE_MAGAZINE_PTR_UNLOCK(mag_ptr); tiny_depot_ptr->mag_num_bytes_in_objects += bytes_inplay; tiny_depot_ptr->num_bytes_in_magazine += TINY_REGION_PAYLOAD_BYTES; tiny_depot_ptr->mag_num_objects -= objects_in_use; recirc_list_splice_last(&szone->tiny_rack, tiny_depot_ptr, REGION_TRAILER_FOR_TINY_REGION(tiny)); /* Actually do the scan, done holding the depot lock, the call will drop the lock * around the actual madvise syscalls. */ tiny_free_scan_madvise_free(&szone->tiny_rack, tiny_depot_ptr, tiny); /* Now the region is in the recirc depot, the next allocations to require more * blocks will come along and take one of these regions back out of the depot. * As OS X madvise's reuse on an per-region basis, we leave as many of these * regions in the depot as possible after memory pressure. */ SZONE_MAGAZINE_PTR_UNLOCK(tiny_depot_ptr); } } for (mag_index = 0; mag_index < szone->small_rack.num_magazines; mag_index++) { size_t index; for (index = 0; index < szone->small_rack.region_generation->num_regions_allocated; ++index) { SZONE_LOCK(szone); region_t small = szone->small_rack.region_generation->hashed_regions[index]; if (!small || small == HASHRING_REGION_DEALLOCATED) { SZONE_UNLOCK(szone); continue; } magazine_t *mag_ptr = mag_lock_zine_for_region_trailer(szone->small_rack.magazines, REGION_TRAILER_FOR_SMALL_REGION(small), MAGAZINE_INDEX_FOR_SMALL_REGION(small)); SZONE_UNLOCK(szone); /* Ordering is important here, the magazine of a region may potentially change * during mag_lock_zine_for_region_trailer, so src_mag_index must be taken * after we've obtained the lock. */ mag_index_t src_mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(small); /* We can (and must) ignore magazines that are already in the recirc depot. */ if (src_mag_index == DEPOT_MAGAZINE_INDEX) { SZONE_MAGAZINE_PTR_UNLOCK(mag_ptr); continue; } if (small == mag_ptr->mag_last_region && (mag_ptr->mag_bytes_free_at_end || mag_ptr->mag_bytes_free_at_start)) { small_finalize_region(&szone->small_rack, mag_ptr); } /* Because this region is currently in use, we can't safely madvise it while * it's attached to the magazine. For this operation we have to remove it from * the current mag, attach it to the depot and then madvise. */ recirc_list_extract(&szone->small_rack, mag_ptr, REGION_TRAILER_FOR_SMALL_REGION(small)); int objects_in_use = small_free_detach_region(&szone->small_rack, mag_ptr, small); SZONE_MAGAZINE_PTR_LOCK(small_depot_ptr); MAGAZINE_INDEX_FOR_SMALL_REGION(small) = DEPOT_MAGAZINE_INDEX; REGION_TRAILER_FOR_SMALL_REGION(small)->pinned_to_depot = 0; size_t bytes_inplay = small_free_reattach_region(&szone->small_rack, small_depot_ptr, small); /* Fix up the metadata of the target magazine while the region is in the depot. */ mag_ptr->mag_num_bytes_in_objects -= bytes_inplay; mag_ptr->num_bytes_in_magazine -= SMALL_REGION_PAYLOAD_BYTES; mag_ptr->mag_num_objects -= objects_in_use; /* Now we can drop the magazine lock of the source mag. */ SZONE_MAGAZINE_PTR_UNLOCK(mag_ptr); small_depot_ptr->mag_num_bytes_in_objects += bytes_inplay; small_depot_ptr->num_bytes_in_magazine += SMALL_REGION_PAYLOAD_BYTES; small_depot_ptr->mag_num_objects -= objects_in_use; recirc_list_splice_last(&szone->small_rack, small_depot_ptr, REGION_TRAILER_FOR_SMALL_REGION(small)); /* Actually do the scan, done holding the depot lock, the call will drop the lock * around the actual madvise syscalls. */ small_free_scan_madvise_free(&szone->small_rack, small_depot_ptr, small); /* Now the region is in the recirc depot, the next allocations to require more * blocks will come along and take one of these regions back out of the depot. * As OS X madvise's reuse on an per-region basis, we leave as many of these * regions in the depot as possible after memory pressure. */ SZONE_MAGAZINE_PTR_UNLOCK(small_depot_ptr); } } #endif #if CONFIG_LARGE_CACHE if (szone->flotsam_enabled) { SZONE_LOCK(szone); // stack allocated copy of the death-row cache int idx = szone->large_entry_cache_oldest, idx_max = szone->large_entry_cache_newest; large_entry_t local_entry_cache[LARGE_ENTRY_CACHE_SIZE]; memcpy((void *)local_entry_cache, (void *)szone->large_entry_cache, sizeof(local_entry_cache)); szone->large_entry_cache_oldest = szone->large_entry_cache_newest = 0; szone->large_entry_cache[0].address = 0x0; szone->large_entry_cache[0].size = 0; szone->large_entry_cache_bytes = 0; szone->large_entry_cache_reserve_bytes = 0; szone->flotsam_enabled = FALSE; SZONE_UNLOCK(szone); // deallocate the death-row cache outside the zone lock size_t total = 0; while (idx != idx_max) { mvm_deallocate_pages((void *)local_entry_cache[idx].address, local_entry_cache[idx].size, 0); total += local_entry_cache[idx].size; if (++idx == LARGE_ENTRY_CACHE_SIZE) { idx = 0; } } if (0 != local_entry_cache[idx].address && 0 != local_entry_cache[idx].size) { mvm_deallocate_pages((void *)local_entry_cache[idx].address, local_entry_cache[idx].size, 0); total += local_entry_cache[idx].size; } } #endif MAGMALLOC_PRESSURERELIEFEND((void *)szone, szone->basic_zone.zone_name, (int)goal, (int)total); // DTrace USDT Probe MALLOC_TRACE(TRACE_malloc_memory_pressure | DBG_FUNC_END, (uint64_t)szone, goal, total, 0); return total; } boolean_t scalable_zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats, unsigned subzone) { szone_t *szone = (szone_t *)zone; switch (subzone) { case 0: { size_t s = 0; unsigned t = 0; size_t u = 0; mag_index_t mag_index; for (mag_index = -1; mag_index < szone->tiny_rack.num_magazines; mag_index++) { s += szone->tiny_rack.magazines[mag_index].mag_bytes_free_at_start; s += szone->tiny_rack.magazines[mag_index].mag_bytes_free_at_end; t += szone->tiny_rack.magazines[mag_index].mag_num_objects; u += szone->tiny_rack.magazines[mag_index].mag_num_bytes_in_objects; } stats->blocks_in_use = t; stats->size_in_use = u; stats->size_allocated = (szone->tiny_rack.num_regions - szone->tiny_rack.num_regions_dealloc) * TINY_REGION_SIZE; stats->max_size_in_use = stats->size_allocated - s; return 1; } case 1: { size_t s = 0; unsigned t = 0; size_t u = 0; mag_index_t mag_index; for (mag_index = -1; mag_index < szone->small_rack.num_magazines; mag_index++) { s += szone->small_rack.magazines[mag_index].mag_bytes_free_at_start; s += szone->small_rack.magazines[mag_index].mag_bytes_free_at_end; t += szone->small_rack.magazines[mag_index].mag_num_objects; u += szone->small_rack.magazines[mag_index].mag_num_bytes_in_objects; } stats->blocks_in_use = t; stats->size_in_use = u; stats->size_allocated = (szone->small_rack.num_regions - szone->small_rack.num_regions_dealloc) * SMALL_REGION_SIZE; stats->max_size_in_use = stats->size_allocated - s; return 1; } case 2: stats->blocks_in_use = szone->num_large_objects_in_use; stats->size_in_use = szone->num_bytes_in_large_objects; stats->max_size_in_use = stats->size_allocated = stats->size_in_use; return 1; case 3: stats->blocks_in_use = 0; // DEPRECATED szone->num_huge_entries; stats->size_in_use = 0; // DEPRECATED szone->num_bytes_in_huge_objects; stats->max_size_in_use = stats->size_allocated = 0; return 1; } return 0; } static void szone_statistics(szone_t *szone, malloc_statistics_t *stats) { size_t large; size_t s = 0; unsigned t = 0; size_t u = 0; mag_index_t mag_index; for (mag_index = -1; mag_index < szone->tiny_rack.num_magazines; mag_index++) { s += szone->tiny_rack.magazines[mag_index].mag_bytes_free_at_start; s += szone->tiny_rack.magazines[mag_index].mag_bytes_free_at_end; t += szone->tiny_rack.magazines[mag_index].mag_num_objects; u += szone->tiny_rack.magazines[mag_index].mag_num_bytes_in_objects; } for (mag_index = -1; mag_index < szone->small_rack.num_magazines; mag_index++) { s += szone->small_rack.magazines[mag_index].mag_bytes_free_at_start; s += szone->small_rack.magazines[mag_index].mag_bytes_free_at_end; t += szone->small_rack.magazines[mag_index].mag_num_objects; u += szone->small_rack.magazines[mag_index].mag_num_bytes_in_objects; } large = szone->num_bytes_in_large_objects + 0; // DEPRECATED szone->num_bytes_in_huge_objects; stats->blocks_in_use = t + szone->num_large_objects_in_use + 0; // DEPRECATED szone->num_huge_entries; stats->size_in_use = u + large; stats->max_size_in_use = stats->size_allocated = (szone->tiny_rack.num_regions - szone->tiny_rack.num_regions_dealloc) * TINY_REGION_SIZE + (szone->small_rack.num_regions - szone->small_rack.num_regions_dealloc) * SMALL_REGION_SIZE + large; // Now we account for the untouched areas stats->max_size_in_use -= s; } const struct malloc_introspection_t szone_introspect = { (void *)szone_ptr_in_use_enumerator, (void *)szone_good_size, (void *)szone_check, (void *)szone_print, szone_log, (void *)szone_force_lock, (void *)szone_force_unlock, (void *)szone_statistics, (void *)szone_locked, NULL, NULL, NULL, NULL, /* Zone enumeration version 7 and forward. */ (void *)szone_reinit_lock, // reinit_lock version 9 and foward }; // marked as const to spare the DATA section szone_t * create_scalable_szone(size_t initial_size, unsigned debug_flags) { szone_t *szone; #if defined(__i386__) || defined(__x86_64__) if (_COMM_PAGE_VERSION_REQD > (*((uint16_t *)_COMM_PAGE_VERSION))) { MALLOC_REPORT_FATAL_ERROR((*((uint16_t *)_COMM_PAGE_VERSION)), "comm page version mismatch"); } #endif /* get memory for the zone. */ szone = mvm_allocate_pages(SZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC); if (!szone) { return NULL; } /* set up the szone structure */ #if 0 #warning CHECK_REGIONS enabled debug_flags |= CHECK_REGIONS; #endif #if 0 #warning LOG enabled szone->log_address = ~0; #endif if (mvm_aslr_enabled()) { debug_flags &= ~DISABLE_ASLR; } else { debug_flags |= DISABLE_ASLR; } #if CONFIG_SMALL_CUTOFF_DYNAMIC || CONFIG_LARGE_CACHE uint64_t memsize = platform_hw_memsize(); #endif bool is_largemem = false; #if CONFIG_SMALL_CUTOFF_LARGEMEM is_largemem = true; #elif CONFIG_SMALL_CUTOFF_DYNAMIC // TODO: rdar://problem/35395572 // switch to largemem thresholds on devices with > 2 cores and > 2gb of memory uint32_t nproc = platform_cpu_count(); is_largemem = (nproc > 2) && (memsize > (2ull << 30)); #endif if (is_largemem) { debug_flags |= MALLOC_EXTENDED_SMALL_SLOTS; szone->is_largemem = 1; szone->large_threshold = LARGE_THRESHOLD_LARGEMEM; szone->vm_copy_threshold = VM_COPY_THRESHOLD_LARGEMEM; } else { debug_flags &= ~MALLOC_EXTENDED_SMALL_SLOTS; szone->is_largemem = 0; szone->large_threshold = LARGE_THRESHOLD; szone->vm_copy_threshold = VM_COPY_THRESHOLD; } // Query the number of configured processors. // Uniprocessor case gets just one tiny and one small magazine (whose index is zero). This gives // the same behavior as the original scalable malloc. MP gets per-CPU magazines // that scale (way) better. unsigned int max_mags = mag_max_magazines(); uint32_t num_magazines = (max_mags > 1) ? MIN(max_mags, TINY_MAX_MAGAZINES) : 1; rack_init(&szone->tiny_rack, RACK_TYPE_TINY, num_magazines, debug_flags); rack_init(&szone->small_rack, RACK_TYPE_SMALL, num_magazines, debug_flags); #if CONFIG_LARGE_CACHE // madvise(..., MADV_REUSABLE) death-row arrivals above this threshold [~0.1%] szone->large_entry_cache_reserve_limit = (size_t)(memsize >> 10); /* Reset protection when returning a previous large allocation? */ int32_t libSystemVersion = NSVersionOfLinkTimeLibrary("System"); if ((-1 != libSystemVersion) && ((libSystemVersion >> 16) < 112) /* CFSystemVersionSnowLeopard */) { szone->large_legacy_reset_mprotect = TRUE; } else { szone->large_legacy_reset_mprotect = FALSE; } #endif // Initialize the security token. szone->cookie = (uintptr_t)malloc_entropy[0]; szone->basic_zone.version = 10; szone->basic_zone.size = (void *)szone_size; szone->basic_zone.malloc = (void *)szone_malloc; szone->basic_zone.calloc = (void *)szone_calloc; szone->basic_zone.valloc = (void *)szone_valloc; szone->basic_zone.free = (void *)szone_free; szone->basic_zone.realloc = (void *)szone_realloc; szone->basic_zone.destroy = (void *)szone_destroy; szone->basic_zone.batch_malloc = (void *)szone_batch_malloc; szone->basic_zone.batch_free = (void *)szone_batch_free; szone->basic_zone.introspect = (struct malloc_introspection_t *)&szone_introspect; szone->basic_zone.memalign = (void *)szone_memalign; szone->basic_zone.free_definite_size = (void *)szone_free_definite_size; szone->basic_zone.pressure_relief = (void *)szone_pressure_relief; szone->basic_zone.claimed_address = (void *)szone_claimed_address; /* Set to zero once and for all as required by CFAllocator. */ szone->basic_zone.reserved1 = 0; /* Set to zero once and for all as required by CFAllocator. */ szone->basic_zone.reserved2 = 0; /* Prevent overwriting the function pointers in basic_zone. */ mprotect(szone, sizeof(szone->basic_zone), PROT_READ); szone->debug_flags = debug_flags; _malloc_lock_init(&szone->large_szone_lock); szone->cpu_id_key = -1UL; // Unused. CHECK(szone, __PRETTY_FUNCTION__); return szone; } malloc_zone_t * create_scalable_zone(size_t initial_size, unsigned debug_flags) { return (malloc_zone_t *) create_scalable_szone(initial_size, debug_flags); } /* vim: set noet:ts=4:sw=4:cindent: */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_malloc.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __MAGAZINE_MALLOC_H #define __MAGAZINE_MALLOC_H // MARK: magazine_malloc MALLOC_NOEXPORT malloc_zone_t * create_scalable_zone(size_t initial_size, unsigned debug_flags); MALLOC_NOEXPORT szone_t * create_scalable_szone(size_t initial_size, unsigned debug_flags); MALLOC_EXPORT boolean_t scalable_zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats, unsigned subzone); MALLOC_NOEXPORT extern int max_magazines; MALLOC_NOEXPORT extern int recirc_retained_regions; // MARK: magazine_malloc utility functions MALLOC_NOEXPORT extern const struct malloc_introspection_t szone_introspect; MALLOC_NOEXPORT void szone_batch_free(szone_t *szone, void **to_be_freed, unsigned count); MALLOC_NOEXPORT unsigned szone_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count); MALLOC_NOEXPORT void * szone_calloc(szone_t *szone, size_t num_items, size_t size); MALLOC_NOEXPORT void szone_free(szone_t *szone, void *ptr); MALLOC_NOEXPORT void szone_free_definite_size(szone_t *szone, void *ptr, size_t size); MALLOC_NOEXPORT size_t szone_good_size(szone_t *szone, size_t size); MALLOC_NOEXPORT void * szone_malloc(szone_t *szone, size_t size); MALLOC_NOEXPORT void * szone_memalign(szone_t *szone, size_t alignment, size_t size); MALLOC_NOEXPORT size_t szone_pressure_relief(szone_t *szone, size_t goal); MALLOC_NOEXPORT boolean_t szone_claimed_address(szone_t *szone, void *ptr); MALLOC_NOEXPORT void * szone_realloc(szone_t *szone, void *ptr, size_t new_size); MALLOC_NOEXPORT size_t szone_size(szone_t *szone, const void *ptr); MALLOC_NOEXPORT size_t szone_size_try_large(szone_t *szone, const void *ptr); MALLOC_NOEXPORT void * szone_valloc(szone_t *szone, size_t size); // MARK: tiny region allocator functions MALLOC_NOEXPORT boolean_t tiny_check_region(rack_t *rack, region_t region, size_t region_index, unsigned counter); MALLOC_NOEXPORT void tiny_finalize_region(rack_t *rack, magazine_t *tiny_mag_ptr); MALLOC_NOEXPORT int tiny_free_detach_region(rack_t *rack, magazine_t *tiny_mag_ptr, region_t r); MALLOC_NOEXPORT boolean_t tiny_free_list_check(rack_t *rack, grain_t slot, unsigned counter); MALLOC_NOEXPORT boolean_t tiny_free_no_lock(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, region_t region, void *ptr, msize_t msize); MALLOC_NOEXPORT size_t tiny_free_reattach_region(rack_t *rack, magazine_t *tiny_mag_ptr, region_t r); MALLOC_NOEXPORT void tiny_free_scan_madvise_free(rack_t *rack, magazine_t *depot_ptr, region_t r); MALLOC_NOEXPORT kern_return_t tiny_in_use_enumerator(task_t task, void *context, unsigned type_mask, szone_t *szone, memory_reader_t reader, vm_range_recorder_t recorder); MALLOC_NOEXPORT void * tiny_malloc_from_free_list(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize); MALLOC_NOEXPORT void * tiny_malloc_should_clear(rack_t *rack, msize_t msize, boolean_t cleared_requested); MALLOC_NOEXPORT void * tiny_memalign(szone_t *szone, size_t alignment, size_t size, size_t span); MALLOC_NOEXPORT boolean_t tiny_claimed_address(rack_t *rack, void *ptr); MALLOC_NOEXPORT void * tiny_try_shrink_in_place(rack_t *rack, void *ptr, size_t old_size, size_t new_good_size); MALLOC_NOEXPORT boolean_t tiny_try_realloc_in_place(rack_t *rack, void *ptr, size_t old_size, size_t new_size); MALLOC_NOEXPORT void free_tiny(rack_t *rack, void *ptr, region_t tiny_region, size_t known_size); MALLOC_NOEXPORT size_t tiny_size(rack_t *rack, const void *ptr); MALLOC_NOEXPORT unsigned tiny_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count); MALLOC_NOEXPORT void tiny_batch_free(szone_t *szone, void **to_be_freed, unsigned count); MALLOC_NOEXPORT void print_tiny_free_list(rack_t *rack); MALLOC_NOEXPORT void print_tiny_region(boolean_t verbose, region_t region, size_t bytes_at_start, size_t bytes_at_end); // MARK: small region allocation functions MALLOC_NOEXPORT boolean_t small_check_region(rack_t *rack, region_t region, size_t region_index, unsigned counter); MALLOC_NOEXPORT void small_finalize_region(rack_t *rack, magazine_t *small_mag_ptr); MALLOC_NOEXPORT int small_free_detach_region(rack_t *rack, magazine_t *small_mag_ptr, region_t r); MALLOC_NOEXPORT boolean_t small_free_list_check(rack_t *rack, grain_t slot, unsigned counter); MALLOC_NOEXPORT size_t small_free_reattach_region(rack_t *rack, magazine_t *small_mag_ptr, region_t r); MALLOC_NOEXPORT void small_free_scan_madvise_free(rack_t *rack, magazine_t *depot_ptr, region_t r); MALLOC_NOEXPORT kern_return_t small_in_use_enumerator(task_t task, void *context, unsigned type_mask, szone_t *szone, memory_reader_t reader, vm_range_recorder_t recorder); MALLOC_NOEXPORT void * small_malloc_should_clear(rack_t *rack, msize_t msize, boolean_t cleared_requested); MALLOC_NOEXPORT void * small_memalign(szone_t *szone, size_t alignment, size_t size, size_t span); MALLOC_NOEXPORT boolean_t small_claimed_address(rack_t *rack, void *ptr); MALLOC_NOEXPORT void * small_try_shrink_in_place(rack_t *rack, void *ptr, size_t old_size, size_t new_good_size); MALLOC_NOEXPORT boolean_t small_try_realloc_in_place(rack_t *rack, void *ptr, size_t old_size, size_t new_size); MALLOC_NOEXPORT void free_small(rack_t *rack, void *ptr, region_t small_region, size_t known_size); MALLOC_NOEXPORT size_t small_size(rack_t *rack, const void *ptr); MALLOC_NOEXPORT void print_small_free_list(rack_t *rack); MALLOC_NOEXPORT void print_small_region(szone_t *szone, boolean_t verbose, region_t region, size_t bytes_at_start, size_t bytes_at_end); // MARK: large region allocator functions MALLOC_NOEXPORT void free_large(szone_t *szone, void *ptr); MALLOC_NOEXPORT void large_entries_free_no_lock(szone_t *szone, large_entry_t *entries, unsigned num, vm_range_t *range_to_deallocate); MALLOC_NOEXPORT large_entry_t * large_entry_for_pointer_no_lock(szone_t *szone, const void *ptr); MALLOC_NOEXPORT kern_return_t large_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t large_entries_address, unsigned num_entries, memory_reader_t reader, vm_range_recorder_t recorder); MALLOC_NOEXPORT int large_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size); MALLOC_NOEXPORT void * large_try_shrink_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_good_size); MALLOC_NOEXPORT void * large_malloc(szone_t *szone, size_t num_kernel_pages, unsigned char alignment, boolean_t cleared_requested); MALLOC_NOEXPORT boolean_t large_claimed_address(szone_t *szone, void *ptr); MALLOC_NOEXPORT void * szone_malloc_should_clear(szone_t *szone, size_t size, boolean_t cleared_requested); // MARK: stack logging lite functionality #define MALLOC_STOCK_LOGGING_LITE_ZONE_NAME "MallocStackLoggingLiteZone" // These enable/disable stack logging lite for malloc allocations, not VM-only lite mode MALLOC_NOEXPORT void enable_stack_logging_lite(); MALLOC_NOEXPORT void disable_stack_logging_lite(); MALLOC_NOEXPORT malloc_zone_t * create_stack_logging_lite_zone(size_t initial_size, malloc_zone_t *helper_zone, unsigned debug_flags); #endif // __MAGAZINE_MALLOC_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_rack.c ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" void rack_init(rack_t *rack, rack_type_t type, uint32_t num_magazines, uint32_t debug_flags) { rack->type = type; rack->rg[0].nextgen = &rack->rg[1]; rack->rg[1].nextgen = &rack->rg[0]; rack->region_generation = &rack->rg[0]; rack->region_generation->hashed_regions = rack->initial_regions; rack->region_generation->num_regions_allocated = INITIAL_NUM_REGIONS; rack->region_generation->num_regions_allocated_shift = INITIAL_NUM_REGIONS_SHIFT; memset(rack->initial_regions, '\0', sizeof(region_t) * INITIAL_NUM_REGIONS); rack->cookie = (uintptr_t)malloc_entropy[0]; if (type == RACK_TYPE_SMALL) { // Flip the cookie for SMALL regions so that tiny and small free list // entries will trap when used if used in opposing region types. rack->cookie = ~rack->cookie; } rack->debug_flags = debug_flags; rack->num_magazines = num_magazines; rack->num_regions = 0; rack->num_regions_dealloc = 0; rack->magazines = NULL; if (num_magazines > 0) { // num_magazines + 1, the [-1] index will become the depot magazine size_t magsize = round_page_quanta(sizeof(magazine_t) * (num_magazines + 1)); magazine_t *magazines = mvm_allocate_pages(magsize, 0, MALLOC_ADD_GUARD_PAGES, VM_MEMORY_MALLOC); if (!magazines) { MALLOC_REPORT_FATAL_ERROR(0, "unable to allocate magazine array"); } rack->magazines = &magazines[1]; rack->num_magazines_mask_shift = 0; // The magazines are indexed in [0 .. (num_magazines - 1)] // Find the smallest power of 2 that exceeds (num_magazines - 1) int i = 1; while (i <= (num_magazines - 1)) { rack->num_magazines_mask_shift++; i <<= 1; } // Reduce i by 1 to obtain a mask covering [0 .. (num_tiny_magazines - 1)] rack->num_magazines_mask = i - 1; rack->last_madvise = 0; _malloc_lock_init(&rack->region_lock); _malloc_lock_init(&rack->magazines[DEPOT_MAGAZINE_INDEX].magazine_lock); for (int i=0; i < rack->num_magazines; i++) { _malloc_lock_init(&rack->magazines[i].magazine_lock); } } } void rack_destroy_regions(rack_t *rack, size_t region_size) { /* destroy regions attached to this rack */ for (int i=0; i < rack->region_generation->num_regions_allocated; i++) { if ((rack->region_generation->hashed_regions[i] != HASHRING_OPEN_ENTRY) && (rack->region_generation->hashed_regions[i] != HASHRING_REGION_DEALLOCATED)) { mvm_deallocate_pages(rack->region_generation->hashed_regions[i], region_size, 0); rack->region_generation->hashed_regions[i] = HASHRING_REGION_DEALLOCATED; } } } void rack_destroy(rack_t *rack) { /* if the rack has additional regions, then deallocate them */ if (rack->region_generation->hashed_regions != rack->initial_regions) { size_t size = round_page_quanta(rack->region_generation->num_regions_allocated * sizeof(region_t)); mvm_deallocate_pages(rack->region_generation->hashed_regions, size, 0); } if (rack->num_magazines > 0) { size_t size = round_page_quanta(sizeof(magazine_t) * (rack->num_magazines + 1)); mvm_deallocate_pages(&rack->magazines[-1], size, MALLOC_ADD_GUARD_PAGES); rack->magazines = NULL; } } void rack_region_insert(rack_t *rack, region_t region) { // Here find the only place in rackland that (infrequently) takes the tiny_regions_lock. // Only one thread at a time should be permitted to assess the density of the hash // ring and adjust if needed. // Only one thread at a time should be permitted to insert its new region on // the hash ring. // It is safe for all other threads to read the hash ring (hashed_regions) and // the associated sizes (num_regions_allocated and num_tiny_regions). _malloc_lock_lock(&rack->region_lock); // Check to see if the hash ring of tiny regions needs to grow. Try to // avoid the hash ring becoming too dense. if (rack->region_generation->num_regions_allocated < (2 * rack->num_regions)) { region_t *new_regions; size_t new_size; size_t new_shift = rack->region_generation->num_regions_allocated_shift; // In/Out parameter new_regions = hash_regions_grow_no_lock(rack->region_generation->hashed_regions, rack->region_generation->num_regions_allocated, &new_shift, &new_size); // Do not deallocate the current hashed_regions allocation since someone may // be iterating it. Instead, just leak it. // Prepare to advance to the "next generation" of the hash ring. rack->region_generation->nextgen->hashed_regions = new_regions; rack->region_generation->nextgen->num_regions_allocated = new_size; rack->region_generation->nextgen->num_regions_allocated_shift = new_shift; // Throw the switch to atomically advance to the next generation. rack->region_generation = rack->region_generation->nextgen; // Ensure everyone sees the advance. OSMemoryBarrier(); } // Insert the new region into the hash ring, and update malloc statistics hash_region_insert_no_lock(rack->region_generation->hashed_regions, rack->region_generation->num_regions_allocated, rack->region_generation->num_regions_allocated_shift, region); rack->num_regions++; _malloc_lock_unlock(&rack->region_lock); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_rack.h ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __MAGAZINE_RACK_H #define __MAGAZINE_RACK_H /******************************************************************************* * Definitions for region hash ******************************************************************************/ typedef void *region_t; typedef region_t *rgnhdl_t; /* A pointer into hashed_regions array. */ #define INITIAL_NUM_REGIONS_SHIFT 6 // log2(INITIAL_NUM_REGIONS) #define INITIAL_NUM_REGIONS (1 << INITIAL_NUM_REGIONS_SHIFT) // Must be a power of 2! #define HASHRING_OPEN_ENTRY ((region_t)0) // Initial value and sentinel marking end of collision chain #define HASHRING_REGION_DEALLOCATED ((region_t)-1) // Region at this slot reclaimed by OS #define HASH_BLOCKS_ALIGN TINY_BLOCKS_ALIGN // MIN( TINY_BLOCKS_ALIGN, SMALL_BLOCKS_ALIGN, ... ) typedef struct region_hash_generation { size_t num_regions_allocated; size_t num_regions_allocated_shift; // log2(num_regions_allocated) region_t *hashed_regions; // hashed by location struct region_hash_generation *nextgen; } region_hash_generation_t; OS_ENUM(rack_type, uint32_t, RACK_TYPE_NONE = 0, RACK_TYPE_TINY, RACK_TYPE_SMALL, ); /******************************************************************************* * Per-allocator collection of regions and magazines ******************************************************************************/ typedef struct rack_s { /* Regions for tiny objects */ _malloc_lock_s region_lock MALLOC_CACHE_ALIGN; rack_type_t type; size_t num_regions; size_t num_regions_dealloc; region_hash_generation_t *region_generation; region_hash_generation_t rg[2]; region_t initial_regions[INITIAL_NUM_REGIONS]; int num_magazines; unsigned num_magazines_mask; int num_magazines_mask_shift; uint32_t debug_flags; // array of per-processor magazines magazine_t *magazines; uintptr_t cookie; uintptr_t last_madvise; } rack_t; MALLOC_NOEXPORT void rack_init(rack_t *rack, rack_type_t type, uint32_t num_magazines, uint32_t debug_flags); MALLOC_NOEXPORT void rack_destroy_regions(rack_t *rack, size_t region_size); MALLOC_NOEXPORT void rack_destroy(rack_t *rack); MALLOC_NOEXPORT void rack_region_insert(rack_t *rack, region_t region); #endif // __MAGAZINE_RACK_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_small.c ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" /********************* SMALL FREE LIST UTILITIES ************************/ #pragma mark meta header helpers /* * Mark a block as free. Only the first quantum of a block is marked thusly, * the remainder are marked "middle". */ static MALLOC_INLINE void small_meta_header_set_is_free(msize_t *meta_headers, msize_t index, msize_t msize) { meta_headers[index] = msize | SMALL_IS_FREE; } /* * Mark a block as not free, preserving its size. */ static MALLOC_INLINE void small_meta_header_set_not_free(msize_t *meta_headers, msize_t index) { meta_headers[index] &= ~SMALL_IS_FREE; } /* * Mark a block as in use. Only the first quantum of a block is marked thusly, * the remainder are marked "middle". */ static MALLOC_INLINE void small_meta_header_set_in_use(msize_t *meta_headers, msize_t index, msize_t msize) { meta_headers[index] = msize; } /* * Mark a quantum as being the second or later in a block. */ static MALLOC_INLINE void small_meta_header_set_middle(msize_t *meta_headers, msize_t index) { meta_headers[index] = 0; } static MALLOC_INLINE MALLOC_ALWAYS_INLINE mag_index_t small_mag_get_thread_index(void) { #if CONFIG_SMALL_USES_HYPER_SHIFT if (os_likely(_os_cpu_number_override == -1)) { return _os_cpu_number() >> hyper_shift; } else { return _os_cpu_number_override >> hyper_shift; } #else // CONFIG_SMALL_USES_HYPER_SHIFT if (os_likely(_os_cpu_number_override == -1)) { return _os_cpu_number(); } else { return _os_cpu_number_override; } #endif // CONFIG_SMALL_USES_HYPER_SHIFT } #pragma mark in-place free list static MALLOC_INLINE void small_inplace_checksum_ptr(rack_t *rack, inplace_linkage_s *linkage, void *ptr) { uintptr_t checksum = free_list_gen_checksum((uintptr_t)ptr ^ rack->cookie ^ (uintptr_t)rack); linkage->checksum = checksum; linkage->ptr = ptr; } static MALLOC_INLINE free_list_t small_inplace_unchecksum_ptr(rack_t *rack, inplace_linkage_s *linkage) { if (linkage->checksum != (uint8_t)free_list_gen_checksum((uintptr_t)linkage->ptr ^ rack->cookie ^ (uintptr_t)rack)) { free_list_checksum_botch(rack, linkage, linkage->ptr); __builtin_trap(); } return (free_list_t){ .p = linkage->ptr }; } static MALLOC_INLINE free_list_t small_inplace_free_entry_get_previous(rack_t *rack, small_inplace_free_entry_t ptr) { return small_inplace_unchecksum_ptr(rack, &ptr->previous); } static MALLOC_INLINE void small_inplace_free_entry_set_previous(rack_t *rack, small_inplace_free_entry_t entry, free_list_t previous) { small_inplace_checksum_ptr(rack, &entry->previous, previous.p); } static MALLOC_INLINE free_list_t small_inplace_free_entry_get_next(rack_t *rack, small_inplace_free_entry_t ptr) { return small_inplace_unchecksum_ptr(rack, &ptr->next); } static MALLOC_INLINE void small_inplace_free_entry_set_next(rack_t *rack, small_inplace_free_entry_t entry, free_list_t next) { small_inplace_checksum_ptr(rack, &entry->next, next.p); } #pragma mark OOB free list // Returns true if the address and size of the free list entry would result // in the free entry being the only data on a given page. static MALLOC_INLINE boolean_t small_needs_oob_free_entry(void *ptr, msize_t msize) { return ((trunc_page_quanta((uintptr_t)ptr) == (uintptr_t)ptr) && (SMALL_BYTES_FOR_MSIZE(msize) >= vm_kernel_page_size)); } // Returns true if the address given lies within the region's OOB free // list entries, rather than a free_list_t in the region's heap space. static MALLOC_INLINE boolean_t small_is_oob_free_entry(free_list_t ptr) { small_region_t region = SMALL_REGION_FOR_PTR(ptr.p); return (((uintptr_t)ptr.p >= (uintptr_t)®ion->small_oob_free_entries[0]) && ((uintptr_t)ptr.p < (uintptr_t)®ion->small_oob_free_entries[SMALL_OOB_COUNT])); } static MALLOC_INLINE void small_oob_free_entry_set_previous(oob_free_entry_t oobe, free_list_t previous) { oobe->prev = (uintptr_t)previous.p; } static MALLOC_INLINE free_list_t small_oob_free_entry_get_previous(oob_free_entry_t oobe) { return (free_list_t){ .p = (void *)oobe->prev }; } static MALLOC_INLINE void small_oob_free_entry_set_next(oob_free_entry_t oobe, free_list_t next) { oobe->next = (uintptr_t)next.p; } static MALLOC_INLINE free_list_t small_oob_free_entry_get_next(oob_free_entry_t oobe) { return (free_list_t){ .p = (void *)oobe->next }; } static MALLOC_INLINE void * small_oob_free_entry_get_ptr(oob_free_entry_t oobe) { small_region_t region = SMALL_REGION_FOR_PTR(oobe); uint16_t block = oobe->ptr & ~SMALL_IS_OOB; return (void *)((uintptr_t)region + (block << SHIFT_SMALL_QUANTUM)); } static MALLOC_INLINE void small_oob_free_entry_set_ptr(oob_free_entry_t oobe, void *ptr) { oobe->ptr = SMALL_IS_OOB | (SMALL_OFFSET_FOR_PTR(ptr) >> SHIFT_SMALL_QUANTUM); } static MALLOC_INLINE void small_oob_free_entry_set_free(oob_free_entry_t oobe) { oobe->prev = ~0; oobe->next = ~0; oobe->ptr = 0; } // Finds the first unused OOB free list entry in the pointer's region. // Returns NULL if all of the OOB entries are used. static MALLOC_INLINE oob_free_entry_t small_oob_free_find_empty(void *ptr, msize_t msize) { small_region_t region = SMALL_REGION_FOR_PTR(ptr); // There are 61 of these entries at the end of a small region. // If this changes, then a linear search through the list may // become an unsuitable choice. for (int i=0; i < SMALL_OOB_COUNT; i++) { if (region->small_oob_free_entries[i].ptr == 0) { return ®ion->small_oob_free_entries[i]; } } #if DEBUG_MALLOC malloc_report(ASL_LEVEL_INFO, "used all slots of OOB entries\n"); #endif return NULL; } static MALLOC_INLINE oob_free_entry_t small_oob_free_find_ptr(void *ptr, msize_t msize) { small_region_t region = SMALL_REGION_FOR_PTR(ptr); // There are 61 of these entries at the end of a small region. // If this changes, then a linear search through the list may // become an unsuitable choice. for (int i=0; i < SMALL_OOB_COUNT; i++) { if (small_oob_free_entry_get_ptr(®ion->small_oob_free_entries[i]) == ptr) { return ®ion->small_oob_free_entries[i]; } } return NULL; } #pragma mark generic free list static MALLOC_INLINE void small_free_list_set_previous(rack_t *rack, free_list_t entry, free_list_t previous) { if (small_is_oob_free_entry(entry)) { small_oob_free_entry_set_previous(entry.oob, previous); } else { small_inplace_free_entry_set_previous(rack, entry.small_inplace, previous); } } static MALLOC_INLINE free_list_t small_free_list_get_previous(rack_t *rack, free_list_t ptr) { MALLOC_ASSERT(ptr.p); if (small_is_oob_free_entry(ptr)) { return small_oob_free_entry_get_previous(ptr.oob); } else { return small_inplace_free_entry_get_previous(rack, ptr.small_inplace); } } static MALLOC_INLINE void small_free_list_set_next(rack_t *rack, free_list_t entry, free_list_t next) { if (small_is_oob_free_entry(entry)) { small_oob_free_entry_set_next(entry.oob, next); } else { small_inplace_free_entry_set_next(rack, entry.small_inplace, next); } } static MALLOC_INLINE free_list_t small_free_list_get_next(rack_t *rack, free_list_t ptr) { MALLOC_ASSERT(ptr.p); if (small_is_oob_free_entry(ptr)) { return small_oob_free_entry_get_next(ptr.oob); } else { return small_inplace_free_entry_get_next(rack, ptr.small_inplace); } } static MALLOC_INLINE void * small_free_list_get_ptr(rack_t *rack, free_list_t ptr) { if (!ptr.p) { return NULL; } else if (small_is_oob_free_entry(ptr)) { return small_oob_free_entry_get_ptr(ptr.oob); } else { return (void *)ptr.p; } } // Returns a free_list_t that is either inline or not based on the // pointer and msize. static MALLOC_INLINE free_list_t small_free_list_from_ptr(rack_t *rack, void *ptr, msize_t msize) { MALLOC_ASSERT(msize); // The default is to put the free_list_t in the memory that // the pointer leads to. free_list_t entry; entry.p = ptr; // If the pointer is page aligned, and the msize is greater // than a whole page, then we try and put the entry in // the out-of-band area instead. if (small_needs_oob_free_entry(ptr, msize)) { oob_free_entry_t oobe = small_oob_free_find_empty(ptr, msize); if (oobe) { small_oob_free_entry_set_ptr(oobe, ptr); entry.oob = oobe; } } return entry; } static MALLOC_INLINE void small_free_mark_free(rack_t *rack, free_list_t entry, msize_t msize) { // Marks both the start and end block of a free-list entry as free. void *ptr = small_free_list_get_ptr(rack, entry); msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr); uintptr_t start_index = SMALL_META_INDEX_FOR_PTR(ptr); uintptr_t end_index = SMALL_META_INDEX_FOR_PTR(ptr + SMALL_BYTES_FOR_MSIZE(msize) - 1); MALLOC_ASSERT(start_index <= end_index); small_meta_header_set_is_free(meta_headers, start_index, msize); small_meta_header_set_is_free(meta_headers, end_index, msize); } static MALLOC_INLINE void small_free_mark_middle(rack_t *rack, free_list_t entry, msize_t msize) { // Marks both the start and end block of a free-list entry as "middle" (unfree). void *ptr = small_free_list_get_ptr(rack, entry); msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr); uintptr_t start_index = SMALL_META_INDEX_FOR_PTR(ptr); uintptr_t end_index = SMALL_META_INDEX_FOR_PTR(ptr + SMALL_BYTES_FOR_MSIZE(msize) - 1); MALLOC_ASSERT(start_index <= end_index); MALLOC_ASSERT((meta_headers[start_index] & ~SMALL_IS_FREE) == msize); small_meta_header_set_middle(meta_headers, start_index); small_meta_header_set_middle(meta_headers, end_index); } static MALLOC_INLINE void small_free_mark_unfree(rack_t *rack, free_list_t entry, msize_t msize) { // Marks both the start and end block of a free-list entry as not free. void *ptr = small_free_list_get_ptr(rack, entry); msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr); uintptr_t start_index = SMALL_META_INDEX_FOR_PTR(ptr); uintptr_t end_index = SMALL_META_INDEX_FOR_PTR(ptr + SMALL_BYTES_FOR_MSIZE(msize) - 1); MALLOC_ASSERT(start_index <= end_index); small_meta_header_set_not_free(meta_headers, start_index); small_meta_header_set_not_free(meta_headers, end_index); } static MALLOC_INLINE unsigned int small_free_list_count(rack_t *rack, free_list_t ptr) { unsigned int count = 0; while (ptr.p) { count++; ptr = small_free_list_get_next(rack, ptr); } return count; } /* * Adds an item to the proper free list, and also marks the meta-header of the * block properly. * Assumes szone has been locked */ static free_list_t small_free_list_add_ptr(rack_t *rack, magazine_t *small_mag_ptr, void *ptr, msize_t msize) { grain_t slot = SMALL_FREE_SLOT_FOR_MSIZE(rack, msize); free_list_t free_head = small_mag_ptr->mag_free_list[slot]; // This will either return the free_list_t for the current pointer, or attempt // to reserve an OOB entry for us. free_list_t free_ptr = small_free_list_from_ptr(rack, ptr, msize); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in %s, ptr=%p, msize=%d\n", __FUNCTION__, ptr, msize); } if (((uintptr_t)ptr) & (SMALL_QUANTUM - 1)) { malloc_zone_error(rack->debug_flags, true, "small_free_list_add_ptr: Unaligned ptr %p\n", ptr); } #endif small_free_list_set_previous(rack, free_ptr, (free_list_t){ .p = NULL }); small_free_list_set_next(rack, free_ptr, free_head); // Set the start and end blocks of the meta header as "free". Marking the last block // allows coalescing the regions when we free adjacent regions. small_free_mark_free(rack, free_ptr, msize); if (small_free_list_get_ptr(rack, free_head)) { #if DEBUG_MALLOC if (small_free_list_get_previous(szone, free_head)) { malloc_zone_error(rack->debug_flags, true, "small_free_list_add_ptr: Internal invariant broken (free_head->previous != NULL)\n" "ptr=%p slot=%d free_head=%p previous=%p\n", ptr, slot, (void *)free_head, free_head->previous.p); } if (!SMALL_PTR_IS_FREE(small_free_list_get_ptr(szone, free_head))) { malloc_zone_error(rack->debug_flags, true, "small_free_list_add_ptr: Internal invariant broken (free_head is not a free pointer)\n" "ptr=%p slot=%d free_head=%p\n", ptr, slot, (void *)small_free_list_get_ptr(szone, free_head)); } #endif small_free_list_set_previous(rack, free_head, free_ptr); } else { BITMAPN_SET(small_mag_ptr->mag_bitmap, slot); } small_mag_ptr->mag_free_list[slot] = free_ptr; return free_ptr; } /* * Removes the item pointed to by ptr in the proper free list. * Assumes szone has been locked */ static void small_free_list_remove_ptr_no_clear(rack_t *rack, magazine_t *small_mag_ptr, free_list_t entry, msize_t msize) { grain_t slot = SMALL_FREE_SLOT_FOR_MSIZE(rack, msize); free_list_t next, previous; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "In %s, ptr=%p, msize=%d\n", __FUNCTION__, ptr, msize); } #endif previous = small_free_list_get_previous(rack, entry); next = small_free_list_get_next(rack, entry); if (!small_free_list_get_ptr(rack, previous)) { // The block to remove is the head of the free list #if DEBUG_MALLOC if (small_mag_ptr->mag_free_list[slot] != ptr) { malloc_zone_error(rack->debug_flags, true, "small_free_list_remove_ptr_no_clear: Internal invariant broken (small_mag_ptr->mag_free_list[slot])\n" "ptr=%p slot=%d msize=%d small_mag_ptr->mag_free_list[slot]=%p\n", ptr, slot, msize, (void *)small_mag_ptr->mag_free_list[slot]); return; } #endif small_mag_ptr->mag_free_list[slot] = next; if (!small_free_list_get_ptr(rack, next)) { BITMAPN_CLR(small_mag_ptr->mag_bitmap, slot); } } else { // Check that the next pointer of "previous" points to "entry". free_list_t prev_next = small_free_list_get_next(rack, previous); if (small_free_list_get_ptr(rack, prev_next) != small_free_list_get_ptr(rack, entry)) { malloc_zone_error(rack->debug_flags, true, "small_free_list_remove_ptr_no_clear: Internal invariant broken (next ptr of prev) for %p, prev_next=%p\n", small_free_list_get_ptr(rack, entry), small_free_list_get_ptr(rack, prev_next)); __builtin_unreachable(); // Always crashes in malloc_zone_error(). } small_free_list_set_next(rack, previous, next); } if (small_free_list_get_ptr(rack, next)) { // Check that the previous pointer of "next" points to "entry". free_list_t next_prev = small_free_list_get_previous(rack, next); if (small_free_list_get_ptr(rack, next_prev) != small_free_list_get_ptr(rack, entry)) { malloc_zone_error(rack->debug_flags, true, "small_free_list_remove_ptr_no_clear: Internal invariant broken (prev ptr of next) for %p, next_prev=%p\n", small_free_list_get_ptr(rack, entry), small_free_list_get_ptr(rack, next_prev)); __builtin_unreachable(); // Always crashes in malloc_zone_error(). } small_free_list_set_previous(rack, next, previous); } if (small_is_oob_free_entry(entry)) { small_oob_free_entry_set_free(entry.oob); } } static void small_free_list_remove_ptr(rack_t *rack, magazine_t *small_mag_ptr, free_list_t entry, msize_t msize) { // In the general case we want to ensure we marked these entries as "middle" // while we are in this function. However, when we're moving free list entries // from/to the recirc depot we rely on the metadata bits being intact to // reconstruct the free list. In that case we have to be able to skip this // metadata manipulation. small_free_mark_middle(rack, entry, msize); small_free_list_remove_ptr_no_clear(rack, small_mag_ptr, entry, msize); } // Find a free list entry by its pointer address. This should only really be used // by small_finalize_region, or similar, where the free_list_t entry of a known // pointer is desired. Otherwise it is cheaper to always pull off the free lists. static free_list_t small_free_list_find_by_ptr(rack_t *rack, magazine_t *small_mag_ptr, void *ptr, msize_t msize) { if (*SMALL_METADATA_FOR_PTR(ptr) == (SMALL_IS_FREE | msize)) { // If the block is marked free, and of size `msize`, then we first must check // if the alignment+size is such that we could have use an OOB-entry. if (small_needs_oob_free_entry(ptr, msize)) { // Scan the OOB entries looking for this address. small_region_t region = SMALL_REGION_FOR_PTR(ptr); for (int i=0; ismall_oob_free_entries[i].ptr) { continue; } if (small_oob_free_entry_get_ptr(®ion->small_oob_free_entries[i]) == ptr) { return (free_list_t){ .oob = ®ion->small_oob_free_entries[i] }; } } } // Otherwise, the freed pointer will be in place. return (free_list_t){ .p = ptr }; } malloc_zone_error(rack->debug_flags, true, "small_free_list_find_by_ptr: ptr is not free (ptr metadata !SMALL_IS_FREE), " "ptr=%p msize=%d metadata=0x%x\n", ptr, msize, *SMALL_METADATA_FOR_PTR(ptr)); __builtin_trap(); } void small_finalize_region(rack_t *rack, magazine_t *small_mag_ptr) { void *last_block, *previous_block; msize_t last_msize, previous_msize, last_index; free_list_t previous; // It is possible that the block prior to the last block in the region has // been free'd, but was not coalesced with the free bytes at the end of the // block, since we treat the bytes at the end of the region as "in use" in // the meta headers. Attempt to coalesce the last block with the previous // block, so we don't violate the "no consecutive free blocks" invariant. // // FIXME: If we could calculate the previous small free size in the same // manner as tiny_previous_preceding_free, it would eliminate the // index & previous msize checks, which are a guard against reading // bogus data out of in-use or written-on-freed memory. // // FIXME: Need to investigate how much work would be required to increase // 'mag_bytes_free_at_end' when freeing the preceding block, rather // than performing this workaround. // if (small_mag_ptr->mag_bytes_free_at_end) { last_block = SMALL_REGION_END(small_mag_ptr->mag_last_region) - small_mag_ptr->mag_bytes_free_at_end; last_msize = SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_end); last_index = SMALL_META_INDEX_FOR_PTR(last_block); previous_msize = SMALL_PREVIOUS_MSIZE(last_block); if (last_index && (previous_msize <= last_index)) { previous_block = (void *)((uintptr_t)last_block - SMALL_BYTES_FOR_MSIZE(previous_msize)); if (SMALL_PTR_IS_FREE(previous_block)) { previous = small_free_list_find_by_ptr(rack, small_mag_ptr, previous_block, previous_msize); small_free_list_remove_ptr(rack, small_mag_ptr, previous, previous_msize); last_block = previous_block; last_msize += previous_msize; } } // splice last_block into the free list small_free_list_add_ptr(rack, small_mag_ptr, last_block, last_msize); small_mag_ptr->mag_bytes_free_at_end = 0; } #if CONFIG_ASLR_INTERNAL free_list_t next; if (small_mag_ptr->mag_bytes_free_at_start) { last_block = SMALL_REGION_ADDRESS(small_mag_ptr->mag_last_region); last_msize = SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_start); void *next_block = (void *)((uintptr_t)last_block + small_mag_ptr->mag_bytes_free_at_start); if (SMALL_PTR_IS_FREE(next_block)) { msize_t next_msize = SMALL_PTR_SIZE(next_block); next = small_free_list_find_by_ptr(rack, small_mag_ptr, next_block, next_msize); small_free_list_remove_ptr(rack, small_mag_ptr, next, next_msize); last_msize += next_msize; } // splice last_block into the free list small_free_list_add_ptr(rack, small_mag_ptr, last_block, last_msize); small_mag_ptr->mag_bytes_free_at_start = 0; } #endif // TODO: Will we ever need to coalesce the blocks at the beginning and end when we finalize? small_mag_ptr->mag_last_region = NULL; } int small_free_detach_region(rack_t *rack, magazine_t *small_mag_ptr, region_t r) { unsigned char *ptr = SMALL_REGION_ADDRESS(r); msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr); uintptr_t start = (uintptr_t)SMALL_REGION_ADDRESS(r); uintptr_t current = start; uintptr_t limit = (uintptr_t)SMALL_REGION_END(r); int total_alloc = 0; while (current < limit) { unsigned index = SMALL_META_INDEX_FOR_PTR(current); msize_t msize_and_free = meta_headers[index]; boolean_t is_free = msize_and_free & SMALL_IS_FREE; msize_t msize = msize_and_free & ~SMALL_IS_FREE; if (!msize) { #if DEBUG_MALLOC boolean_t is_free = msize_and_free & SMALL_IS_FREE; malloc_report(ASL_LEVEL_ERR, "*** small_free_detach_region error with %p: msize=%d is_free=%d\n", (void *)current, msize, is_free); #endif break; } if (is_free) { free_list_t entry = small_free_list_find_by_ptr(rack, small_mag_ptr, (void *)current, msize); small_free_list_remove_ptr_no_clear(rack, small_mag_ptr, entry, msize); } else { total_alloc++; } current += SMALL_BYTES_FOR_MSIZE(msize); } return total_alloc; } size_t small_free_reattach_region(rack_t *rack, magazine_t *small_mag_ptr, region_t r) { unsigned char *ptr = SMALL_REGION_ADDRESS(r); msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr); uintptr_t start = (uintptr_t)SMALL_REGION_ADDRESS(r); uintptr_t current = start; uintptr_t limit = (uintptr_t)SMALL_REGION_END(r); size_t total_alloc = 0; while (current < limit) { unsigned index = SMALL_META_INDEX_FOR_PTR(current); msize_t msize_and_free = meta_headers[index]; boolean_t is_free = msize_and_free & SMALL_IS_FREE; msize_t msize = msize_and_free & ~SMALL_IS_FREE; if (!msize) { #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "*** small_free_reattach_region error with %p: msize=%d is_free=%d\n", (void *)current, msize, is_free); #endif break; } if (is_free) { small_free_list_add_ptr(rack, small_mag_ptr, (void *)current, msize); } else { total_alloc += SMALL_BYTES_FOR_MSIZE(msize); } current += SMALL_BYTES_FOR_MSIZE(msize); } return total_alloc; } typedef struct { uint16_t pnum, size; } small_pg_pair_t; void small_free_scan_madvise_free(rack_t *rack, magazine_t *depot_ptr, region_t r) { uintptr_t start = (uintptr_t)SMALL_REGION_ADDRESS(r); uintptr_t current = start; uintptr_t limit = (uintptr_t)SMALL_REGION_END(r); msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(start); small_pg_pair_t advisory[((SMALL_REGION_PAYLOAD_BYTES + vm_kernel_page_size - 1) >> vm_kernel_page_shift) >> 1]; // 4096bytes stack allocated int advisories = 0; // Scan the metadata identifying blocks which span one or more pages. Mark the pages MADV_FREE taking care to preserve free list // management data. while (current < limit) { unsigned index = SMALL_META_INDEX_FOR_PTR(current); msize_t msize_and_free = meta_headers[index]; boolean_t is_free = msize_and_free & SMALL_IS_FREE; msize_t msize = msize_and_free & ~SMALL_IS_FREE; if (is_free && !msize && (current == start)) { #if DEBUG_MALLOC // first block is all free malloc_report(ASL_LEVEL_ERR, "*** small_free_scan_madvise_free first block is all free! %p: msize=%d is_free=%d\n", (void *)current, msize, is_free); #endif uintptr_t pgLo = round_page_kernel(start + sizeof(free_list_t) + sizeof(msize_t)); uintptr_t pgHi = trunc_page_kernel(start + SMALL_REGION_SIZE - sizeof(msize_t)); if (pgLo < pgHi) { advisory[advisories].pnum = (pgLo - start) >> vm_kernel_page_shift; advisory[advisories].size = (pgHi - pgLo) >> vm_kernel_page_shift; advisories++; } break; } if (!msize) { #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "*** small_free_scan_madvise_free error with %p: msize=%d is_free=%d\n", (void *)current, msize, is_free); #endif break; } if (is_free) { uintptr_t pgLo = round_page_kernel(current + sizeof(free_list_t) + sizeof(msize_t)); uintptr_t pgHi = trunc_page_kernel(current + SMALL_BYTES_FOR_MSIZE(msize) - sizeof(msize_t)); if (pgLo < pgHi) { advisory[advisories].pnum = (pgLo - start) >> vm_kernel_page_shift; advisory[advisories].size = (pgHi - pgLo) >> vm_kernel_page_shift; advisories++; } } current += SMALL_BYTES_FOR_MSIZE(msize); } if (advisories > 0) { int i; OSAtomicIncrement32Barrier(&(REGION_TRAILER_FOR_SMALL_REGION(r)->pinned_to_depot)); SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); for (i = 0; i < advisories; ++i) { uintptr_t addr = (advisory[i].pnum << vm_page_quanta_shift) + start; size_t size = advisory[i].size << vm_page_quanta_shift; mvm_madvise_free(rack, r, addr, addr + size, NULL, rack->debug_flags & MALLOC_DO_SCRIBBLE); } SZONE_MAGAZINE_PTR_LOCK(depot_ptr); OSAtomicDecrement32Barrier(&(REGION_TRAILER_FOR_SMALL_REGION(r)->pinned_to_depot)); } } static region_t small_find_msize_region(rack_t *rack, magazine_t *small_mag_ptr, mag_index_t mag_index, msize_t msize) { void *ptr; grain_t slot = SMALL_FREE_SLOT_FOR_MSIZE(rack, msize); free_list_t *free_list = small_mag_ptr->mag_free_list; free_list_t *the_slot = free_list + slot; free_list_t *limit; unsigned bitmap; // Assumes we've locked the magazine CHECK_MAGAZINE_PTR_LOCKED(szone, small_mag_ptr, __PRETTY_FUNCTION__); // Look for an exact match by checking the freelist for this msize. ptr = small_free_list_get_ptr(rack, *the_slot); if (ptr) { return SMALL_REGION_FOR_PTR(ptr); } // Mask off the bits representing slots holding free blocks smaller than // the size we need. if (SMALL_FREELIST_BITMAP_WORDS(rack) > 1) { // BITMAPN_CTZ implementation unsigned idx = slot >> 5; bitmap = 0; unsigned mask = ~((1 << (slot & 31)) - 1); for (; idx < SMALL_FREELIST_BITMAP_WORDS(rack); ++idx) { bitmap = small_mag_ptr->mag_bitmap[idx] & mask; if (bitmap != 0) { break; } mask = ~0U; } // Check for fallthrough: No bits set in bitmap if ((bitmap == 0) && (idx == SMALL_FREELIST_BITMAP_WORDS(rack))) { return NULL; } // Start looking at the first set bit, plus 32 bits for every word of // zeroes or entries that were too small. slot = BITMAP32_CTZ((&bitmap)) + (idx * 32); } else { bitmap = small_mag_ptr->mag_bitmap[0] & ~((1 << slot) - 1); if (!bitmap) { return NULL; } slot = BITMAP32_CTZ((&bitmap)); } limit = free_list + SMALL_FREE_SLOT_COUNT(rack) - 1; free_list += slot; if (free_list < limit) { ptr = small_free_list_get_ptr(rack, *free_list); if (ptr) { return SMALL_REGION_FOR_PTR(ptr); } else { /* Shouldn't happen. Fall through to look at last slot. */ #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "in small_malloc_from_free_list(), mag_bitmap out of sync, slot=%d\n", slot); #endif } } // We are now looking at the last slot, which contains blocks equal to, or // due to coalescing of free blocks, larger than (num_small_slots - 1) * (small quantum size). ptr = small_free_list_get_ptr(rack, *limit); if (ptr) { return SMALL_REGION_FOR_PTR(ptr); } return NULL; } static boolean_t small_get_region_from_depot(rack_t *rack, magazine_t *small_mag_ptr, mag_index_t mag_index, msize_t msize) { magazine_t *depot_ptr = &(rack->magazines[DEPOT_MAGAZINE_INDEX]); /* FIXME: Would Uniprocessor benefit from recirc and MADV_FREE? */ if (rack->num_magazines == 1) { // Uniprocessor, single magazine, so no recirculation necessary return 0; } #if DEBUG_MALLOC if (DEPOT_MAGAZINE_INDEX == mag_index) { malloc_zone_error(rack->debug_flags, true, "small_get_region_from_depot called for magazine index -1\n", NULL, NULL); return 0; } #endif SZONE_MAGAZINE_PTR_LOCK(depot_ptr); // Appropriate a Depot'd region that can satisfy requested msize. region_trailer_t *node; region_t sparse_region; while (1) { sparse_region = small_find_msize_region(rack, depot_ptr, DEPOT_MAGAZINE_INDEX, msize); if (NULL == sparse_region) { // Depot empty? SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); return 0; } node = REGION_TRAILER_FOR_SMALL_REGION(sparse_region); if (0 >= node->pinned_to_depot) { break; } SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); yield(); SZONE_MAGAZINE_PTR_LOCK(depot_ptr); } // disconnect node from Depot recirc_list_extract(rack, depot_ptr, node); // Iterate the region pulling its free entries off the (locked) Depot's free list int objects_in_use = small_free_detach_region(rack, depot_ptr, sparse_region); // Transfer ownership of the region MAGAZINE_INDEX_FOR_SMALL_REGION(sparse_region) = mag_index; node->pinned_to_depot = 0; // Iterate the region putting its free entries on its new (locked) magazine's free list size_t bytes_inplay = small_free_reattach_region(rack, small_mag_ptr, sparse_region); depot_ptr->mag_num_bytes_in_objects -= bytes_inplay; depot_ptr->num_bytes_in_magazine -= SMALL_REGION_PAYLOAD_BYTES; depot_ptr->mag_num_objects -= objects_in_use; small_mag_ptr->mag_num_bytes_in_objects += bytes_inplay; small_mag_ptr->num_bytes_in_magazine += SMALL_REGION_PAYLOAD_BYTES; small_mag_ptr->mag_num_objects += objects_in_use; // connect to magazine as first node recirc_list_splice_first(rack, small_mag_ptr, node); SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); MAGMALLOC_DEPOTREGION(SMALL_SZONE_FROM_RACK(rack), (int)mag_index, (void *)sparse_region, SMALL_REGION_SIZE, (int)BYTES_USED_FOR_SMALL_REGION(sparse_region)); // DTrace USDT Probe return 1; } #if CONFIG_AGGRESSIVE_MADVISE || CONFIG_RECIRC_DEPOT static MALLOC_INLINE void small_madvise_free_range_no_lock(rack_t *rack, magazine_t *small_mag_ptr, region_t region, free_list_t freee, msize_t fmsize, void *headptr, size_t headsize) { void *ptr = small_free_list_get_ptr(rack, freee); region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(region); // Lock on small_magazines[mag_index] is already held here. // Calculate the first page in the coalesced block that would be safe to mark MADV_FREE size_t free_header_size = sizeof(free_list_t) + sizeof(msize_t); // If the free_list_t entry is out-of-line then we don't need to reserve any space // at the start of the region. if (small_is_oob_free_entry(freee)) { free_header_size = 0; } uintptr_t safe_ptr = (uintptr_t)ptr + free_header_size; uintptr_t round_safe = round_page_kernel(safe_ptr); // Calculate the last page in the coalesced block that would be safe to mark MADV_FREE uintptr_t safe_extent = (uintptr_t)ptr + SMALL_BYTES_FOR_MSIZE(fmsize); uintptr_t trunc_extent = trunc_page_kernel(safe_extent); // The newly freed block may complete a span of bytes that cover one or more pages. Mark the span with MADV_FREE. if (round_safe < trunc_extent) { // Coalesced area covers a page (perhaps many) // Extend the freed block by the free region header and tail sizes to include pages // we may have coalesced that no longer host free region tails and headers. // This may extend over in-use ranges, but the MIN/MAX clamping below will fix that up. uintptr_t lo = trunc_page_kernel((uintptr_t)headptr); uintptr_t hi = round_page_kernel((uintptr_t)headptr + headsize + free_header_size); uintptr_t free_lo = MAX(round_safe, lo); uintptr_t free_hi = MIN(trunc_extent, hi); if (free_lo < free_hi) { // Before unlocking, ensure that the metadata for the freed region // makes it look not free but includes the length. This ensures that // any code that inspects the metadata while we are unlocked sees // a valid state and will not try to use or coalesce freed memory // into it. small_free_mark_unfree(rack, freee, fmsize); small_free_list_remove_ptr_no_clear(rack, small_mag_ptr, freee, fmsize); OSAtomicIncrement32Barrier(&(node->pinned_to_depot)); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); mvm_madvise_free(rack, region, free_lo, free_hi, &rack->last_madvise, rack->debug_flags & MALLOC_DO_SCRIBBLE); SZONE_MAGAZINE_PTR_LOCK(small_mag_ptr); OSAtomicDecrement32Barrier(&(node->pinned_to_depot)); small_free_list_add_ptr(rack, small_mag_ptr, ptr, fmsize); } } } #endif // CONFIG_AGGRESSIVE_MADVISE || CONFIG_RECIRC_DEPOT #if CONFIG_RECIRC_DEPOT static region_t small_free_try_depot_unmap_no_lock(rack_t *rack, magazine_t *depot_ptr, region_trailer_t *node) { if (0 < node->bytes_used || 0 < node->pinned_to_depot || depot_ptr->recirculation_entries < recirc_retained_regions) { return NULL; } // disconnect first node from Depot recirc_list_extract(rack, depot_ptr, node); // Iterate the region pulling its free entries off the (locked) Depot's free list region_t sparse_region = SMALL_REGION_FOR_PTR(node); int objects_in_use = small_free_detach_region(rack, depot_ptr, sparse_region); if (0 == objects_in_use) { // Invalidate the hash table entry for this region with HASHRING_REGION_DEALLOCATED. // Using HASHRING_REGION_DEALLOCATED preserves the collision chain, using HASHRING_OPEN_ENTRY (0) would not. rgnhdl_t pSlot = hash_lookup_region_no_lock(rack->region_generation->hashed_regions, rack->region_generation->num_regions_allocated, rack->region_generation->num_regions_allocated_shift, sparse_region); if (NULL == pSlot) { malloc_zone_error(rack->debug_flags, true, "small_free_try_depot_unmap_no_lock hash lookup failed: %p\n", sparse_region); return NULL; } *pSlot = HASHRING_REGION_DEALLOCATED; depot_ptr->num_bytes_in_magazine -= SMALL_REGION_PAYLOAD_BYTES; // Atomically increment num_regions_dealloc #ifdef __LP64___ OSAtomicIncrement64(&rack->num_regions_dealloc); #else OSAtomicIncrement32((int32_t *)&rack->num_regions_dealloc); #endif // Caller will transfer ownership of the region back to the OS with no locks held MAGMALLOC_DEALLOCREGION(SMALL_SZONE_FROM_RACK(rack), (void *)sparse_region, (int)SMALL_REGION_SIZE); // DTrace USDT Probe return sparse_region; } else { malloc_zone_error(rack->debug_flags, true, "small_free_try_depot_unmap_no_lock objects_in_use not zero: %d\n", objects_in_use); return NULL; } } static boolean_t small_free_do_recirc_to_depot(rack_t *rack, magazine_t *small_mag_ptr, mag_index_t mag_index) { // The entire magazine crossed the "emptiness threshold". Transfer a region // from this magazine to the Depot. Choose a region that itself has crossed the emptiness threshold (i.e // is at least fraction "f" empty.) Such a region will be marked "suitable" on the recirculation list. region_trailer_t *node = small_mag_ptr->firstNode; while (node && (!node->recirc_suitable || node->pinned_to_depot)) { node = node->next; } if (NULL == node) { #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "*** small_free_do_recirc_to_depot end of list\n"); #endif return TRUE; // Caller must SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); } region_t sparse_region = SMALL_REGION_FOR_PTR(node); // Deal with unclaimed memory -- mag_bytes_free_at_end or mag_bytes_free_at start if (sparse_region == small_mag_ptr->mag_last_region && (small_mag_ptr->mag_bytes_free_at_end || small_mag_ptr->mag_bytes_free_at_start)) { small_finalize_region(rack, small_mag_ptr); } // disconnect "suitable" node from magazine recirc_list_extract(rack, small_mag_ptr, node); // Iterate the region pulling its free entries off its (locked) magazine's free list int objects_in_use = small_free_detach_region(rack, small_mag_ptr, sparse_region); magazine_t *depot_ptr = &(rack->magazines[DEPOT_MAGAZINE_INDEX]); // hand over the region to the (locked) Depot SZONE_MAGAZINE_PTR_LOCK(depot_ptr); // this will cause small_free_list_add_ptr called by small_free_reattach_region to use // the depot as its target magazine, rather than magazine formerly associated with sparse_region MAGAZINE_INDEX_FOR_SMALL_REGION(sparse_region) = DEPOT_MAGAZINE_INDEX; node->pinned_to_depot = 0; // Iterate the region putting its free entries on Depot's free list size_t bytes_inplay = small_free_reattach_region(rack, depot_ptr, sparse_region); small_mag_ptr->mag_num_bytes_in_objects -= bytes_inplay; small_mag_ptr->num_bytes_in_magazine -= SMALL_REGION_PAYLOAD_BYTES; small_mag_ptr->mag_num_objects -= objects_in_use; SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); // Unlock the originating magazine depot_ptr->mag_num_bytes_in_objects += bytes_inplay; depot_ptr->num_bytes_in_magazine += SMALL_REGION_PAYLOAD_BYTES; depot_ptr->mag_num_objects += objects_in_use; // connect to Depot as last node recirc_list_splice_last(rack, depot_ptr, node); MAGMALLOC_RECIRCREGION(SMALL_SZONE_FROM_RACK(rack), (int)mag_index, (void *)sparse_region, SMALL_REGION_SIZE, (int)BYTES_USED_FOR_SMALL_REGION(sparse_region)); // DTrace USDT Probe #if !CONFIG_AGGRESSIVE_MADVISE // Mark free'd dirty pages with MADV_FREE to reduce memory pressure small_free_scan_madvise_free(rack, depot_ptr, sparse_region); #endif // If the region is entirely empty vm_deallocate() it outside the depot lock region_t r_dealloc = small_free_try_depot_unmap_no_lock(rack, depot_ptr, node); SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); if (r_dealloc) { mvm_deallocate_pages(r_dealloc, SMALL_REGION_SIZE, 0); } return FALSE; // Caller need not unlock the originating magazine } static MALLOC_INLINE boolean_t small_free_try_recirc_to_depot(rack_t *rack, magazine_t *small_mag_ptr, mag_index_t mag_index, region_t region, free_list_t freee, msize_t msize, void *headptr, size_t headsize) { region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(region); size_t bytes_used = node->bytes_used; /* FIXME: Would Uniprocessor benefit from recirc and MADV_FREE? */ if (rack->num_magazines == 1) { // Uniprocessor, single magazine, so no recirculation necessary /* NOTHING */ return TRUE; // Caller must do SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr) } else if (DEPOT_MAGAZINE_INDEX != mag_index) { // Emptiness discriminant if (bytes_used < DENSITY_THRESHOLD(SMALL_REGION_PAYLOAD_BYTES)) { /* Region has crossed threshold from density to sparsity. Mark it "suitable" on the * recirculation candidates list. */ node->recirc_suitable = TRUE; } else { /* After this free, we've found the region is still dense, so it must have been even more so before * the free. That implies the region is already correctly marked. Do nothing. */ } // Has the entire magazine crossed the "emptiness threshold"? If so, transfer a region // from this magazine to the Depot. Choose a region that itself has crossed the emptiness threshold (i.e // is at least fraction "f" empty.) Such a region will be marked "suitable" on the recirculation list. size_t a = small_mag_ptr->num_bytes_in_magazine; // Total bytes allocated to this magazine size_t u = small_mag_ptr->mag_num_bytes_in_objects; // In use (malloc'd) from this magaqzine if (a - u > ((3 * SMALL_REGION_PAYLOAD_BYTES) / 2) && u < DENSITY_THRESHOLD(a)) { return small_free_do_recirc_to_depot(rack, small_mag_ptr, mag_index); } } else { #if !CONFIG_AGGRESSIVE_MADVISE // We are free'ing into the depot, so madvise as we do so unless we were madvising every incoming // allocation anyway. small_madvise_free_range_no_lock(rack, small_mag_ptr, region, freee, msize, headptr, headsize); #endif if (0 < bytes_used || 0 < node->pinned_to_depot) { /* Depot'd region is still live. Leave it in place on the Depot's recirculation list * so as to avoid thrashing between the Depot's free list and a magazines's free list * with detach_region/reattach_region */ } else { /* Depot'd region is just now empty. Consider return to OS. */ region_t r_dealloc = small_free_try_depot_unmap_no_lock(rack, small_mag_ptr, node); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); if (r_dealloc) { mvm_deallocate_pages(r_dealloc, SMALL_REGION_SIZE, 0); } return FALSE; // Caller need not unlock } } return TRUE; // Caller must do SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr) } #endif // CONFIG_RECIRC_DEPOT static MALLOC_INLINE boolean_t small_free_no_lock(rack_t *rack, magazine_t *small_mag_ptr, mag_index_t mag_index, region_t region, void *ptr, msize_t msize) { msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr); unsigned index = SMALL_META_INDEX_FOR_PTR(ptr); size_t original_size = SMALL_BYTES_FOR_MSIZE(msize); unsigned char *next_block = ((unsigned char *)ptr + original_size); msize_t next_index = index + msize; MALLOC_TRACE(TRACE_small_free, (uintptr_t)rack, (uintptr_t)small_mag_ptr, (uintptr_t)ptr, SMALL_BYTES_FOR_MSIZE(msize)); #if CONFIG_AGGRESSIVE_MADVISE || CONFIG_RECIRC_DEPOT void *original_ptr = ptr; #endif #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_ERR, "in small_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize); } if (!msize) { malloc_zone_error(rack->debug_flags, true, "trying to free small block that is too small in small_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize); } #endif // We try to coalesce this block with the preceeding one if (index > 0 && (meta_headers[index - 1] & SMALL_IS_FREE)) { msize_t previous_msize = meta_headers[index - 1] & ~SMALL_IS_FREE; grain_t previous_index = index - previous_msize; // Check if the metadata for the start of the block is also free. if (meta_headers[previous_index] == (previous_msize | SMALL_IS_FREE)) { void *previous_ptr = (void *)((uintptr_t)ptr - SMALL_BYTES_FOR_MSIZE(previous_msize)); free_list_t previous = small_free_list_find_by_ptr(rack, small_mag_ptr, previous_ptr, previous_msize); small_free_list_remove_ptr(rack, small_mag_ptr, previous, previous_msize); ptr = previous_ptr; small_meta_header_set_middle(meta_headers, index); // This block is now a middle block. msize += previous_msize; index -= previous_msize; } else { _os_set_crash_log_message("small free list metadata inconsistency (headers[previous] != previous size)"); __builtin_trap(); } } // Try to coalesce with this block with the next block if ((next_block < SMALL_REGION_END(region)) && (meta_headers[next_index] & SMALL_IS_FREE)) { msize_t next_msize = meta_headers[next_index] & ~SMALL_IS_FREE; free_list_t next = small_free_list_find_by_ptr(rack, small_mag_ptr, next_block, next_msize); small_free_list_remove_ptr(rack, small_mag_ptr, next, next_msize); msize += next_msize; } if (rack->debug_flags & MALLOC_DO_SCRIBBLE) { if (!msize) { malloc_zone_error(rack->debug_flags, true, "incorrect size information for %p - block header was damaged\n", ptr); } else { memset(ptr, SCRABBLE_BYTE, SMALL_BYTES_FOR_MSIZE(msize)); } } free_list_t freee = small_free_list_add_ptr(rack, small_mag_ptr, ptr, msize); // use original_size and not msize to avoid double counting the coalesced blocks small_mag_ptr->mag_num_bytes_in_objects -= original_size; small_mag_ptr->mag_num_objects--; // Update this region's bytes in use count region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(region); size_t bytes_used = node->bytes_used - original_size; node->bytes_used = (unsigned int)bytes_used; #if CONFIG_AGGRESSIVE_MADVISE small_madvise_free_range_no_lock(rack, small_mag_ptr, region, freee, msize, original_ptr, original_size); #endif // Caller must do SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr) if this function // returns TRUE. boolean_t needs_unlock = TRUE; #if CONFIG_RECIRC_DEPOT needs_unlock = small_free_try_recirc_to_depot(rack, small_mag_ptr, mag_index, region, freee, msize, original_ptr, original_size); #endif return needs_unlock; } // Allocates from the last region or a freshly allocated region static void * small_malloc_from_region_no_lock(rack_t *rack, magazine_t *small_mag_ptr, mag_index_t mag_index, msize_t msize, void *aligned_address) { void *ptr; // Before anything we transform the mag_bytes_free_at_end or mag_bytes_free_at_start - if any - to a regular free block /* FIXME: last_block needs to be coalesced with previous entry if free, */ if (small_mag_ptr->mag_bytes_free_at_end || small_mag_ptr->mag_bytes_free_at_start) { small_finalize_region(rack, small_mag_ptr); } // Tag the region at "aligned_address" as belonging to us, // and so put it under the protection of the magazine lock we are holding. // Do this before advertising "aligned_address" on the hash ring(!) MAGAZINE_INDEX_FOR_SMALL_REGION(aligned_address) = mag_index; // Insert the new region into the hash ring rack_region_insert(rack, (region_t)aligned_address); small_mag_ptr->mag_last_region = aligned_address; BYTES_USED_FOR_SMALL_REGION(aligned_address) = SMALL_BYTES_FOR_MSIZE(msize); #if CONFIG_ASLR_INTERNAL int offset_msize = malloc_entropy[1] & SMALL_ENTROPY_MASK; #if DEBUG_MALLOC if (getenv("MallocASLRForce")) { offset_msize = strtol(getenv("MallocASLRForce"), NULL, 0) & SMALL_ENTROPY_MASK; } if (getenv("MallocASLRPrint")) { malloc_report(ASL_LEVEL_INFO, "Region: %p offset: %d\n", aligned_address, offset_msize); } #endif #else int offset_msize = 0; #endif ptr = (void *)((uintptr_t)aligned_address + SMALL_BYTES_FOR_MSIZE(offset_msize)); small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr), offset_msize, msize); small_mag_ptr->mag_num_objects++; small_mag_ptr->mag_num_bytes_in_objects += SMALL_BYTES_FOR_MSIZE(msize); small_mag_ptr->num_bytes_in_magazine += SMALL_REGION_PAYLOAD_BYTES; // add a big free block at the end small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr), offset_msize + msize, NUM_SMALL_BLOCKS - msize - offset_msize); small_mag_ptr->mag_bytes_free_at_end = SMALL_BYTES_FOR_MSIZE(NUM_SMALL_BLOCKS - msize - offset_msize); #if CONFIG_ASLR_INTERNAL // add a big free block at the start small_mag_ptr->mag_bytes_free_at_start = SMALL_BYTES_FOR_MSIZE(offset_msize); if (offset_msize) { small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr), 0, offset_msize); } #else small_mag_ptr->mag_bytes_free_at_start = 0; #endif // connect to magazine as last node recirc_list_splice_last(rack, small_mag_ptr, REGION_TRAILER_FOR_SMALL_REGION(aligned_address)); return ptr; } void * small_memalign(szone_t *szone, size_t alignment, size_t size, size_t span) { msize_t mspan = SMALL_MSIZE_FOR_BYTES(span + SMALL_QUANTUM - 1); void *p = small_malloc_should_clear(&szone->small_rack, mspan, 0); if (NULL == p) { return NULL; } size_t offset = ((uintptr_t)p) & (alignment - 1); // p % alignment size_t pad = (0 == offset) ? 0 : alignment - offset; // p + pad achieves desired alignment msize_t msize = SMALL_MSIZE_FOR_BYTES(size + SMALL_QUANTUM - 1); msize_t mpad = SMALL_MSIZE_FOR_BYTES(pad + SMALL_QUANTUM - 1); msize_t mwaste = mspan - msize - mpad; // excess blocks if (mpad > 0) { void *q = (void *)(((uintptr_t)p) + pad); // Mark q as block header and in-use, thus creating two blocks. magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(szone->small_rack.magazines, REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(p)), MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(p))); small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(p), SMALL_META_INDEX_FOR_PTR(p), mpad); small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(q), SMALL_META_INDEX_FOR_PTR(q), msize + mwaste); small_mag_ptr->mag_num_objects++; SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); // Give up mpad blocks beginning at p to the small free list free_small(&szone->small_rack, p, SMALL_REGION_FOR_PTR(p), SMALL_BYTES_FOR_MSIZE(mpad)); p = q; // advance p to the desired alignment } if (mwaste > 0) { void *q = (void *)(((uintptr_t)p) + SMALL_BYTES_FOR_MSIZE(msize)); // Mark q as block header and in-use, thus creating two blocks. magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(szone->small_rack.magazines, REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(p)), MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(p))); small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(p), SMALL_META_INDEX_FOR_PTR(p), msize); small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(q), SMALL_META_INDEX_FOR_PTR(q), mwaste); small_mag_ptr->mag_num_objects++; SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); // Give up mwaste blocks beginning at q to the small free list free_small(&szone->small_rack, q, SMALL_REGION_FOR_PTR(q), SMALL_BYTES_FOR_MSIZE(mwaste)); } return p; // p has the desired size and alignment, and can later be free()'d } boolean_t small_claimed_address(rack_t *rack, void *ptr) { region_t r = small_region_for_ptr_no_lock(rack, ptr); return r && ptr < (void *)SMALL_REGION_END(r); } void * small_try_shrink_in_place(rack_t *rack, void *ptr, size_t old_size, size_t new_good_size) { msize_t new_msize = SMALL_MSIZE_FOR_BYTES(new_good_size); msize_t mshrinkage = SMALL_MSIZE_FOR_BYTES(old_size) - new_msize; if (mshrinkage) { void *q = (void *)((uintptr_t)ptr + SMALL_BYTES_FOR_MSIZE(new_msize)); magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(rack->magazines, REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)), MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr))); // Mark q as block header and in-use, thus creating two blocks. small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr), SMALL_META_INDEX_FOR_PTR(ptr), new_msize); small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(q), SMALL_META_INDEX_FOR_PTR(q), mshrinkage); small_mag_ptr->mag_num_objects++; SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); free_small(rack, q, SMALL_REGION_FOR_PTR(q), 0); } return ptr; } boolean_t small_try_realloc_in_place(rack_t *rack, void *ptr, size_t old_size, size_t new_size) { // returns 1 on success msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr); unsigned index; msize_t old_msize, new_msize; unsigned next_index; void *next_block; msize_t next_msize_and_free; boolean_t is_free; msize_t next_msize, leftover_msize; void *leftover; index = SMALL_META_INDEX_FOR_PTR(ptr); old_msize = SMALL_MSIZE_FOR_BYTES(old_size); new_msize = SMALL_MSIZE_FOR_BYTES(new_size + SMALL_QUANTUM - 1); next_index = index + old_msize; if (next_index >= NUM_SMALL_BLOCKS) { return 0; } next_block = (char *)ptr + old_size; #if DEBUG_MALLOC if ((uintptr_t)next_block & (SMALL_QUANTUM - 1)) { malloc_zone_error(rack->debug_flags, true, "internal invariant broken in realloc(next_block) for %p\n", next_block); } if (meta_headers[index] != old_msize) { malloc_report(ASL_LEVEL_ERR, "*** small_try_realloc_in_place incorrect old %d %d\n", meta_headers[index], old_msize); } #endif magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(rack->magazines, REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)), MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr))); if (DEPOT_MAGAZINE_INDEX == MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr))) { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); return 0; } msize_t coalesced_msize = new_msize - old_msize; #if CONFIG_SMALL_CACHE void *last_free_ptr = small_mag_ptr->mag_last_free; msize_t last_free_msize = small_mag_ptr->mag_last_free_msize; if (last_free_ptr == next_block && old_msize + last_free_msize >= new_msize) { /* * There is a block in mag_last_free and it's immediately after * this block and it's large enough. We can use some or all of it. */ leftover_msize = last_free_msize - coalesced_msize; if (leftover_msize) { small_mag_ptr->mag_last_free_msize -= coalesced_msize; small_mag_ptr->mag_last_free += new_size - old_size; // The block in mag_last_free is still marked as header and in-use, so copy that // state to the block that remains. The state for the block that we're going to // use is adjusted by the small_meta_header_set_middle() call below. small_meta_header_set_in_use(meta_headers, index + new_msize, leftover_msize); } else { // Using the whole block. small_mag_ptr->mag_last_free = NULL; small_mag_ptr->mag_last_free_msize = 0; small_mag_ptr->mag_last_free_rgn = NULL; } small_meta_header_set_in_use(meta_headers, index, new_msize); small_meta_header_set_middle(meta_headers, next_index); } else { #endif // CONFIG_SMALL_CACHE /* * Try to expand into unused space immediately after this block. */ msize_t unused_msize = SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_end); void *unused_start = SMALL_REGION_END(SMALL_REGION_FOR_PTR(ptr)) - small_mag_ptr->mag_bytes_free_at_end; if (small_mag_ptr->mag_last_region == SMALL_REGION_FOR_PTR(ptr) && coalesced_msize < unused_msize && unused_start == ptr + old_size) { // Extend the in-use for this block to the new size small_meta_header_set_in_use(meta_headers, index, new_msize); // Clear the in-use size for the start of the area we extended into small_meta_header_set_middle(meta_headers, next_index); // Reduce mag_bytes_free_at_end and update its in-use size. small_mag_ptr->mag_bytes_free_at_end -= SMALL_BYTES_FOR_MSIZE(coalesced_msize); small_meta_header_set_in_use(meta_headers, index + new_msize, SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_end)); } else { /* * Look for a free block immediately afterwards. If it's large enough, we can consume (part of) * it. */ next_msize_and_free = meta_headers[next_index]; is_free = next_msize_and_free & SMALL_IS_FREE; if (!is_free) { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); return 0; // next_block is in use; } next_msize = next_msize_and_free & ~SMALL_IS_FREE; if (old_msize + next_msize < new_msize) { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); return 0; // even with next block, not enough } // The following block is big enough; pull it from its freelist and chop off enough to satisfy // our needs. free_list_t freee = small_free_list_find_by_ptr(rack, small_mag_ptr, next_block, next_msize); small_free_list_remove_ptr(rack, small_mag_ptr, freee, next_msize); small_meta_header_set_middle(meta_headers, next_index); leftover_msize = old_msize + next_msize - new_msize; if (leftover_msize) { /* there's some left, so put the remainder back */ leftover = (unsigned char *)ptr + SMALL_BYTES_FOR_MSIZE(new_msize); small_free_list_add_ptr(rack, small_mag_ptr, leftover, leftover_msize); } small_meta_header_set_in_use(meta_headers, index, new_msize); } #if CONFIG_SMALL_CACHE } #endif // CONFIG_SMALL_CACHE #if DEBUG_MALLOC if (SMALL_BYTES_FOR_MSIZE(new_msize) > szone->large_threshold) { malloc_report(ASL_LEVEL_ERR, "*** realloc in place for %p exceeded msize=%d\n", new_msize); } if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in small_try_realloc_in_place(), ptr=%p, msize=%d\n", ptr, *SMALL_METADATA_FOR_PTR(ptr)); } #endif small_mag_ptr->mag_num_bytes_in_objects += SMALL_BYTES_FOR_MSIZE(new_msize - old_msize); // Update this region's bytes in use count region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)); size_t bytes_used = node->bytes_used + SMALL_BYTES_FOR_MSIZE(new_msize - old_msize); node->bytes_used = (unsigned int)bytes_used; // Emptiness discriminant if (bytes_used < DENSITY_THRESHOLD(SMALL_REGION_PAYLOAD_BYTES)) { /* After this reallocation the region is still sparse, so it must have been even more so before * the reallocation. That implies the region is already correctly marked. Do nothing. */ } else { /* Region has crossed threshold from sparsity to density. Mark it not "suitable" on the * recirculation candidates list. */ node->recirc_suitable = FALSE; } SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); return 1; } static char *small_check_fail_msg = "check: incorrect small region "; #define SMALL_CHECK_FAIL(fmt, ...) \ malloc_zone_check_fail(small_check_fail_msg, \ "%ld, counter=%d\n" fmt, region_index, counter, __VA_ARGS__); boolean_t small_check_region(rack_t *rack, region_t region, size_t region_index, unsigned counter) { unsigned char *ptr = SMALL_REGION_ADDRESS(region); msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr); unsigned char *region_end = SMALL_REGION_END(region); msize_t prev_free = 0; unsigned index; msize_t msize_and_free; msize_t msize; free_list_t free_head, previous, next; msize_t *follower; mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)); magazine_t *small_mag_ptr = &(rack->magazines[mag_index]); // Assumes locked CHECK_MAGAZINE_PTR_LOCKED(szone, small_mag_ptr, __PRETTY_FUNCTION__); if (region == small_mag_ptr->mag_last_region) { ptr += small_mag_ptr->mag_bytes_free_at_start; region_end -= small_mag_ptr->mag_bytes_free_at_end; } while (ptr < region_end) { index = SMALL_META_INDEX_FOR_PTR(ptr); msize_and_free = meta_headers[index]; if (!(msize_and_free & SMALL_IS_FREE)) { // block is in use msize = msize_and_free; if (!msize) { SMALL_CHECK_FAIL("*** invariant broken: null msize ptr=%p num_small_regions=%d end=%p\n", ptr, (int)rack->num_regions, region_end); return 0; } #if !CONFIG_RELAXED_INVARIANT_CHECKS if (SMALL_BYTES_FOR_MSIZE(msize) > szone->large_threshold) { SMALL_CHECK_FAIL("*** invariant broken for %p this small msize=%d - size is too large\n", ptr, msize_and_free); return 0; } #endif // CONFIG_RELAXED_INVARIANT_CHECKS ptr += SMALL_BYTES_FOR_MSIZE(msize); prev_free = 0; } else { // free pointer msize = msize_and_free & ~SMALL_IS_FREE; free_head = (free_list_t){ .p = ptr }; follower = (msize_t *)FOLLOWING_SMALL_PTR(ptr, msize); if (!msize) { SMALL_CHECK_FAIL("*** invariant broken for free block %p this msize=%d\n", ptr, msize); return 0; } #if !CONFIG_RELAXED_INVARIANT_CHECKS if (prev_free) { SMALL_CHECK_FAIL("*** invariant broken for %p (2 free in a row)\n", ptr); return 0; } #endif // check for possible OOB entry if needed if (small_needs_oob_free_entry(ptr, msize)) { oob_free_entry_t oob = small_oob_free_find_ptr(ptr, msize); if (oob) { free_head.oob = oob; } } previous = small_free_list_get_previous(rack, free_head); next = small_free_list_get_next(rack, free_head); if (previous.p && !SMALL_PTR_IS_FREE(small_free_list_get_ptr(rack, previous))) { SMALL_CHECK_FAIL("*** invariant broken for %p (previous %p is not a free pointer)\n", ptr, small_free_list_get_ptr(rack, previous)); return 0; } if (next.p && !SMALL_PTR_IS_FREE(small_free_list_get_ptr(rack, next))) { SMALL_CHECK_FAIL("*** invariant broken for %p (next %p is not a free pointer)\n", ptr, small_free_list_get_ptr(rack, next)); return 0; } if (SMALL_PREVIOUS_MSIZE(follower) != msize) { SMALL_CHECK_FAIL("*** invariant broken for small free %p followed by %p in region [%p-%p] " "(end marker incorrect) should be %d; in fact %d\n", ptr, follower, SMALL_REGION_ADDRESS(region), region_end, msize, SMALL_PREVIOUS_MSIZE(follower)); return 0; } ptr = (unsigned char *)follower; prev_free = SMALL_IS_FREE; } } return 1; } kern_return_t small_in_use_enumerator(task_t task, void *context, unsigned type_mask, szone_t *szone, memory_reader_t reader, vm_range_recorder_t recorder) { size_t num_regions; size_t index; region_t *regions; vm_range_t buffer[MAX_RECORDER_BUFFER]; unsigned count = 0; kern_return_t err; region_t region; vm_range_t range; vm_range_t admin_range; vm_range_t ptr_range; unsigned char *mapped_region; msize_t *block_header; unsigned block_index; unsigned block_limit; msize_t msize_and_free; msize_t msize; magazine_t *small_mag_base = NULL; region_hash_generation_t *srg_ptr; err = reader(task, (vm_address_t)szone->small_rack.region_generation, sizeof(region_hash_generation_t), (void **)&srg_ptr); if (err) { return err; } num_regions = srg_ptr->num_regions_allocated; err = reader(task, (vm_address_t)srg_ptr->hashed_regions, sizeof(region_t) * num_regions, (void **)®ions); if (err) { return err; } if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) { // Map in all active magazines. Do this outside the iteration over regions. err = reader(task, (vm_address_t)(szone->small_rack.magazines), szone->small_rack.num_magazines * sizeof(magazine_t), (void **)&small_mag_base); if (err) { return err; } } for (index = 0; index < num_regions; ++index) { region = regions[index]; if (HASHRING_OPEN_ENTRY != region && HASHRING_REGION_DEALLOCATED != region) { range.address = (vm_address_t)SMALL_REGION_ADDRESS(region); range.size = SMALL_REGION_SIZE; if (type_mask & MALLOC_ADMIN_REGION_RANGE_TYPE) { admin_range.address = range.address + SMALL_METADATA_START; admin_range.size = SMALL_METADATA_SIZE; recorder(task, context, MALLOC_ADMIN_REGION_RANGE_TYPE, &admin_range, 1); } if (type_mask & (MALLOC_PTR_REGION_RANGE_TYPE | MALLOC_ADMIN_REGION_RANGE_TYPE)) { ptr_range.address = range.address; ptr_range.size = NUM_SMALL_BLOCKS * SMALL_QUANTUM; recorder(task, context, MALLOC_PTR_REGION_RANGE_TYPE, &ptr_range, 1); } if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) { vm_address_t mag_last_free = 0; msize_t mag_last_free_msize = 0; err = reader(task, range.address, range.size, (void **)&mapped_region); if (err) { return err; } mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(mapped_region); magazine_t *small_mag_ptr = small_mag_base + mag_index; if (DEPOT_MAGAZINE_INDEX != mag_index) { mag_last_free = (uintptr_t)small_mag_ptr->mag_last_free; mag_last_free_msize = small_mag_ptr->mag_last_free_msize; } else { for (mag_index = 0; mag_index < szone->small_rack.num_magazines; mag_index++) { if ((void *)range.address == (small_mag_base + mag_index)->mag_last_free_rgn) { mag_last_free = (uintptr_t)(small_mag_base + mag_index)->mag_last_free; mag_last_free_msize = (small_mag_base + mag_index)->mag_last_free_msize; } } } block_header = (msize_t *)(mapped_region + SMALL_METADATA_START + sizeof(region_trailer_t)); block_index = 0; block_limit = NUM_SMALL_BLOCKS; if (region == small_mag_ptr->mag_last_region) { block_index += SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_start); block_limit -= SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_end); } while (block_index < block_limit) { msize_and_free = block_header[block_index]; msize = msize_and_free & ~SMALL_IS_FREE; if (!(msize_and_free & SMALL_IS_FREE) && range.address + SMALL_BYTES_FOR_MSIZE(block_index) != mag_last_free) { // Block in use buffer[count].address = range.address + SMALL_BYTES_FOR_MSIZE(block_index); buffer[count].size = SMALL_BYTES_FOR_MSIZE(msize); count++; if (count >= MAX_RECORDER_BUFFER) { recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count); count = 0; } } if (!msize) { return KERN_FAILURE; // Somethings amiss. Avoid looping at this block_index. } block_index += msize; } if (count) { recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count); count = 0; } } } } return 0; } static void * small_malloc_from_free_list(rack_t *rack, magazine_t *small_mag_ptr, mag_index_t mag_index, msize_t msize) { msize_t this_msize; grain_t slot = SMALL_FREE_SLOT_FOR_MSIZE(rack, msize); free_list_t *free_list = small_mag_ptr->mag_free_list; free_list_t *the_slot = free_list + slot; free_list_t *limit; unsigned bitmap; msize_t leftover_msize; void *leftover_ptr; void *ptr; // Assumes we've locked the region CHECK_MAGAZINE_PTR_LOCKED(szone, small_mag_ptr, __PRETTY_FUNCTION__); // Look for an exact match by checking the freelist for this msize. if (small_free_list_get_ptr(rack, *the_slot)) { ptr = small_free_list_get_ptr(rack, *the_slot); this_msize = msize; small_free_list_remove_ptr(rack, small_mag_ptr, *the_slot, msize); goto return_small_alloc; } // Mask off the bits representing slots holding free blocks smaller than // the size we need. If there are no larger free blocks, try allocating // from the free space at the end of the small region. if (SMALL_FREELIST_BITMAP_WORDS(rack) > 1) { // BITMAPN_CTZ implementation unsigned idx = slot >> 5; bitmap = 0; unsigned mask = ~((1 << (slot & 31)) - 1); for (; idx < SMALL_FREELIST_BITMAP_WORDS(rack); ++idx) { bitmap = small_mag_ptr->mag_bitmap[idx] & mask; if (bitmap != 0) { break; } mask = ~0U; } // Check for fallthrough: No bits set in bitmap if ((bitmap == 0) && (idx == SMALL_FREELIST_BITMAP_WORDS(rack))) { goto try_small_from_end; } // Start looking at the first set bit, plus 32 bits for every word of // zeroes or entries that were too small. slot = BITMAP32_CTZ((&bitmap)) + (idx * 32); } else { bitmap = small_mag_ptr->mag_bitmap[0] & ~((1 << slot) - 1); if (!bitmap) { goto try_small_from_end; } slot = BITMAP32_CTZ((&bitmap)); } // FIXME: Explain use of - 1 here, last slot has special meaning limit = free_list + SMALL_FREE_SLOT_COUNT(rack) - 1; free_list += slot; // Attempt to pull off the free_list slot that we now think is full. if ((ptr = small_free_list_get_ptr(rack, *free_list))) { this_msize = SMALL_PTR_SIZE(ptr); small_free_list_remove_ptr(rack, small_mag_ptr, *free_list, this_msize); goto add_leftover_and_proceed; } #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "in small_malloc_from_free_list(), mag_bitmap out of sync, slot=%d\n", slot); #endif try_small_from_end: // Let's see if we can use small_mag_ptr->mag_bytes_free_at_end if (small_mag_ptr->mag_bytes_free_at_end >= SMALL_BYTES_FOR_MSIZE(msize)) { ptr = SMALL_REGION_END(small_mag_ptr->mag_last_region) - small_mag_ptr->mag_bytes_free_at_end; small_mag_ptr->mag_bytes_free_at_end -= SMALL_BYTES_FOR_MSIZE(msize); if (small_mag_ptr->mag_bytes_free_at_end) { // let's mark this block as in use to serve as boundary small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr), SMALL_META_INDEX_FOR_PTR((unsigned char *)ptr + SMALL_BYTES_FOR_MSIZE(msize)), SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_end)); } this_msize = msize; goto return_small_alloc; } #if CONFIG_ASLR_INTERNAL // Try from start if nothing left at end if (small_mag_ptr->mag_bytes_free_at_start >= SMALL_BYTES_FOR_MSIZE(msize)) { ptr = SMALL_REGION_ADDRESS(small_mag_ptr->mag_last_region) + small_mag_ptr->mag_bytes_free_at_start - SMALL_BYTES_FOR_MSIZE(msize); small_mag_ptr->mag_bytes_free_at_start -= SMALL_BYTES_FOR_MSIZE(msize); if (small_mag_ptr->mag_bytes_free_at_start) { // let's mark this block as in use to serve as boundary small_meta_header_set_in_use( SMALL_META_HEADER_FOR_PTR(ptr), 0, SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_start)); } this_msize = msize; goto return_small_alloc; } #endif return NULL; add_leftover_and_proceed: if (this_msize > msize) { leftover_msize = this_msize - msize; leftover_ptr = (unsigned char *)ptr + SMALL_BYTES_FOR_MSIZE(msize); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in small_malloc_from_free_list(), adding leftover ptr=%p, this_msize=%d\n", ptr, this_msize); } #endif small_free_list_add_ptr(rack, small_mag_ptr, leftover_ptr, leftover_msize); this_msize = msize; } return_small_alloc: small_mag_ptr->mag_num_objects++; small_mag_ptr->mag_num_bytes_in_objects += SMALL_BYTES_FOR_MSIZE(this_msize); // Update this region's bytes in use count region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)); size_t bytes_used = node->bytes_used + SMALL_BYTES_FOR_MSIZE(this_msize); node->bytes_used = (unsigned int)bytes_used; // Emptiness discriminant if (bytes_used < DENSITY_THRESHOLD(SMALL_REGION_PAYLOAD_BYTES)) { /* After this allocation the region is still sparse, so it must have been even more so before * the allocation. That implies the region is already correctly marked. Do nothing. */ } else { /* Region has crossed threshold from sparsity to density. Mark in not "suitable" on the * recirculation candidates list. */ node->recirc_suitable = FALSE; } #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in small_malloc_from_free_list(), ptr=%p, this_msize=%d, msize=%d\n", ptr, this_msize, msize); } #endif small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr), SMALL_META_INDEX_FOR_PTR(ptr), this_msize); return ptr; } void * small_malloc_should_clear(rack_t *rack, msize_t msize, boolean_t cleared_requested) { void *ptr; mag_index_t mag_index = small_mag_get_thread_index() % rack->num_magazines; magazine_t *small_mag_ptr = &(rack->magazines[mag_index]); MALLOC_TRACE(TRACE_small_malloc, (uintptr_t)rack, SMALL_BYTES_FOR_MSIZE(msize), (uintptr_t)small_mag_ptr, cleared_requested); SZONE_MAGAZINE_PTR_LOCK(small_mag_ptr); #if CONFIG_SMALL_CACHE ptr = small_mag_ptr->mag_last_free; if (small_mag_ptr->mag_last_free_msize == msize) { // we have a winner small_mag_ptr->mag_last_free = NULL; small_mag_ptr->mag_last_free_msize = 0; small_mag_ptr->mag_last_free_rgn = NULL; SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); if (cleared_requested) { memset(ptr, 0, SMALL_BYTES_FOR_MSIZE(msize)); } return ptr; } #endif /* CONFIG_SMALL_CACHE */ while (1) { ptr = small_malloc_from_free_list(rack, small_mag_ptr, mag_index, msize); if (ptr) { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); if (cleared_requested) { memset(ptr, 0, SMALL_BYTES_FOR_MSIZE(msize)); } return ptr; } if (small_get_region_from_depot(rack, small_mag_ptr, mag_index, msize)) { ptr = small_malloc_from_free_list(rack, small_mag_ptr, mag_index, msize); if (ptr) { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); if (cleared_requested) { memset(ptr, 0, SMALL_BYTES_FOR_MSIZE(msize)); } return ptr; } } // The magazine is exhausted. A new region (heap) must be allocated to satisfy this call to malloc(). // The allocation, an mmap() system call, will be performed outside the magazine spin locks by the first // thread that suffers the exhaustion. That thread sets "alloc_underway" and enters a critical section. // Threads arriving here later are excluded from the critical section, yield the CPU, and then retry the // allocation. After some time the magazine is resupplied, the original thread leaves with its allocation, // and retry-ing threads succeed in the code just above. if (!small_mag_ptr->alloc_underway) { void *fresh_region; // time to create a new region (do this outside the magazine lock) small_mag_ptr->alloc_underway = TRUE; OSMemoryBarrier(); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); fresh_region = mvm_allocate_pages_securely(SMALL_REGION_SIZE, SMALL_BLOCKS_ALIGN, VM_MEMORY_MALLOC_SMALL, rack->debug_flags); SZONE_MAGAZINE_PTR_LOCK(small_mag_ptr); // DTrace USDT Probe MAGMALLOC_ALLOCREGION(SMALL_SZONE_FROM_RACK(rack), (int)mag_index, fresh_region, SMALL_REGION_SIZE); if (!fresh_region) { // out of memory! small_mag_ptr->alloc_underway = FALSE; OSMemoryBarrier(); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); return NULL; } ptr = small_malloc_from_region_no_lock(rack, small_mag_ptr, mag_index, msize, fresh_region); // we don't clear because this freshly allocated space is pristine small_mag_ptr->alloc_underway = FALSE; OSMemoryBarrier(); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); return ptr; } else { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); yield(); SZONE_MAGAZINE_PTR_LOCK(small_mag_ptr); } } /* NOTREACHED */ } size_t small_size(rack_t *rack, const void *ptr) { if (small_region_for_ptr_no_lock(rack, ptr)) { if (SMALL_META_INDEX_FOR_PTR(ptr) >= NUM_SMALL_BLOCKS) { return 0; } msize_t msize_and_free = *SMALL_METADATA_FOR_PTR(ptr); if (msize_and_free & SMALL_IS_FREE) { return 0; } #if CONFIG_SMALL_CACHE { mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)); if (DEPOT_MAGAZINE_INDEX != mag_index) { magazine_t *small_mag_ptr = &(rack->magazines[mag_index]); if (ptr == small_mag_ptr->mag_last_free) { return 0; } } else { for (mag_index = 0; mag_index < rack->num_magazines; mag_index++) { magazine_t *small_mag_ptr = &(rack->magazines[mag_index]); if (ptr == small_mag_ptr->mag_last_free) { return 0; } } } } #endif return SMALL_BYTES_FOR_MSIZE(msize_and_free); } return 0; } static MALLOC_NOINLINE void free_small_botch(rack_t *rack, void *ptr) { mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)); magazine_t *small_mag_ptr = &(rack->magazines[mag_index]); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); malloc_zone_error(rack->debug_flags, true, "double free for ptr %p\n", ptr); } void free_small(rack_t *rack, void *ptr, region_t small_region, size_t known_size) { msize_t msize; mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)); magazine_t *small_mag_ptr = &(rack->magazines[mag_index]); // ptr is known to be in small_region if (known_size) { msize = SMALL_MSIZE_FOR_BYTES(known_size + SMALL_QUANTUM - 1); } else { msize = SMALL_PTR_SIZE(ptr); if (SMALL_PTR_IS_FREE(ptr)) { free_small_botch(rack, ptr); return; } } SZONE_MAGAZINE_PTR_LOCK(small_mag_ptr); #if CONFIG_SMALL_CACHE // Depot does not participate in CONFIG_SMALL_CACHE since it can't be directly malloc()'d if (DEPOT_MAGAZINE_INDEX != mag_index) { void *ptr2 = small_mag_ptr->mag_last_free; // Might be NULL msize_t msize2 = small_mag_ptr->mag_last_free_msize; region_t rgn2 = small_mag_ptr->mag_last_free_rgn; /* check that we don't already have this pointer in the cache */ if (ptr == ptr2) { free_small_botch(rack, ptr); return; } if ((rack->debug_flags & MALLOC_DO_SCRIBBLE) && msize) { memset(ptr, SCRABBLE_BYTE, SMALL_BYTES_FOR_MSIZE(msize)); } small_mag_ptr->mag_last_free = ptr; small_mag_ptr->mag_last_free_msize = msize; small_mag_ptr->mag_last_free_rgn = small_region; if (!ptr2) { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); return; } msize = msize2; ptr = ptr2; small_region = rgn2; } #endif /* CONFIG_SMALL_CACHE */ // Now in the time it took to acquire the lock, the region may have migrated // from one magazine to another. I.e. trailer->mag_index is volatile. // In which case the magazine lock we obtained (namely magazines[mag_index].mag_lock) // is stale. If so, keep on tryin' ... region_trailer_t *trailer = REGION_TRAILER_FOR_SMALL_REGION(small_region); mag_index_t refreshed_index; while (mag_index != (refreshed_index = trailer->mag_index)) { // Note assignment SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); mag_index = refreshed_index; small_mag_ptr = &(rack->magazines[mag_index]); SZONE_MAGAZINE_PTR_LOCK(small_mag_ptr); } if (small_free_no_lock(rack, small_mag_ptr, mag_index, small_region, ptr, msize)) { SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); } CHECK(szone, __PRETTY_FUNCTION__); } void print_small_free_list(rack_t *rack) { free_list_t ptr; _SIMPLE_STRING b = _simple_salloc(); mag_index_t mag_index; if (b) { _simple_sappend(b, "small free sizes:\n"); for (mag_index = -1; mag_index < rack->num_magazines; mag_index++) { grain_t slot = 0; _simple_sprintf(b, "\tMagazine %d: ", mag_index); while (slot < SMALL_FREE_SLOT_COUNT(rack)) { ptr = rack->magazines[mag_index].mag_free_list[slot]; if (small_free_list_get_ptr(rack, ptr)) { _simple_sprintf(b, "%s%y[%d]; ", (slot == SMALL_FREE_SLOT_COUNT(rack) - 1) ? ">=" : "", (slot + 1) * SMALL_QUANTUM, small_free_list_count(rack, ptr)); } slot++; } _simple_sappend(b, "\n"); } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%s\n", _simple_string(b)); _simple_sfree(b); } } void print_small_region(szone_t *szone, boolean_t verbose, region_t region, size_t bytes_at_start, size_t bytes_at_end) { unsigned counts[1024]; unsigned in_use = 0; uintptr_t start = (uintptr_t)SMALL_REGION_ADDRESS(region); uintptr_t current = start + bytes_at_start; uintptr_t limit = (uintptr_t)SMALL_REGION_END(region) - bytes_at_end; msize_t msize_and_free; msize_t msize; unsigned ci; _SIMPLE_STRING b; uintptr_t pgTot = 0; if (region == HASHRING_REGION_DEALLOCATED) { if ((b = _simple_salloc()) != NULL) { _simple_sprintf(b, "Small region [unknown address] was returned to the OS\n"); malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%s\n", _simple_string(b)); _simple_sfree(b); } return; } memset(counts, 0, sizeof(counts)); while (current < limit) { msize_and_free = *SMALL_METADATA_FOR_PTR(current); msize = msize_and_free & ~SMALL_IS_FREE; if (!msize) { malloc_report(ASL_LEVEL_ERR, "*** error with %p: msize=%d\n", (void *)current, (unsigned)msize); break; } if (!(msize_and_free & SMALL_IS_FREE)) { // block in use if (msize < 1024) { counts[msize]++; } in_use++; } else { uintptr_t pgLo = round_page_quanta(current + sizeof(free_list_t) + sizeof(msize_t)); uintptr_t pgHi = trunc_page_quanta(current + SMALL_BYTES_FOR_MSIZE(msize) - sizeof(msize_t)); if (pgLo < pgHi) { pgTot += (pgHi - pgLo); } } current += SMALL_BYTES_FOR_MSIZE(msize); } if ((b = _simple_salloc()) != NULL) { _simple_sprintf(b, "Small region [%p-%p, %y] \t", (void *)start, SMALL_REGION_END(region), (int)SMALL_REGION_SIZE); _simple_sprintf(b, "Magazine=%d \t", MAGAZINE_INDEX_FOR_SMALL_REGION(region)); _simple_sprintf(b, "Allocations in use=%d \t Bytes in use=%ly \t", in_use, BYTES_USED_FOR_SMALL_REGION(region)); if (bytes_at_end || bytes_at_start) { _simple_sprintf(b, "Untouched=%ly ", bytes_at_end + bytes_at_start); } if (DEPOT_MAGAZINE_INDEX == MAGAZINE_INDEX_FOR_SMALL_REGION(region)) { _simple_sprintf(b, "Advised MADV_FREE=%ly", pgTot); } else { _simple_sprintf(b, "Fragments subject to reclamation=%ly", pgTot); } if (verbose && in_use) { _simple_sappend(b, "\n\tSizes in use: "); for (ci = 0; ci < 1024; ci++) { if (counts[ci]) { _simple_sprintf(b, "%d[%d] ", SMALL_BYTES_FOR_MSIZE(ci), counts[ci]); } } } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%s\n", _simple_string(b)); _simple_sfree(b); } } static char *small_freelist_fail_msg = "check: small free list incorrect"; #define SMALL_FREELIST_FAIL(fmt, ...) \ malloc_zone_check_fail(small_freelist_fail_msg, \ " (slot=%u), counter=%d\n" fmt, slot, counter, __VA_ARGS__); boolean_t small_free_list_check(rack_t *rack, grain_t slot, unsigned counter) { mag_index_t mag_index; for (mag_index = -1; mag_index < rack->num_magazines; mag_index++) { magazine_t *small_mag_ptr = &(rack->magazines[mag_index]); SZONE_MAGAZINE_PTR_LOCK(small_mag_ptr); unsigned count = 0; free_list_t current = rack->magazines[mag_index].mag_free_list[slot]; free_list_t previous = (free_list_t){ .p = NULL }; msize_t msize_and_free; void *ptr = NULL; while ((ptr = small_free_list_get_ptr(rack, current))) { msize_and_free = *SMALL_METADATA_FOR_PTR(ptr); if (!(msize_and_free & SMALL_IS_FREE)) { SMALL_FREELIST_FAIL("*** in-use ptr in free list slot=%u count=%d ptr=%p\n", slot, count, ptr); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); return 0; } if (((uintptr_t)ptr) & (SMALL_QUANTUM - 1)) { SMALL_FREELIST_FAIL("*** unaligned ptr in free list slot=%u count=%d ptr=%p\n", slot, count, ptr); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); return 0; } if (!small_region_for_ptr_no_lock(rack, ptr)) { SMALL_FREELIST_FAIL("*** ptr not in szone slot=%d count=%d ptr=%p\n", slot, count, ptr); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); return 0; } if (small_free_list_get_previous(rack, current).p != previous.p) { SMALL_FREELIST_FAIL("*** previous incorrectly set slot=%u count=%d ptr=%p\n", slot, count, ptr); SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); return 0; } previous = current; current = small_free_list_get_next(rack, current); count++; } SZONE_MAGAZINE_PTR_UNLOCK(small_mag_ptr); } return 1; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_tiny.c ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" // The address and size of the block in mag_last_free are combined. These // macros abstract construction of the combined value and extraction of the // size and pointer. #define TINY_MAG_LAST_FREE_FROM_PTR_AND_MSIZE(ptr, msize) (void *)(((uintptr_t)(ptr))|((msize_t)msize)) #define TINY_PTR_FROM_MAG_LAST_FREE(x) (void *)(((uintptr_t)(x)) & ~(TINY_QUANTUM - 1)) #define TINY_MSIZE_FROM_MAG_LAST_FREE(x) (msize_t)(((uintptr_t)(x)) & (TINY_QUANTUM - 1)) // Adjusts the pointer part of mag_last_free by a given amount in bytes. Must be // a multiple of the quantum size (not checked). #define TINY_MAG_LAST_FREE_PTR_ADJUST_PTR(x, size) (x) = ((void *)(x) + (size)) // Decrements the size part of mag_last_free by a given msize value. Must not // reduce the msize part below zero (not checked). #define TINY_MAG_LAST_FREE_PTR_DEC_MSIZE(x, msize_delta) (x) = ((void *)(x) - (msize_delta)) static MALLOC_INLINE MALLOC_ALWAYS_INLINE mag_index_t tiny_mag_get_thread_index(void) { #if CONFIG_TINY_USES_HYPER_SHIFT if (os_likely(_os_cpu_number_override == -1)) { return _os_cpu_number() >> hyper_shift; } else { return _os_cpu_number_override >> hyper_shift; } #else // CONFIG_SMALL_USES_HYPER_SHIFT if (os_likely(_os_cpu_number_override == -1)) { return _os_cpu_number(); } else { return _os_cpu_number_override; } #endif // CONFIG_SMALL_USES_HYPER_SHIFT } /* * Get the size of the previous free block, which is stored in the last two * bytes of the block. If the previous block is not free, then the result is * undefined. */ static msize_t get_tiny_previous_free_msize(const void *ptr) { // check whether the previous block is in the tiny region and a block header // if so, then the size of the previous block is one, and there is no stored // size. if (ptr != TINY_REGION_FOR_PTR(ptr)) { void *prev_block = (void *)((uintptr_t)ptr - TINY_QUANTUM); uint32_t *prev_header = TINY_BLOCK_HEADER_FOR_PTR(prev_block); msize_t prev_index = TINY_INDEX_FOR_PTR(prev_block); if (BITARRAY_BIT(prev_header, prev_index)) { return 1; } return TINY_PREVIOUS_MSIZE(ptr); } // don't read possibly unmapped memory before the beginning of the region return 0; } static MALLOC_INLINE void set_tiny_meta_header_in_use(const void *ptr, msize_t msize) { uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr); msize_t index = TINY_INDEX_FOR_PTR(ptr); msize_t clr_msize = msize - 1; msize_t midx = (index >> 5) << 1; uint32_t val = (1 << (index & 31)); #if DEBUG_MALLOC if (msize >= NUM_TINY_SLOTS) { malloc_report(ASL_LEVEL_ERR, "set_tiny_meta_header_in_use() invariant broken %p %d\n", ptr, msize); } if ((unsigned)index + (unsigned)msize > 0x10000) { malloc_report(ASL_LEVEL_ERR, "set_tiny_meta_header_in_use() invariant broken (2) %p %d\n", ptr, msize); } #endif block_header[midx] |= val; // BITARRAY_SET(block_header, index); block_header[midx + 1] |= val; // BITARRAY_SET(in_use, index); // bitarray_mclr(block_header, index, end_bit); // bitarray_mclr(in_use, index, end_bit); index++; midx = (index >> 5) << 1; unsigned start = index & 31; unsigned end = start + clr_msize; #if defined(__LP64__) if (end > 63) { unsigned mask0 = (0xFFFFFFFFU >> (31 - start)) >> 1; unsigned mask1 = (0xFFFFFFFFU << (end - 64)); block_header[midx + 0] &= mask0; // clear header block_header[midx + 1] &= mask0; // clear in_use block_header[midx + 2] = 0; // clear header block_header[midx + 3] = 0; // clear in_use block_header[midx + 4] &= mask1; // clear header block_header[midx + 5] &= mask1; // clear in_use } else #endif if (end > 31) { unsigned mask0 = (0xFFFFFFFFU >> (31 - start)) >> 1; unsigned mask1 = (0xFFFFFFFFU << (end - 32)); block_header[midx + 0] &= mask0; block_header[midx + 1] &= mask0; block_header[midx + 2] &= mask1; block_header[midx + 3] &= mask1; } else { unsigned mask = (0xFFFFFFFFU >> (31 - start)) >> 1; mask |= (0xFFFFFFFFU << end); block_header[midx + 0] &= mask; block_header[midx + 1] &= mask; } // we set the block_header bit for the following block to reaffirm next block is a block index += clr_msize; midx = (index >> 5) << 1; val = (1 << (index & 31)); block_header[midx] |= val; // BITARRAY_SET(block_header, (index+clr_msize)); #if DEBUG_MALLOC { boolean_t ff; msize_t mf; mf = get_tiny_meta_header(ptr, &ff); if (msize != mf) { malloc_report(ASL_LEVEL_INFO, "setting header for tiny in_use %p : %d\n", ptr, msize); malloc_report(ASL_LEVEL_INFO, "reading header for tiny %p : %d %d\n", ptr, mf, ff); } } #endif } static MALLOC_INLINE void set_tiny_meta_header_in_use_1(const void *ptr) // As above with msize == 1 { uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr); msize_t index = TINY_INDEX_FOR_PTR(ptr); msize_t midx = (index >> 5) << 1; uint32_t val = (1 << (index & 31)); block_header[midx] |= val; // BITARRAY_SET(block_header, index); block_header[midx + 1] |= val; // BITARRAY_SET(in_use, index); index++; midx = (index >> 5) << 1; val = (1 << (index & 31)); block_header[midx] |= val; // BITARRAY_SET(block_header, (index+clr_msize)) } static MALLOC_INLINE void set_tiny_meta_header_middle(const void *ptr) { // indicates this block is in the middle of an in use block uint32_t *block_header; uint32_t *in_use; msize_t index; block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr); in_use = TINY_INUSE_FOR_HEADER(block_header); index = TINY_INDEX_FOR_PTR(ptr); BITARRAY_CLR(block_header, index); BITARRAY_CLR(in_use, index); } static MALLOC_INLINE void set_tiny_meta_header_free(const void *ptr, msize_t msize) { // !msize is acceptable and means 65536 uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr); msize_t index = TINY_INDEX_FOR_PTR(ptr); msize_t midx = (index >> 5) << 1; uint32_t val = (1 << (index & 31)); #if DEBUG_MALLOC if ((unsigned)index + (unsigned)msize > 0x10000) { malloc_report(ASL_LEVEL_ERR, "setting header for tiny free %p msize too large: %d\n", ptr, msize); } #endif block_header[midx] |= val; // BITARRAY_SET(block_header, index); block_header[midx + 1] &= ~val; // BITARRAY_CLR(in_use, index); // mark the end of this block if msize is > 1. For msize == 0, the whole // region is free, so there is no following block. For msize == 1, there is // no space to write the size on 64 bit systems. The size for 1 quantum // blocks is computed from the metadata bitmaps. if (msize > 1) { void *follower = FOLLOWING_TINY_PTR(ptr, msize); TINY_PREVIOUS_MSIZE(follower) = msize; TINY_FREE_SIZE(ptr) = msize; } if (msize == 0) { TINY_FREE_SIZE(ptr) = msize; } #if DEBUG_MALLOC boolean_t ff; msize_t mf = get_tiny_meta_header(ptr, &ff); if ((msize != mf) || !ff) { malloc_report(ASL_LEVEL_INFO, "setting header for tiny free %p : %u\n", ptr, msize); malloc_report(ASL_LEVEL_INFO, "reading header for tiny %p : %u %u\n", ptr, mf, ff); } #endif } static MALLOC_INLINE boolean_t tiny_meta_header_is_free(const void *ptr) { uint32_t *block_header; uint32_t *in_use; msize_t index; block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr); in_use = TINY_INUSE_FOR_HEADER(block_header); index = TINY_INDEX_FOR_PTR(ptr); if (!BITARRAY_BIT(block_header, index)) { return 0; } return !BITARRAY_BIT(in_use, index); } static MALLOC_INLINE void * tiny_previous_preceding_free(void *ptr, msize_t *prev_msize) { // returns the previous block, assuming and verifying it's free uint32_t *block_header; uint32_t *in_use; msize_t index; msize_t previous_msize; msize_t previous_index; void *previous_ptr; block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr); in_use = TINY_INUSE_FOR_HEADER(block_header); index = TINY_INDEX_FOR_PTR(ptr); if (!index) { return NULL; } if ((previous_msize = get_tiny_previous_free_msize(ptr)) > index) { return NULL; } previous_index = index - previous_msize; previous_ptr = (void *)((uintptr_t)TINY_REGION_FOR_PTR(ptr) + TINY_BYTES_FOR_MSIZE(previous_index)); if (!BITARRAY_BIT(block_header, previous_index)) { return NULL; } if (BITARRAY_BIT(in_use, previous_index)) { return NULL; } if (get_tiny_free_size(previous_ptr) != previous_msize) { return NULL; } // conservative check did match true check *prev_msize = previous_msize; return previous_ptr; } /* * Adds an item to the proper free list, and also marks the meta-header of the * block properly. * Assumes szone has been locked */ static void tiny_free_list_add_ptr(rack_t *rack, magazine_t *tiny_mag_ptr, void *ptr, msize_t msize) { grain_t slot = (!msize || (msize >= NUM_TINY_SLOTS)) ? NUM_TINY_SLOTS - 1 : msize - 1; tiny_free_list_t *free_ptr = ptr; tiny_free_list_t *free_head = tiny_mag_ptr->mag_free_list[slot].p; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in %s, ptr=%p, msize=%d\n", __FUNCTION__, ptr, msize); } if (((uintptr_t)ptr) & (TINY_QUANTUM - 1)) { malloc_zone_error(rack->debug_flags, true, "tiny_free_list_add_ptr: Unaligned ptr: %p\n", ptr); } #endif set_tiny_meta_header_free(ptr, msize); if (free_head) { #if DEBUG_MALLOC if (free_list_unchecksum_ptr(szone, &free_head->previous)) { malloc_zone_error(rack->debug_flags, true, "tiny_free_list_add_ptr: Internal invariant broken (free_head->previous): " "ptr=%p slot=%d free_head=%p previous=%p\n", ptr, slot, (void *)free_head, free_head->previous.p); } if (!tiny_meta_header_is_free(free_head)) { malloc_zone_error(rack->debug_flags, true, "tiny_free_list_add_ptr: Internal invariant broken (free_head is not a free pointer): " "ptr=%p slot=%d free_head=%p\n", ptr, slot, (void *)free_head); } #endif free_head->previous.u = free_list_checksum_ptr(rack, free_ptr); } else { BITMAPV_SET(tiny_mag_ptr->mag_bitmap, slot); } free_ptr->previous.u = free_list_checksum_ptr(rack, NULL); free_ptr->next.u = free_list_checksum_ptr(rack, free_head); tiny_mag_ptr->mag_free_list[slot].p = free_ptr; } /* * Removes the item pointed to by ptr in the proper free list. * Assumes szone has been locked */ static void tiny_free_list_remove_ptr(rack_t *rack, magazine_t *tiny_mag_ptr, void *ptr, msize_t msize) { grain_t slot = (!msize || (msize >= NUM_TINY_SLOTS)) ? NUM_TINY_SLOTS - 1 : msize - 1; tiny_free_list_t *free_ptr = ptr, *next, *previous; next = free_list_unchecksum_ptr(rack, &free_ptr->next); previous = free_list_unchecksum_ptr(rack, &free_ptr->previous); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "In %s, ptr=%p, msize=%d\n", __FUNCTION__, ptr, msize); } #endif if (!previous) { // The block to remove is the head of the free list #if DEBUG_MALLOC if (tiny_mag_ptr->mag_free_list[slot] != ptr) { malloc_zone_error(rack->debug_flags, true, "tiny_free_list_remove_ptr: Internal invariant broken (tiny_mag_ptr->mag_free_list[slot]): " "ptr=%p slot=%d msize=%d tiny_mag_ptr->mag_free_list[slot]=%p\n", ptr, slot, msize, (void *)tiny_mag_ptr->mag_free_list[slot]); return; } #endif tiny_mag_ptr->mag_free_list[slot].p = next; if (!next) { BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot); } } else { // Check that the next pointer of "previous" points to free_ptr. tiny_free_list_t *prev_next = free_list_unchecksum_ptr(rack, &previous->next); if (prev_next != free_ptr) { malloc_zone_error(rack->debug_flags, true, "tiny_free_list_remove_ptr: Internal invariant broken (next ptr of prev): " "ptr=%p, prev_next=%p\n", ptr, prev_next); __builtin_unreachable(); // Always crashes in malloc_zone_error(). } // We know free_ptr is already checksummed, so we don't need to do it // again. previous->next = free_ptr->next; } if (next) { // Check that the previous pointer of "next" points to free_ptr. tiny_free_list_t *next_prev = free_list_unchecksum_ptr(rack, &next->previous); if (next_prev != free_ptr) { malloc_zone_error(rack->debug_flags, true, "tiny_free_list_remove_ptr: Internal invariant broken (prev ptr of next): " "ptr=%p, next_prev=%p\n", ptr, next_prev); __builtin_unreachable(); // Always crashes in malloc_zone_error(). } // We know free_ptr is already checksummed, so we don't need to do it // again. next->previous = free_ptr->previous; } } void tiny_finalize_region(rack_t *rack, magazine_t *tiny_mag_ptr) { void *last_block, *previous_block; uint32_t *last_header; msize_t last_msize, previous_msize, last_index; // It is possible that the block prior to the last block in the region has // been free'd, but was not coalesced with the free bytes at the end of the // block, since we treat the bytes at the end of the region as "in use" in // the meta headers. Attempt to coalesce the last block with the previous // block, so we don't violate the "no consecutive free blocks" invariant. // // FIXME: Need to investigate how much work would be required to increase // 'mag_bytes_free_at_end' when freeing the preceding block, rather // than performing this workaround. // if (tiny_mag_ptr->mag_bytes_free_at_end) { last_block = (void *)((uintptr_t)TINY_REGION_END(tiny_mag_ptr->mag_last_region) - tiny_mag_ptr->mag_bytes_free_at_end); last_msize = TINY_MSIZE_FOR_BYTES(tiny_mag_ptr->mag_bytes_free_at_end); last_header = TINY_BLOCK_HEADER_FOR_PTR(last_block); last_index = TINY_INDEX_FOR_PTR(last_block); // Before anything we transform any remaining mag_bytes_free_at_end into a // regular free block. We take special care here to update the bitfield // information, since we are bypassing the normal free codepath. If there // is more than one quanta worth of memory in mag_bytes_free_at_end, then // there will be two block headers: // 1) header for the free space at end, msize = 1 // 2) header inserted by set_tiny_meta_header_in_use after block // We must clear the second one so that when the free block's size is // queried, we do not think the block is only 1 quantum in size because // of the second set header bit. if (last_index != (NUM_TINY_BLOCKS - 1)) { BITARRAY_CLR(last_header, (last_index + 1)); } previous_block = tiny_previous_preceding_free(last_block, &previous_msize); if (previous_block) { set_tiny_meta_header_middle(last_block); tiny_free_list_remove_ptr(rack, tiny_mag_ptr, previous_block, previous_msize); last_block = previous_block; last_msize += previous_msize; } // splice last_block into the free list tiny_free_list_add_ptr(rack, tiny_mag_ptr, last_block, last_msize); tiny_mag_ptr->mag_bytes_free_at_end = 0; } #if CONFIG_ASLR_INTERNAL // Coalesce the big free block at start with any following free blocks if (tiny_mag_ptr->mag_bytes_free_at_start) { last_block = TINY_REGION_ADDRESS(tiny_mag_ptr->mag_last_region); last_msize = TINY_MSIZE_FOR_BYTES(tiny_mag_ptr->mag_bytes_free_at_start); void *next_block = (void *)((uintptr_t)last_block + tiny_mag_ptr->mag_bytes_free_at_start); // clear the in use bit we were using to mark the end of the big start block set_tiny_meta_header_middle((void *)((uintptr_t)next_block - TINY_QUANTUM)); // Coalesce the big start block with any following free blocks if (tiny_meta_header_is_free(next_block)) { msize_t next_msize = get_tiny_free_size(next_block); set_tiny_meta_header_middle(next_block); tiny_free_list_remove_ptr(rack, tiny_mag_ptr, next_block, next_msize); last_msize += next_msize; } // splice last_block into the free list tiny_free_list_add_ptr(rack, tiny_mag_ptr, last_block, last_msize); tiny_mag_ptr->mag_bytes_free_at_start = 0; } #endif tiny_mag_ptr->mag_last_region = NULL; } int tiny_free_detach_region(rack_t *rack, magazine_t *tiny_mag_ptr, region_t r) { uintptr_t start = (uintptr_t)TINY_REGION_ADDRESS(r); uintptr_t current = start; uintptr_t limit = (uintptr_t)TINY_REGION_END(r); boolean_t is_free; msize_t msize; int total_alloc = 0; while (current < limit) { msize = get_tiny_meta_header((void *)current, &is_free); if (is_free && !msize && (current == start)) { // first block is all free break; } if (!msize) { #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "*** tiny_free_detach_region error with %p: msize=%d is_free=%d\n", (void *)current, msize, is_free); #endif break; } if (is_free) { tiny_free_list_remove_ptr(rack, tiny_mag_ptr, (void *)current, msize); } else { total_alloc++; } current += TINY_BYTES_FOR_MSIZE(msize); } return total_alloc; } size_t tiny_free_reattach_region(rack_t *rack, magazine_t *tiny_mag_ptr, region_t r) { uintptr_t start = (uintptr_t)TINY_REGION_ADDRESS(r); uintptr_t current = start; uintptr_t limit = (uintptr_t)TINY_REGION_END(r); boolean_t is_free; msize_t msize; size_t total_alloc = 0; while (current < limit) { msize = get_tiny_meta_header((void *)current, &is_free); if (is_free && !msize && (current == start)) { // first block is all free break; } if (!msize) { #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "*** tiny_free_reattach_region error with %p: msize=%d is_free=%d\n", (void *)current, msize, is_free); #endif break; } if (is_free) { tiny_free_list_add_ptr(rack, tiny_mag_ptr, (void *)current, msize); } else { total_alloc += TINY_BYTES_FOR_MSIZE(msize); } current += TINY_BYTES_FOR_MSIZE(msize); } return total_alloc; } typedef struct { uint8_t pnum, size; } tiny_pg_pair_t; void tiny_free_scan_madvise_free(rack_t *rack, magazine_t *depot_ptr, region_t r) { uintptr_t start = (uintptr_t)TINY_REGION_ADDRESS(r); uintptr_t current = start; uintptr_t limit = (uintptr_t)TINY_REGION_END(r); boolean_t is_free; msize_t msize; tiny_pg_pair_t advisory[((TINY_REGION_PAYLOAD_BYTES + vm_page_quanta_size - 1) >> vm_page_quanta_shift) >> 1]; // 256bytes stack allocated int advisories = 0; // Scan the metadata identifying blocks which span one or more pages. Mark the pages MADV_FREE taking care to preserve free list // management data. while (current < limit) { msize = get_tiny_meta_header((void *)current, &is_free); if (is_free && !msize && (current == start)) { // first block is all free #if DEBUG_MALLOC malloc_report(ASL_LEVEL_INFO, "*** tiny_free_scan_madvise_free first block is all free! %p: msize=%d is_free=%d\n", (void *)current, msize, is_free); #endif uintptr_t pgLo = round_page_kernel(start + sizeof(tiny_free_list_t) + sizeof(msize_t)); uintptr_t pgHi = trunc_page_kernel(start + TINY_REGION_SIZE - sizeof(msize_t)); if (pgLo < pgHi) { advisory[advisories].pnum = (pgLo - start) >> vm_kernel_page_shift; advisory[advisories].size = (pgHi - pgLo) >> vm_kernel_page_shift; advisories++; } break; } if (!msize) { #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "*** tiny_free_scan_madvise_free error with %p: msize=%d is_free=%d\n", (void *)current, msize, is_free); #endif break; } if (is_free) { uintptr_t pgLo = round_page_kernel(current + sizeof(tiny_free_list_t) + sizeof(msize_t)); uintptr_t pgHi = trunc_page_kernel(current + TINY_BYTES_FOR_MSIZE(msize) - sizeof(msize_t)); if (pgLo < pgHi) { advisory[advisories].pnum = (pgLo - start) >> vm_kernel_page_shift; advisory[advisories].size = (pgHi - pgLo) >> vm_kernel_page_shift; advisories++; } } current += TINY_BYTES_FOR_MSIZE(msize); } if (advisories > 0) { int i; // So long as the following hold for this region: // (1) No malloc()'s are ever performed from the depot (hence free pages remain free,) // (2) The region is not handed over to a per-CPU magazine (where malloc()'s could be performed), // (3) The entire region is not mumap()'d (so the madvise's are applied to the intended addresses), // then the madvise opportunities collected just above can be applied outside all locks. // (1) is ensured by design, (2) and (3) are ensured by bumping the globally visible counter node->pinned_to_depot. OSAtomicIncrement32Barrier(&(REGION_TRAILER_FOR_TINY_REGION(r)->pinned_to_depot)); SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); for (i = 0; i < advisories; ++i) { uintptr_t addr = (advisory[i].pnum << vm_kernel_page_shift) + start; size_t size = advisory[i].size << vm_kernel_page_shift; mvm_madvise_free(rack, r, addr, addr + size, NULL, rack->debug_flags & MALLOC_DO_SCRIBBLE); } SZONE_MAGAZINE_PTR_LOCK(depot_ptr); OSAtomicDecrement32Barrier(&(REGION_TRAILER_FOR_TINY_REGION(r)->pinned_to_depot)); } } static region_t tiny_find_msize_region(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize) { tiny_free_list_t *ptr; grain_t slot = msize - 1; free_list_t *free_list = tiny_mag_ptr->mag_free_list; free_list_t *the_slot = free_list + slot; free_list_t *limit; #if defined(__LP64__) uint64_t bitmap; #else uint32_t bitmap; #endif // Assumes we've locked the magazine CHECK_MAGAZINE_PTR_LOCKED(szone, tiny_mag_ptr, __PRETTY_FUNCTION__); // Look for an exact match by checking the freelist for this msize. ptr = the_slot->p; if (ptr) { return TINY_REGION_FOR_PTR(ptr); } // Mask off the bits representing slots holding free blocks smaller than the // size we need. If there are no larger free blocks, try allocating from // the free space at the end of the tiny region. #if defined(__LP64__) bitmap = ((uint64_t *)(tiny_mag_ptr->mag_bitmap))[0] & ~((1ULL << slot) - 1); #else bitmap = tiny_mag_ptr->mag_bitmap[0] & ~((1 << slot) - 1); #endif if (!bitmap) { return NULL; } slot = BITMAPV_CTZ(bitmap); limit = free_list + NUM_TINY_SLOTS - 1; free_list += slot; if (free_list < limit) { ptr = free_list->p; if (ptr) { return TINY_REGION_FOR_PTR(ptr); } else { /* Shouldn't happen. Fall through to look at last slot. */ #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "in tiny_find_msize_region(), mag_bitmap out of sync, slot=%d\n", slot); #endif } } // We are now looking at the last slot, which contains blocks equal to, or // due to coalescing of free blocks, larger than (NUM_TINY_SLOTS - 1) * tiny quantum size. ptr = limit->p; if (ptr) { return TINY_REGION_FOR_PTR(ptr); } return NULL; } static MALLOC_INLINE void tiny_madvise_free_range_no_lock(rack_t *rack, magazine_t *tiny_mag_ptr, region_t region, void *headptr, size_t headsize, void *ptr, msize_t msize) { region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(region); // Lock on tiny_magazines[mag_index] is already held here // Calculate the first page in the coalesced block that would be safe to mark MADV_FREE size_t free_header_size = sizeof(tiny_free_list_t) + sizeof(msize_t); uintptr_t safe_ptr = (uintptr_t)ptr + free_header_size; uintptr_t round_safe = round_page_kernel(safe_ptr); // Calculate the last page in the coalesced block that would be safe to mark MADV_FREE size_t free_tail_size = sizeof(msize_t); uintptr_t safe_extent = (uintptr_t)ptr + TINY_BYTES_FOR_MSIZE(msize) - free_tail_size; uintptr_t trunc_extent = trunc_page_kernel(safe_extent); // The newly freed block may complete a span of bytes that cover a page. Mark it with MADV_FREE. if (round_safe < trunc_extent) { // Coalesced area covers a page (perhaps many) // Extend the freed block by the free region header and tail sizes to include pages // we may have coalesced that no longer host free region tails and headers. // This may extend over in-use ranges, but the MIN/MAX clamping below will fix that up. uintptr_t lo = trunc_page_kernel((uintptr_t)headptr - free_tail_size); uintptr_t hi = round_page_kernel((uintptr_t)headptr + headsize + free_header_size); uintptr_t free_lo = MAX(round_safe, lo); uintptr_t free_hi = MIN(trunc_extent, hi); if (free_lo < free_hi) { tiny_free_list_remove_ptr(rack, tiny_mag_ptr, ptr, msize); set_tiny_meta_header_in_use(ptr, msize); OSAtomicIncrement32Barrier(&(node->pinned_to_depot)); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); mvm_madvise_free(rack, region, free_lo, free_hi, &rack->last_madvise, rack->debug_flags & MALLOC_DO_SCRIBBLE); SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr); OSAtomicDecrement32Barrier(&(node->pinned_to_depot)); set_tiny_meta_header_free(ptr, msize); tiny_free_list_add_ptr(rack, tiny_mag_ptr, ptr, msize); } } } static boolean_t tiny_get_region_from_depot(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize) { magazine_t *depot_ptr = &(rack->magazines[DEPOT_MAGAZINE_INDEX]); /* FIXME: Would Uniprocessor benefit from recirc and MADV_FREE? */ if (rack->num_magazines == 1) { // Uniprocessor, single magazine, so no recirculation necessary return 0; } #if DEBUG_MALLOC if (DEPOT_MAGAZINE_INDEX == mag_index) { malloc_zone_error(rack->debug_flags, true, "tiny_get_region_from_depot called for magazine index -1\n"); return 0; } #endif SZONE_MAGAZINE_PTR_LOCK(depot_ptr); // Appropriate a Depot'd region that can satisfy requested msize. region_trailer_t *node; region_t sparse_region; while (1) { sparse_region = tiny_find_msize_region(rack, depot_ptr, DEPOT_MAGAZINE_INDEX, msize); if (NULL == sparse_region) { // Depot empty? SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); return 0; } node = REGION_TRAILER_FOR_TINY_REGION(sparse_region); if (0 >= node->pinned_to_depot) { break; } SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); yield(); SZONE_MAGAZINE_PTR_LOCK(depot_ptr); } // disconnect node from Depot recirc_list_extract(rack, depot_ptr, node); // Iterate the region pulling its free entries off the (locked) Depot's free list int objects_in_use = tiny_free_detach_region(rack, depot_ptr, sparse_region); // Transfer ownership of the region MAGAZINE_INDEX_FOR_TINY_REGION(sparse_region) = mag_index; node->pinned_to_depot = 0; // Iterate the region putting its free entries on its new (locked) magazine's free list size_t bytes_inplay = tiny_free_reattach_region(rack, tiny_mag_ptr, sparse_region); depot_ptr->mag_num_bytes_in_objects -= bytes_inplay; depot_ptr->num_bytes_in_magazine -= TINY_REGION_PAYLOAD_BYTES; depot_ptr->mag_num_objects -= objects_in_use; tiny_mag_ptr->mag_num_bytes_in_objects += bytes_inplay; tiny_mag_ptr->num_bytes_in_magazine += TINY_REGION_PAYLOAD_BYTES; tiny_mag_ptr->mag_num_objects += objects_in_use; // connect to magazine as first node recirc_list_splice_first(rack, tiny_mag_ptr, node); SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); // DTrace USDT Probe MAGMALLOC_DEPOTREGION(TINY_SZONE_FROM_RACK(rack), (int)mag_index, (void *)sparse_region, TINY_REGION_SIZE, (int)BYTES_USED_FOR_TINY_REGION(sparse_region)); return 1; } #if CONFIG_RECIRC_DEPOT static region_t tiny_free_try_depot_unmap_no_lock(rack_t *rack, magazine_t *depot_ptr, region_trailer_t *node) { if (0 < node->bytes_used || 0 < node->pinned_to_depot || depot_ptr->recirculation_entries < recirc_retained_regions) { return NULL; } // disconnect node from Depot recirc_list_extract(rack, depot_ptr, node); // Iterate the region pulling its free entries off the (locked) Depot's free list region_t sparse_region = TINY_REGION_FOR_PTR(node); int objects_in_use = tiny_free_detach_region(rack, depot_ptr, sparse_region); if (0 == objects_in_use) { // Invalidate the hash table entry for this region with HASHRING_REGION_DEALLOCATED. // Using HASHRING_REGION_DEALLOCATED preserves the collision chain, using HASHRING_OPEN_ENTRY (0) would not. rgnhdl_t pSlot = hash_lookup_region_no_lock(rack->region_generation->hashed_regions, rack->region_generation->num_regions_allocated, rack->region_generation->num_regions_allocated_shift, sparse_region); if (NULL == pSlot) { malloc_zone_error(rack->debug_flags, true, "tiny_free_try_depot_unmap_no_lock hash lookup failed: %p\n", sparse_region); return NULL; } *pSlot = HASHRING_REGION_DEALLOCATED; depot_ptr->num_bytes_in_magazine -= TINY_REGION_PAYLOAD_BYTES; // Atomically increment num_regions_dealloc #ifdef __LP64___ OSAtomicIncrement64(&rack->num_regions_dealloc); #else OSAtomicIncrement32((int32_t *)&rack->num_regions_dealloc); #endif // Caller will transfer ownership of the region back to the OS with no locks held MAGMALLOC_DEALLOCREGION(TINY_SZONE_FROM_RACK(rack), (void *)sparse_region, TINY_REGION_SIZE); // DTrace USDT Probe return sparse_region; } else { malloc_zone_error(rack->debug_flags, true, "tiny_free_try_depot_unmap_no_lock objects_in_use not zero: %d\n", objects_in_use); return NULL; } } static boolean_t tiny_free_do_recirc_to_depot(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index) { // The entire magazine crossed the "emptiness threshold". Transfer a region // from this magazine to the Depot. Choose a region that itself has crossed the emptiness threshold (i.e // is at least fraction "f" empty.) Such a region will be marked "suitable" on the recirculation list. region_trailer_t *node = tiny_mag_ptr->firstNode; while (node && (!node->recirc_suitable || node->pinned_to_depot)) { // If we skip a node due to pinned_to_depot being non-zero, it must be // because another thread is madvising the same region in // tiny_madvise_free_range_no_lock(), called from tiny_free_no_lock(). // When that's done, the same thread will enter tiny_free_try_recirc_to_depot() // for the same region, which will come back here. So this just defers // recirculation of the region. node = node->next; } if (NULL == node) { #if DEBUG_MALLOC malloc_report(ASL_LEVEL_INFO, "*** tiny_free_do_recirc_to_depot end of list\n"); #endif return TRUE; // Caller must SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); } region_t sparse_region = TINY_REGION_FOR_PTR(node); // Deal with unclaimed memory -- mag_bytes_free_at_end or mag_bytes_free_at_start if (sparse_region == tiny_mag_ptr->mag_last_region && (tiny_mag_ptr->mag_bytes_free_at_end || tiny_mag_ptr->mag_bytes_free_at_start)) { tiny_finalize_region(rack, tiny_mag_ptr); } // disconnect "suitable" node from magazine recirc_list_extract(rack, tiny_mag_ptr, node); // Iterate the region pulling its free entries off its (locked) magazine's free list int objects_in_use = tiny_free_detach_region(rack, tiny_mag_ptr, sparse_region); magazine_t *depot_ptr = &(rack->magazines[DEPOT_MAGAZINE_INDEX]); // hand over the region to the (locked) Depot SZONE_MAGAZINE_PTR_LOCK(depot_ptr); // this will cause tiny_free_list_add_ptr called by tiny_free_reattach_region to use // the depot as its target magazine, rather than magazine formerly associated with sparse_region MAGAZINE_INDEX_FOR_TINY_REGION(sparse_region) = DEPOT_MAGAZINE_INDEX; node->pinned_to_depot = 0; // Iterate the region putting its free entries on Depot's free list size_t bytes_inplay = tiny_free_reattach_region(rack, depot_ptr, sparse_region); tiny_mag_ptr->mag_num_bytes_in_objects -= bytes_inplay; tiny_mag_ptr->num_bytes_in_magazine -= TINY_REGION_PAYLOAD_BYTES; tiny_mag_ptr->mag_num_objects -= objects_in_use; SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); // Unlock the originating magazine depot_ptr->mag_num_bytes_in_objects += bytes_inplay; depot_ptr->num_bytes_in_magazine += TINY_REGION_PAYLOAD_BYTES; depot_ptr->mag_num_objects += objects_in_use; // connect to Depot as last node recirc_list_splice_last(rack, depot_ptr, node); MAGMALLOC_RECIRCREGION(TINY_SZONE_FROM_RACK(rack), (int)mag_index, (void *)sparse_region, TINY_REGION_SIZE, (int)BYTES_USED_FOR_TINY_REGION(sparse_region)); // DTrace USDT Probe #if !CONFIG_AGGRESSIVE_MADVISE // Mark free'd dirty pages with MADV_FREE to reduce memory pressure tiny_free_scan_madvise_free(rack, depot_ptr, sparse_region); #endif // If the region is entirely empty vm_deallocate() it outside the depot lock region_t r_dealloc = tiny_free_try_depot_unmap_no_lock(rack, depot_ptr, node); SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr); if (r_dealloc) { mvm_deallocate_pages(r_dealloc, TINY_REGION_SIZE, 0); } return FALSE; // Caller need not unlock the originating magazine } static MALLOC_INLINE boolean_t tiny_free_try_recirc_to_depot(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, region_t region, void *headptr, size_t headsize, void *ptr, msize_t msize) { region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(region); size_t bytes_used = node->bytes_used; /* FIXME: Would Uniprocessor benefit from recirc and MADV_FREE? */ if (rack->num_magazines == 1) { // Uniprocessor, single magazine, so no recirculation necessary /* NOTHING */ return TRUE; // Caller must do SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr) } else if (DEPOT_MAGAZINE_INDEX != mag_index) { // Emptiness discriminant if (bytes_used < DENSITY_THRESHOLD(TINY_REGION_PAYLOAD_BYTES)) { /* Region has crossed threshold from density to sparsity. Mark it "suitable" on the * recirculation candidates list. */ node->recirc_suitable = TRUE; } else { /* After this free, we've found the region is still dense, so it must have been even more so before * the free. That implies the region is already correctly marked. Do nothing. */ } // Has the entire magazine crossed the "emptiness threshold"? If so, transfer a region // from this magazine to the Depot. Choose a region that itself has crossed the emptiness threshold (i.e // is at least fraction "f" empty.) Such a region will be marked "suitable" on the recirculation list. size_t a = tiny_mag_ptr->num_bytes_in_magazine; // Total bytes allocated to this magazine size_t u = tiny_mag_ptr->mag_num_bytes_in_objects; // In use (malloc'd) from this magaqzine if (a - u > ((3 * TINY_REGION_PAYLOAD_BYTES) / 2) && u < DENSITY_THRESHOLD(a)) { return tiny_free_do_recirc_to_depot(rack, tiny_mag_ptr, mag_index); } } else { #if !CONFIG_AGGRESSIVE_MADVISE // We are free'ing into the depot, so madvise as we do so unless we were madvising every incoming // allocation anyway. tiny_madvise_free_range_no_lock(rack, tiny_mag_ptr, region, headptr, headsize, ptr, msize); #endif if (0 < bytes_used || 0 < node->pinned_to_depot) { /* Depot'd region is still live. Leave it in place on the Depot's recirculation list * so as to avoid thrashing between the Depot's free list and a magazines's free list * with detach_region/reattach_region */ } else { /* Depot'd region is just now empty. Consider return to OS. */ region_t r_dealloc = tiny_free_try_depot_unmap_no_lock(rack, tiny_mag_ptr, node); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); if (r_dealloc) { mvm_deallocate_pages(r_dealloc, TINY_REGION_SIZE, 0); } return FALSE; // Caller need not unlock } } return TRUE; // Caller must do SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr) } #endif // CONFIG_RECIRC_DEPOT boolean_t tiny_free_no_lock(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, region_t region, void *ptr, msize_t msize) { void *original_ptr = ptr; size_t original_size = TINY_BYTES_FOR_MSIZE(msize); void *next_block = ((unsigned char *)ptr + original_size); msize_t previous_msize, next_msize; void *previous; tiny_free_list_t *big_free_block; tiny_free_list_t *after_next_block; tiny_free_list_t *before_next_block; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize); } if (!msize) { malloc_zone_error(rack->debug_flags, true, "trying to free tiny block that is too small in tiny_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize); } #endif // We try to coalesce this block with the preceeding one previous = tiny_previous_preceding_free(ptr, &previous_msize); if (previous) { #if DEBUG_MALLOC if (LOG(szone, ptr) || LOG(szone, previous)) { malloc_report(ASL_LEVEL_INFO, "in tiny_free_no_lock(), coalesced backwards for %p previous=%p\n", ptr, previous); } #endif // clear the meta_header since this is no longer the start of a block set_tiny_meta_header_middle(ptr); tiny_free_list_remove_ptr(rack, tiny_mag_ptr, previous, previous_msize); ptr = previous; msize += previous_msize; } // We try to coalesce with the next block if ((next_block < TINY_REGION_END(region)) && tiny_meta_header_is_free(next_block)) { next_msize = get_tiny_free_size(next_block); #if DEBUG_MALLOC if (LOG(szone, ptr) || LOG(szone, next_block)) { malloc_report(ASL_LEVEL_INFO, "in tiny_free_no_lock(), for ptr=%p, msize=%d coalesced forward=%p next_msize=%d\n", ptr, msize, next_block, next_msize); } #endif // If we are coalescing with the next block, and the next block is in // the last slot of the free list, then we optimize this case here to // avoid removing next_block from the slot (NUM_TINY_SLOTS - 1) and then adding ptr back // to slot (NUM_TINY_SLOTS - 1). if (next_msize >= NUM_TINY_SLOTS) { msize += next_msize; big_free_block = (tiny_free_list_t *)next_block; after_next_block = free_list_unchecksum_ptr(rack, &big_free_block->next); before_next_block = free_list_unchecksum_ptr(rack, &big_free_block->previous); if (!before_next_block) { tiny_mag_ptr->mag_free_list[NUM_TINY_SLOTS - 1].p = ptr; } else { before_next_block->next.u = free_list_checksum_ptr(rack, ptr); } if (after_next_block) { after_next_block->previous.u = free_list_checksum_ptr(rack, ptr); } // we don't need to checksum these since they are already checksummed ((tiny_free_list_t *)ptr)->previous = big_free_block->previous; ((tiny_free_list_t *)ptr)->next = big_free_block->next; // clear the meta_header to enable coalescing backwards set_tiny_meta_header_middle(big_free_block); set_tiny_meta_header_free(ptr, msize); goto tiny_free_ending; } tiny_free_list_remove_ptr(rack, tiny_mag_ptr, next_block, next_msize); set_tiny_meta_header_middle(next_block); // clear the meta_header to enable coalescing backwards msize += next_msize; } // The tiny cache already scribbles free blocks as they go through the // cache whenever msize < TINY_QUANTUM , so we do not need to do it here. if ((rack->debug_flags & MALLOC_DO_SCRIBBLE) && msize && (msize >= TINY_QUANTUM)) { memset(ptr, SCRABBLE_BYTE, TINY_BYTES_FOR_MSIZE(msize)); } tiny_free_list_add_ptr(rack, tiny_mag_ptr, ptr, msize); tiny_free_ending: tiny_mag_ptr->mag_num_objects--; // we use original_size and not msize to avoid double counting the coalesced blocks tiny_mag_ptr->mag_num_bytes_in_objects -= original_size; // Update this region's bytes in use count region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(region); size_t bytes_used = node->bytes_used - original_size; node->bytes_used = (unsigned int)bytes_used; #if CONFIG_AGGRESSIVE_MADVISE // Platforms that want to madvise every freed allocation do so here, even if we continue // on to use the recirc depot after. tiny_madvise_free_range_no_lock(rack, tiny_mag_ptr, region, original_ptr, original_size, ptr, msize); #endif // Caller must do SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr) if this function // returns TRUE. boolean_t needs_unlock = TRUE; #if CONFIG_RECIRC_DEPOT needs_unlock = tiny_free_try_recirc_to_depot(rack, tiny_mag_ptr, mag_index, region, original_ptr, original_size, ptr, msize); #endif return needs_unlock; } // Allocates from the last region or a freshly allocated region static void * tiny_malloc_from_region_no_lock(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize, void *aligned_address) { void *ptr; // Deal with unclaimed memory -- mag_bytes_free_at_end or mag_bytes_free_at_start if (tiny_mag_ptr->mag_bytes_free_at_end || tiny_mag_ptr->mag_bytes_free_at_start) { tiny_finalize_region(rack, tiny_mag_ptr); } // We set the unused bits of the header in the last pair to be all ones, and those of the inuse to zeroes. #if NUM_TINY_BLOCKS & 31 const uint32_t header = 0xFFFFFFFFU << (NUM_TINY_BLOCKS & 31); #else const uint32_t header = 0; #endif ((tiny_region_t)aligned_address)->pairs[CEIL_NUM_TINY_BLOCKS_WORDS - 1].header = header; ((tiny_region_t)aligned_address)->pairs[CEIL_NUM_TINY_BLOCKS_WORDS - 1].inuse = 0; // Tag the region at "aligned_address" as belonging to us, // and so put it under the protection of the magazine lock we are holding. // Do this before advertising "aligned_address" on the hash ring(!) MAGAZINE_INDEX_FOR_TINY_REGION(aligned_address) = mag_index; // Insert the new region into the hash ring rack_region_insert(rack, (region_t)aligned_address); tiny_mag_ptr->mag_last_region = aligned_address; BYTES_USED_FOR_TINY_REGION(aligned_address) = TINY_BYTES_FOR_MSIZE(msize); #if CONFIG_ASLR_INTERNAL int offset_msize = malloc_entropy[0] & TINY_ENTROPY_MASK; #if DEBUG_MALLOC if (getenv("MallocASLRForce")) { offset_msize = strtol(getenv("MallocASLRForce"), NULL, 0) & TINY_ENTROPY_MASK; } if (getenv("MallocASLRPrint")) { malloc_report(ASL_LEVEL_INFO, "Region: %p offset: %d\n", aligned_address, offset_msize); } #endif #else int offset_msize = 0; #endif ptr = (void *)((uintptr_t)aligned_address + TINY_BYTES_FOR_MSIZE(offset_msize)); set_tiny_meta_header_in_use(ptr, msize); tiny_mag_ptr->mag_num_objects++; tiny_mag_ptr->mag_num_bytes_in_objects += TINY_BYTES_FOR_MSIZE(msize); tiny_mag_ptr->num_bytes_in_magazine += TINY_REGION_PAYLOAD_BYTES; // We put a header on the last block so that it appears in use (for coalescing, etc...) set_tiny_meta_header_in_use_1((void *)((uintptr_t)ptr + TINY_BYTES_FOR_MSIZE(msize))); tiny_mag_ptr->mag_bytes_free_at_end = TINY_BYTES_FOR_MSIZE(NUM_TINY_BLOCKS - msize - offset_msize); #if CONFIG_ASLR_INTERNAL // Put a header on the previous block for same reason tiny_mag_ptr->mag_bytes_free_at_start = TINY_BYTES_FOR_MSIZE(offset_msize); if (offset_msize) { set_tiny_meta_header_in_use_1((void *)((uintptr_t)ptr - TINY_QUANTUM)); } #else tiny_mag_ptr->mag_bytes_free_at_start = 0; #endif // connect to magazine as last node recirc_list_splice_last(rack, tiny_mag_ptr, REGION_TRAILER_FOR_TINY_REGION(aligned_address)); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_region_no_lock(), ptr=%p, msize=%d\n", ptr, msize); } #endif return ptr; } void * tiny_memalign(szone_t *szone, size_t alignment, size_t size, size_t span) { msize_t mspan = TINY_MSIZE_FOR_BYTES(span + TINY_QUANTUM - 1); void *p = tiny_malloc_should_clear(&szone->tiny_rack, mspan, 0); if (NULL == p) { return NULL; } size_t offset = ((uintptr_t)p) & (alignment - 1); // p % alignment size_t pad = (0 == offset) ? 0 : alignment - offset; // p + pad achieves desired alignment msize_t msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1); msize_t mpad = TINY_MSIZE_FOR_BYTES(pad + TINY_QUANTUM - 1); msize_t mwaste = mspan - msize - mpad; // excess blocks if (mpad > 0) { void *q = (void *)(((uintptr_t)p) + pad); // Mark q as a block header and in-use, thus creating two blocks. magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone->tiny_rack.magazines, REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(p)), MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(p))); set_tiny_meta_header_in_use(q, msize); tiny_mag_ptr->mag_num_objects++; // set_tiny_meta_header_in_use() "reaffirms" the block_header on the *following* block, so // now set its in_use bit as well. But only if its within the original allocation made above. if (mwaste > 0) { BITARRAY_SET(TINY_INUSE_FOR_HEADER(TINY_BLOCK_HEADER_FOR_PTR(q)), TINY_INDEX_FOR_PTR(q) + msize); } SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); // Give up mpad blocks beginning at p to the tiny free list free_tiny(&szone->tiny_rack, p, TINY_REGION_FOR_PTR(p), TINY_BYTES_FOR_MSIZE(mpad)); p = q; // advance p to the desired alignment } if (mwaste > 0) { void *q = (void *)(((uintptr_t)p) + TINY_BYTES_FOR_MSIZE(msize)); // Mark q as block header and in-use, thus creating two blocks. magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone->tiny_rack.magazines, REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(p)), MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(p))); set_tiny_meta_header_in_use(q, mwaste); tiny_mag_ptr->mag_num_objects++; SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); // Give up mwaste blocks beginning at q to the tiny free list free_tiny(&szone->tiny_rack, q, TINY_REGION_FOR_PTR(q), TINY_BYTES_FOR_MSIZE(mwaste)); } return p; // p has the desired size and alignment, and can later be free()'d } boolean_t tiny_claimed_address(rack_t *rack, void *ptr) { region_t r = tiny_region_for_ptr_no_lock(rack, ptr); return r && ptr < TINY_REGION_END(r); } void * tiny_try_shrink_in_place(rack_t *rack, void *ptr, size_t old_size, size_t new_good_size) { msize_t new_msize = TINY_MSIZE_FOR_BYTES(new_good_size); msize_t mshrinkage = TINY_MSIZE_FOR_BYTES(old_size) - new_msize; if (mshrinkage) { void *q = (void *)((uintptr_t)ptr + TINY_BYTES_FOR_MSIZE(new_msize)); magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(rack->magazines, REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr)), MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr))); // Mark q as block header and in-use, thus creating two blocks. set_tiny_meta_header_in_use(q, mshrinkage); tiny_mag_ptr->mag_num_objects++; SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); free_tiny(rack, q, TINY_REGION_FOR_PTR(q), 0); } return ptr; } boolean_t tiny_try_realloc_in_place(rack_t *rack, void *ptr, size_t old_size, size_t new_size) { // returns 1 on success msize_t index; msize_t old_msize; unsigned next_index; void *next_block; boolean_t is_free; msize_t next_msize, coalesced_msize, leftover_msize, new_msize; void *leftover; index = TINY_INDEX_FOR_PTR(ptr); old_msize = TINY_MSIZE_FOR_BYTES(old_size); new_msize = TINY_MSIZE_FOR_BYTES(new_size + TINY_QUANTUM - 1); next_index = index + old_msize; if (next_index >= NUM_TINY_BLOCKS) { return 0; } next_block = (char *)ptr + old_size; magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(rack->magazines, REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr)), MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr))); if (DEPOT_MAGAZINE_INDEX == MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr))) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return 0; } coalesced_msize = new_msize - old_msize; #if CONFIG_TINY_CACHE void *last_free_ptr = tiny_mag_ptr->mag_last_free; msize_t last_free_msize = tiny_mag_ptr->mag_last_free_msize; if (last_free_ptr == next_block && old_msize + last_free_msize >= new_msize) { /* * There is a block in mag_last_free and it's immediately after * this block and it's large enough. We can use some or all of it. */ leftover_msize = last_free_msize - coalesced_msize; if (leftover_msize) { tiny_mag_ptr->mag_last_free_msize -= coalesced_msize; tiny_mag_ptr->mag_last_free += new_size - old_size; // The block in mag_last_free is still marked as header and in-use, so copy that // state to the block that remains. The state for the block that we're going to // use is adjusted by the set_tiny_meta_header_middle() call below. set_tiny_meta_header_in_use(next_block + TINY_BYTES_FOR_MSIZE(coalesced_msize), leftover_msize); } else { // Using the whole block. tiny_mag_ptr->mag_last_free = NULL; tiny_mag_ptr->mag_last_free_msize = 0; tiny_mag_ptr->mag_last_free_rgn = NULL; } set_tiny_meta_header_middle(next_block); } else { #endif // CONFIG_TINY_CACHE /* * Try to expand into unused space immediately after this block. */ msize_t unused_msize = TINY_MSIZE_FOR_BYTES(tiny_mag_ptr->mag_bytes_free_at_end); void *unused_start = TINY_REGION_END(TINY_REGION_FOR_PTR(ptr)) - tiny_mag_ptr->mag_bytes_free_at_end; if (tiny_mag_ptr->mag_last_region == TINY_REGION_FOR_PTR(ptr) && coalesced_msize < unused_msize && unused_start == ptr + old_size) { // The block at the start of mag_bytes_free_at_end is marked as // header/in-use and the next one has header/free. We need to // reset both the header and in-use bit in the first block and we // need to reset the header bit in the second block if it's part of // the new allocation. set_tiny_meta_header_middle(unused_start); if (coalesced_msize > 1) { set_tiny_meta_header_middle(unused_start + TINY_QUANTUM); } tiny_mag_ptr->mag_bytes_free_at_end -= TINY_BYTES_FOR_MSIZE(coalesced_msize); if (tiny_mag_ptr->mag_bytes_free_at_end) { // Mark the first block of the remaining free area as a header and in-use. set_tiny_meta_header_in_use_1(ptr + TINY_BYTES_FOR_MSIZE(new_msize)); } } else { /* * Look for a free block immediately afterwards. If it's large * enough, we can consume (part of) it. */ is_free = tiny_meta_header_is_free(next_block); if (!is_free) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return 0; // next_block is in use; } next_msize = get_tiny_free_size(next_block); if (old_msize + next_msize < new_msize) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return 0; // even with next block, not enough } /* * The following block is big enough; pull it from its freelist and chop off enough to satisfy * our needs. */ tiny_free_list_remove_ptr(rack, tiny_mag_ptr, next_block, next_msize); set_tiny_meta_header_middle(next_block); // clear the meta_header to enable coalescing backwards leftover_msize = next_msize - coalesced_msize; if (leftover_msize) { /* there's some left, so put the remainder back */ leftover = (void *)((uintptr_t)next_block + TINY_BYTES_FOR_MSIZE(coalesced_msize)); tiny_free_list_add_ptr(rack, tiny_mag_ptr, leftover, leftover_msize); } set_tiny_meta_header_in_use(ptr, old_msize + coalesced_msize); } #if CONFIG_TINY_CACHE } #endif // CONFIG_TINY_CACHE #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_try_realloc_in_place(), ptr=%p, msize=%d\n", ptr, old_msize + coalesced_msize); } #endif tiny_mag_ptr->mag_num_bytes_in_objects += TINY_BYTES_FOR_MSIZE(coalesced_msize); // Update this region's bytes in use count region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr)); size_t bytes_used = node->bytes_used + TINY_BYTES_FOR_MSIZE(coalesced_msize); node->bytes_used = (unsigned int)bytes_used; // Emptiness discriminant if (bytes_used < DENSITY_THRESHOLD(TINY_REGION_PAYLOAD_BYTES)) { /* After this reallocation the region is still sparse, so it must have been even more so before * the reallocation. That implies the region is already correctly marked. Do nothing. */ } else { /* Region has crossed threshold from sparsity to density. Mark it not "suitable" on the * recirculation candidates list. */ node->recirc_suitable = FALSE; } SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); return 1; } static char *tiny_check_fail_msg = "*** check: incorrect tiny region "; #define TINY_CHECK_FAIL(fmt, ...) \ malloc_zone_check_fail(tiny_check_fail_msg, \ "%ld, counter=%d\n" fmt, region_index, counter, __VA_ARGS__); boolean_t tiny_check_region(rack_t *rack, region_t region, size_t region_index, unsigned counter) { uintptr_t start, ptr, region_end; boolean_t prev_free = 0; boolean_t is_free; msize_t msize; tiny_free_list_t *free_head; void *follower, *previous, *next; mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(region); magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]); // Assumes locked CHECK_MAGAZINE_PTR_LOCKED(szone, tiny_mag_ptr, __PRETTY_FUNCTION__); // Do not check the region if pinned_to_depot is not zero because it // may not be in a consistent state (specifically, if may have a // block marked as in-use that's longer than any legal allocation, // which upsets get_tiny_meta_header() because it can't determine the // block's length). if (!REGION_TRAILER_FOR_TINY_REGION(region)->pinned_to_depot) { return 1; } /* establish region limits */ start = (uintptr_t)TINY_REGION_ADDRESS(region); ptr = start; if (region == tiny_mag_ptr->mag_last_region) { ptr += tiny_mag_ptr->mag_bytes_free_at_start; /* * Check the leading block's integrity here also. */ if (tiny_mag_ptr->mag_bytes_free_at_start) { msize = get_tiny_meta_header((void *)(ptr - TINY_QUANTUM), &is_free); if (is_free || (msize != 1)) { TINY_CHECK_FAIL("*** invariant broken for leader block %p - %d %d\n", (void *)(ptr - TINY_QUANTUM), msize, is_free); return 0; } } } region_end = (uintptr_t)TINY_REGION_END(region); /* * The last region may have a trailing chunk which has not been converted into inuse/freelist * blocks yet. */ if (region == tiny_mag_ptr->mag_last_region) { region_end -= tiny_mag_ptr->mag_bytes_free_at_end; } /* * Scan blocks within the region. */ while (ptr < region_end) { /* * If the first block is free, and its size is 65536 (msize = 0) then the entire region is * free. */ msize = get_tiny_meta_header((void *)ptr, &is_free); if (is_free && !msize && (ptr == start)) { return 1; } /* * If the block's size is 65536 (msize = 0) then since we're not the first entry the size is * corrupt. */ if (!msize) { TINY_CHECK_FAIL("*** invariant broken for tiny block %p this msize=%d - size is too small\n", (void *)ptr, msize); return 0; } if (!is_free) { /* * In use blocks cannot be more than (NUM_TINY_SLOTS - 1) quanta large. */ prev_free = 0; if (msize > (NUM_TINY_SLOTS - 1)) { TINY_CHECK_FAIL("*** invariant broken for %p this tiny msize=%d - size is too large\n", (void *)ptr, msize); return 0; } /* move to next block */ ptr += TINY_BYTES_FOR_MSIZE(msize); } else { #if !CONFIG_RELAXED_INVARIANT_CHECKS /* * Free blocks must have been coalesced, we cannot have a free block following another * free block. */ if (prev_free) { TINY_CHECK_FAIL("*** invariant broken for free block %p this tiny msize=%d: two free blocks in a row\n", (void *)ptr, msize); return 0; } #endif // CONFIG_RELAXED_INVARIANT_CHECKS prev_free = 1; /* * Check the integrity of this block's entry in its freelist. */ free_head = (tiny_free_list_t *)ptr; previous = free_list_unchecksum_ptr(rack, &free_head->previous); next = free_list_unchecksum_ptr(rack, &free_head->next); if (previous && !tiny_meta_header_is_free(previous)) { TINY_CHECK_FAIL("*** invariant broken for %p (previous %p is not a free pointer)\n", (void *)ptr, previous); return 0; } if (next && !tiny_meta_header_is_free(next)) { TINY_CHECK_FAIL("*** invariant broken for %p (next in free list %p is not a free pointer)\n", (void *)ptr, next); return 0; } /* * Check the free block's trailing size value. */ follower = FOLLOWING_TINY_PTR(ptr, msize); if (((uintptr_t)follower != region_end) && (get_tiny_previous_free_msize(follower) != msize)) { TINY_CHECK_FAIL("*** invariant broken for tiny free %p followed by %p in region [%p-%p] " "(end marker incorrect) should be %d; in fact %d\n", (void *)ptr, follower, TINY_REGION_ADDRESS(region), (void *)region_end, msize, get_tiny_previous_free_msize(follower)); return 0; } /* move to next block */ ptr = (uintptr_t)follower; } } /* * Ensure that we scanned the entire region */ if (ptr != region_end) { TINY_CHECK_FAIL("*** invariant broken for region end %p - %p\n", (void *)ptr, (void *)region_end); return 0; } /* * Check the trailing block's integrity. */ if (region == tiny_mag_ptr->mag_last_region) { if (tiny_mag_ptr->mag_bytes_free_at_end) { msize = get_tiny_meta_header((void *)ptr, &is_free); if (is_free || (msize != 1)) { TINY_CHECK_FAIL("*** invariant broken for blocker block %p - %d %d\n", (void *)ptr, msize, is_free); return 0; } } } return 1; } kern_return_t tiny_in_use_enumerator(task_t task, void *context, unsigned type_mask, szone_t *szone, memory_reader_t reader, vm_range_recorder_t recorder) { size_t num_regions; size_t index; region_t *regions; vm_range_t buffer[MAX_RECORDER_BUFFER]; unsigned count = 0; kern_return_t err; region_t region; vm_range_t range; vm_range_t admin_range; vm_range_t ptr_range; unsigned char *mapped_region; uint32_t *block_header; uint32_t *in_use; unsigned block_index; unsigned block_limit; boolean_t is_free; msize_t msize; void *mapped_ptr; unsigned bit; magazine_t *tiny_mag_base = NULL; region_hash_generation_t *trg_ptr; err = reader(task, (vm_address_t)szone->tiny_rack.region_generation, sizeof(region_hash_generation_t), (void **)&trg_ptr); if (err) { return err; } num_regions = trg_ptr->num_regions_allocated; err = reader(task, (vm_address_t)trg_ptr->hashed_regions, sizeof(region_t) * num_regions, (void **)®ions); if (err) { return err; } if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) { // Map in all active magazines. Do this outside the iteration over regions. err = reader(task, (vm_address_t)(szone->tiny_rack.magazines), szone->tiny_rack.num_magazines * sizeof(magazine_t), (void **)&tiny_mag_base); if (err) { return err; } } for (index = 0; index < num_regions; ++index) { region = regions[index]; if (HASHRING_OPEN_ENTRY != region && HASHRING_REGION_DEALLOCATED != region) { range.address = (vm_address_t)TINY_REGION_ADDRESS(region); range.size = (vm_size_t)TINY_REGION_SIZE; if (type_mask & MALLOC_ADMIN_REGION_RANGE_TYPE) { admin_range.address = range.address + TINY_METADATA_START; admin_range.size = TINY_METADATA_SIZE; recorder(task, context, MALLOC_ADMIN_REGION_RANGE_TYPE, &admin_range, 1); } if (type_mask & (MALLOC_PTR_REGION_RANGE_TYPE | MALLOC_ADMIN_REGION_RANGE_TYPE)) { ptr_range.address = range.address; ptr_range.size = NUM_TINY_BLOCKS * TINY_QUANTUM; recorder(task, context, MALLOC_PTR_REGION_RANGE_TYPE, &ptr_range, 1); } if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) { vm_address_t mag_last_free; msize_t mag_last_free_msize = 0; err = reader(task, range.address, range.size, (void **)&mapped_region); if (err) { return err; } mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(mapped_region); magazine_t *tiny_mag_ptr = tiny_mag_base + mag_index; if (DEPOT_MAGAZINE_INDEX != mag_index) { mag_last_free = (uintptr_t)tiny_mag_ptr->mag_last_free; mag_last_free_msize = tiny_mag_ptr->mag_last_free_msize; } else { for (mag_index = 0; mag_index < szone->tiny_rack.num_magazines; mag_index++) { if ((void *)range.address == (tiny_mag_base + mag_index)->mag_last_free_rgn) { mag_last_free = (uintptr_t)(tiny_mag_base + mag_index)->mag_last_free; mag_last_free_msize = (tiny_mag_base + mag_index)->mag_last_free_msize; } } } block_header = (uint32_t *)(mapped_region + TINY_METADATA_START + sizeof(region_trailer_t)); in_use = TINY_INUSE_FOR_HEADER(block_header); block_index = 0; block_limit = NUM_TINY_BLOCKS; if (region == tiny_mag_ptr->mag_last_region) { block_index += TINY_MSIZE_FOR_BYTES(tiny_mag_ptr->mag_bytes_free_at_start); block_limit -= TINY_MSIZE_FOR_BYTES(tiny_mag_ptr->mag_bytes_free_at_end); } while (block_index < block_limit) { vm_size_t block_offset = TINY_BYTES_FOR_MSIZE(block_index); is_free = !BITARRAY_BIT(in_use, block_index); if (is_free) { mapped_ptr = mapped_region + block_offset; // mapped_region, the address at which 'range' in 'task' has been // mapped into our process, is not necessarily aligned to // TINY_BLOCKS_ALIGN. // // Since the code in get_tiny_free_size() assumes the pointer came // from a properly aligned tiny region, and mapped_region is not // necessarily aligned, then do the size calculation directly. // If the next bit is set in the header bitmap, then the size is one // quantum. Otherwise, read the size field. if (!BITARRAY_BIT(block_header, (block_index + 1))) { msize = TINY_FREE_SIZE(mapped_ptr); } else { msize = 1; } } else if (range.address + block_offset != mag_last_free) { msize = 1; bit = block_index + 1; while (!BITARRAY_BIT(block_header, bit)) { bit++; msize++; } buffer[count].address = range.address + block_offset; buffer[count].size = TINY_BYTES_FOR_MSIZE(msize); count++; if (count >= MAX_RECORDER_BUFFER) { recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count); count = 0; } } else { // Block is not free but it matches mag_last_free_ptr so even // though it is not marked free in the bitmap, we treat it as if // it is and move on msize = mag_last_free_msize; } if (!msize) { return KERN_FAILURE; // Somethings amiss. Avoid looping at this block_index. } block_index += msize; } if (count) { recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count); count = 0; } } } } return 0; } void * tiny_malloc_from_free_list(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize) { tiny_free_list_t *ptr; msize_t this_msize; grain_t slot = msize - 1; free_list_t *free_list = tiny_mag_ptr->mag_free_list; free_list_t *the_slot = free_list + slot; tiny_free_list_t *next; free_list_t *limit; #if defined(__LP64__) uint64_t bitmap; #else uint32_t bitmap; #endif msize_t leftover_msize; tiny_free_list_t *leftover_ptr; // Assumes we've locked the region CHECK_MAGAZINE_PTR_LOCKED(szone, tiny_mag_ptr, __PRETTY_FUNCTION__); // Look for an exact match by checking the freelist for this msize. // ptr = the_slot->p; if (ptr) { next = free_list_unchecksum_ptr(rack, &ptr->next); if (next) { next->previous = ptr->previous; } else { BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot); } the_slot->p = next; this_msize = msize; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), exact match ptr=%p, this_msize=%d\n", ptr, this_msize); } #endif goto return_tiny_alloc; } // Mask off the bits representing slots holding free blocks smaller than the // size we need. If there are no larger free blocks, try allocating from // the free space at the end of the tiny region. #if defined(__LP64__) bitmap = ((uint64_t *)(tiny_mag_ptr->mag_bitmap))[0] & ~((1ULL << slot) - 1); #else bitmap = tiny_mag_ptr->mag_bitmap[0] & ~((1 << slot) - 1); #endif if (!bitmap) { goto try_tiny_malloc_from_end; } slot = BITMAPV_CTZ(bitmap); limit = free_list + NUM_TINY_SLOTS - 1; free_list += slot; if (free_list < limit) { ptr = free_list->p; if (ptr) { next = free_list_unchecksum_ptr(rack, &ptr->next); free_list->p = next; if (next) { next->previous = ptr->previous; } else { BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot); } this_msize = get_tiny_free_size(ptr); goto add_leftover_and_proceed; } #if DEBUG_MALLOC malloc_report(ASL_LEVEL_ERR, "in tiny_malloc_from_free_list(), mag_bitmap out of sync, slot=%d\n", slot); #endif } // We are now looking at the last slot, which contains blocks equal to, or // due to coalescing of free blocks, larger than (NUM_TINY_SLOTS - 1) * tiny quantum size. // If the last freelist is not empty, and the head contains a block that is // larger than our request, then the remainder is put back on the free list. ptr = limit->p; if (ptr) { this_msize = get_tiny_free_size(ptr); next = free_list_unchecksum_ptr(rack, &ptr->next); if (this_msize - msize >= NUM_TINY_SLOTS) { // the leftover will go back to the free list, so we optimize by // modifying the free list rather than a pop and push of the head leftover_msize = this_msize - msize; leftover_ptr = (tiny_free_list_t *)((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize)); limit->p = leftover_ptr; if (next) { next->previous.u = free_list_checksum_ptr(rack, leftover_ptr); } leftover_ptr->previous = ptr->previous; leftover_ptr->next = ptr->next; set_tiny_meta_header_free(leftover_ptr, leftover_msize); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), last slot ptr=%p, msize=%d this_msize=%d\n", ptr, msize, this_msize); } #endif this_msize = msize; goto return_tiny_alloc; } if (next) { next->previous = ptr->previous; } limit->p = next; goto add_leftover_and_proceed; /* NOTREACHED */ } try_tiny_malloc_from_end: // Let's see if we can use tiny_mag_ptr->mag_bytes_free_at_end if (tiny_mag_ptr->mag_bytes_free_at_end >= TINY_BYTES_FOR_MSIZE(msize)) { ptr = (tiny_free_list_t *)((uintptr_t)TINY_REGION_END(tiny_mag_ptr->mag_last_region) - tiny_mag_ptr->mag_bytes_free_at_end); tiny_mag_ptr->mag_bytes_free_at_end -= TINY_BYTES_FOR_MSIZE(msize); if (tiny_mag_ptr->mag_bytes_free_at_end) { // let's add an in use block after ptr to serve as boundary set_tiny_meta_header_in_use_1((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize)); } this_msize = msize; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), from end ptr=%p, msize=%d\n", ptr, msize); } #endif goto return_tiny_alloc; } #if CONFIG_ASLR_INTERNAL // Try from start if nothing left at end if (tiny_mag_ptr->mag_bytes_free_at_start >= TINY_BYTES_FOR_MSIZE(msize)) { ptr = (tiny_free_list_t *)(TINY_REGION_ADDRESS(tiny_mag_ptr->mag_last_region) + tiny_mag_ptr->mag_bytes_free_at_start - TINY_BYTES_FOR_MSIZE(msize)); tiny_mag_ptr->mag_bytes_free_at_start -= TINY_BYTES_FOR_MSIZE(msize); if (tiny_mag_ptr->mag_bytes_free_at_start) { // let's add an in use block before ptr to serve as boundary set_tiny_meta_header_in_use_1((unsigned char *)ptr - TINY_QUANTUM); } this_msize = msize; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), from start ptr=%p, msize=%d\n", ptr, msize); } #endif goto return_tiny_alloc; } #endif return NULL; add_leftover_and_proceed: if (!this_msize || (this_msize > msize)) { leftover_msize = this_msize - msize; leftover_ptr = (tiny_free_list_t *)((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize)); #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), adding leftover ptr=%p, this_msize=%d\n", ptr, this_msize); } #endif tiny_free_list_add_ptr(rack, tiny_mag_ptr, leftover_ptr, leftover_msize); this_msize = msize; } return_tiny_alloc: tiny_mag_ptr->mag_num_objects++; tiny_mag_ptr->mag_num_bytes_in_objects += TINY_BYTES_FOR_MSIZE(this_msize); // Update this region's bytes in use count region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr)); size_t bytes_used = node->bytes_used + TINY_BYTES_FOR_MSIZE(this_msize); node->bytes_used = (unsigned int)bytes_used; // Emptiness discriminant if (bytes_used < DENSITY_THRESHOLD(TINY_REGION_PAYLOAD_BYTES)) { /* After this allocation the region is still sparse, so it must have been even more so before * the allocation. That implies the region is already correctly marked. Do nothing. */ } else { /* Region has crossed threshold from sparsity to density. Mark it not "suitable" on the * recirculation candidates list. */ node->recirc_suitable = FALSE; } #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), ptr=%p, this_msize=%d, msize=%d\n", ptr, this_msize, msize); } #endif if (this_msize > 1) { set_tiny_meta_header_in_use(ptr, this_msize); } else { set_tiny_meta_header_in_use_1(ptr); } return ptr; } void * tiny_malloc_should_clear(rack_t *rack, msize_t msize, boolean_t cleared_requested) { void *ptr; mag_index_t mag_index = tiny_mag_get_thread_index() % rack->num_magazines; magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]); MALLOC_TRACE(TRACE_tiny_malloc, (uintptr_t)rack, TINY_BYTES_FOR_MSIZE(msize), (uintptr_t)tiny_mag_ptr, cleared_requested); #if DEBUG_MALLOC if (DEPOT_MAGAZINE_INDEX == mag_index) { malloc_zone_error(rack->debug_flags, true, "malloc called for magazine index -1\n"); return (NULL); } if (!msize) { malloc_zone_error(rack->debug_flags, true, "invariant broken (!msize) in allocation (region)\n"); return (NULL); } #endif SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr); #if CONFIG_TINY_CACHE ptr = tiny_mag_ptr->mag_last_free; if (tiny_mag_ptr->mag_last_free_msize == msize) { // we have a winner tiny_mag_ptr->mag_last_free = NULL; tiny_mag_ptr->mag_last_free_msize = 0; tiny_mag_ptr->mag_last_free_rgn = NULL; SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); if (cleared_requested) { memset(ptr, 0, TINY_BYTES_FOR_MSIZE(msize)); } #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_should_clear(), tiny cache ptr=%p, msize=%d\n", ptr, msize); } #endif return ptr; } #endif /* CONFIG_TINY_CACHE */ while (1) { ptr = tiny_malloc_from_free_list(rack, tiny_mag_ptr, mag_index, msize); if (ptr) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); if (cleared_requested) { memset(ptr, 0, TINY_BYTES_FOR_MSIZE(msize)); } return ptr; } if (tiny_get_region_from_depot(rack, tiny_mag_ptr, mag_index, msize)) { ptr = tiny_malloc_from_free_list(rack, tiny_mag_ptr, mag_index, msize); if (ptr) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); if (cleared_requested) { memset(ptr, 0, TINY_BYTES_FOR_MSIZE(msize)); } return ptr; } } // The magazine is exhausted. A new region (heap) must be allocated to satisfy this call to malloc(). // The allocation, an mmap() system call, will be performed outside the magazine spin locks by the first // thread that suffers the exhaustion. That thread sets "alloc_underway" and enters a critical section. // Threads arriving here later are excluded from the critical section, yield the CPU, and then retry the // allocation. After some time the magazine is resupplied, the original thread leaves with its allocation, // and retry-ing threads succeed in the code just above. if (!tiny_mag_ptr->alloc_underway) { void *fresh_region; // time to create a new region (do this outside the magazine lock) tiny_mag_ptr->alloc_underway = TRUE; OSMemoryBarrier(); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); fresh_region = mvm_allocate_pages_securely(TINY_REGION_SIZE, TINY_BLOCKS_ALIGN, VM_MEMORY_MALLOC_TINY, rack->debug_flags); SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr); // DTrace USDT Probe MAGMALLOC_ALLOCREGION(TINY_SZONE_FROM_RACK(rack), (int)mag_index, fresh_region, TINY_REGION_SIZE); if (!fresh_region) { // out of memory! tiny_mag_ptr->alloc_underway = FALSE; OSMemoryBarrier(); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return NULL; } ptr = tiny_malloc_from_region_no_lock(rack, tiny_mag_ptr, mag_index, msize, fresh_region); // we don't clear because this freshly allocated space is pristine tiny_mag_ptr->alloc_underway = FALSE; OSMemoryBarrier(); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); return ptr; } else { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); yield(); SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr); } } /* NOTREACHED */ } size_t tiny_size(rack_t *rack, const void *ptr) { if (tiny_region_for_ptr_no_lock(rack, ptr)) { if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS) { return 0; } boolean_t is_free; msize_t msize = get_tiny_meta_header(ptr, &is_free); if (is_free) { return 0; } #if CONFIG_TINY_CACHE { mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr)); if (DEPOT_MAGAZINE_INDEX != mag_index) { magazine_t *tiny_mag_ptr = &rack->magazines[mag_index]; if (msize < TINY_QUANTUM && ptr == tiny_mag_ptr->mag_last_free) { return 0; } } else { for (mag_index = 0; mag_index < rack->num_magazines; mag_index++) { magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]); if (msize < TINY_QUANTUM && ptr == tiny_mag_ptr->mag_last_free) { return 0; } } } } #endif return TINY_BYTES_FOR_MSIZE(msize); } return 0; } static MALLOC_NOINLINE void free_tiny_botch(rack_t *rack, tiny_free_list_t *ptr) { mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr)); magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); malloc_zone_error(rack->debug_flags, true, "Double free of object %p\n", ptr); } void free_tiny(rack_t *rack, void *ptr, region_t tiny_region, size_t known_size) { msize_t msize; boolean_t is_free; mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(tiny_region); magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]); MALLOC_TRACE(TRACE_tiny_free, (uintptr_t)rack, (uintptr_t)ptr, (uintptr_t)tiny_mag_ptr, known_size); // ptr is known to be in tiny_region if (known_size) { msize = TINY_MSIZE_FOR_BYTES(known_size + TINY_QUANTUM - 1); } else { msize = get_tiny_meta_header(ptr, &is_free); if (is_free) { free_tiny_botch(rack, ptr); return; } } #if DEBUG_MALLOC if (!msize) { malloc_report(ASL_LEVEL_ERR, "*** free_tiny() block in use is too large: %p\n", ptr); return; } #endif SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr); #if CONFIG_TINY_CACHE // Depot does not participate in CONFIG_TINY_CACHE since it can't be directly malloc()'d if (DEPOT_MAGAZINE_INDEX != mag_index) { if (msize < TINY_QUANTUM) { // to see if the bits fit in the last 4 bits void *ptr2 = tiny_mag_ptr->mag_last_free; // Might be NULL msize_t msize2 = tiny_mag_ptr->mag_last_free_msize; region_t rgn2 = tiny_mag_ptr->mag_last_free_rgn; /* check that we don't already have this pointer in the cache */ if (ptr == ptr2) { free_tiny_botch(rack, ptr); return; } if ((rack->debug_flags & MALLOC_DO_SCRIBBLE) && msize) { memset(ptr, SCRABBLE_BYTE, TINY_BYTES_FOR_MSIZE(msize)); } tiny_mag_ptr->mag_last_free = ptr; tiny_mag_ptr->mag_last_free_msize = msize; tiny_mag_ptr->mag_last_free_rgn = tiny_region; if (!ptr2) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); CHECK(szone, __PRETTY_FUNCTION__); return; } msize = msize2; ptr = ptr2; tiny_region = rgn2; } } #endif /* CONFIG_TINY_CACHE */ // Now in the time it took to acquire the lock, the region may have migrated // from one magazine to another. I.e. trailer->mag_index is volatile. // In which case the magazine lock we obtained (namely magazines[mag_index].mag_lock) // is stale. If so, keep on tryin' ... region_trailer_t *trailer = REGION_TRAILER_FOR_TINY_REGION(tiny_region); mag_index_t refreshed_index; while (mag_index != (refreshed_index = trailer->mag_index)) { // Note assignment SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); mag_index = refreshed_index; tiny_mag_ptr = &(rack->magazines[mag_index]); SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr); } if (tiny_free_no_lock(rack, tiny_mag_ptr, mag_index, tiny_region, ptr, msize)) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); } CHECK(szone, __PRETTY_FUNCTION__); } unsigned tiny_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count) { msize_t msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1); unsigned found = 0; mag_index_t mag_index = tiny_mag_get_thread_index() % szone->tiny_rack.num_magazines; magazine_t *tiny_mag_ptr = &(szone->tiny_rack.magazines[mag_index]); // make sure to return objects at least one quantum in size if (!msize) { msize = 1; } CHECK(szone, __PRETTY_FUNCTION__); // We must lock the zone now, since tiny_malloc_from_free_list assumes that // the caller has done so. SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr); // with the zone locked, allocate objects from the free list until all // sufficiently large objects have been exhausted, or we have met our quota // of objects to allocate. while (found < count) { void *ptr = tiny_malloc_from_free_list(&szone->tiny_rack, tiny_mag_ptr, mag_index, msize); if (!ptr) { break; } *results++ = ptr; found++; } SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return found; } void tiny_batch_free(szone_t *szone, void **to_be_freed, unsigned count) { unsigned cc = 0; void *ptr; region_t tiny_region = NULL; boolean_t is_free; msize_t msize; magazine_t *tiny_mag_ptr = NULL; mag_index_t mag_index = -1; // frees all the pointers in to_be_freed // note that to_be_freed may be overwritten during the process if (!count) { return; } CHECK(szone, __PRETTY_FUNCTION__); while (cc < count) { ptr = to_be_freed[cc]; if (ptr) { if (NULL == tiny_region || tiny_region != TINY_REGION_FOR_PTR(ptr)) { // region same as last iteration? if (tiny_mag_ptr) { // non-NULL iff magazine lock taken SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); tiny_mag_ptr = NULL; } tiny_region = tiny_region_for_ptr_no_lock(&szone->tiny_rack, ptr); if (tiny_region) { tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone->tiny_rack.magazines, REGION_TRAILER_FOR_TINY_REGION(tiny_region), MAGAZINE_INDEX_FOR_TINY_REGION(tiny_region)); mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(tiny_region); } } if (tiny_region) { // this is a tiny pointer if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS) { break; // pointer to metadata; let the standard free deal with it } msize = get_tiny_meta_header(ptr, &is_free); if (is_free) { break; // a double free; let the standard free deal with it } if (!tiny_free_no_lock(&szone->tiny_rack, tiny_mag_ptr, mag_index, tiny_region, ptr, msize)) { // Arrange to re-acquire magazine lock tiny_mag_ptr = NULL; tiny_region = NULL; } to_be_freed[cc] = NULL; } else { // No region in this zone claims ptr; let the standard free deal with it break; } } cc++; } if (tiny_mag_ptr) { SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); tiny_mag_ptr = NULL; } } void print_tiny_free_list(rack_t *rack) { tiny_free_list_t *ptr; _SIMPLE_STRING b = _simple_salloc(); mag_index_t mag_index; if (b) { _simple_sappend(b, "tiny free sizes:\n"); for (mag_index = -1; mag_index < rack->num_magazines; mag_index++) { grain_t slot = 0; _simple_sprintf(b, "\tMagazine %d: ", mag_index); while (slot < NUM_TINY_SLOTS) { ptr = rack->magazines[mag_index].mag_free_list[slot].p; if (ptr) { _simple_sprintf(b, "%s%y[%d]; ", (slot == NUM_TINY_SLOTS - 1) ? ">=" : "", (slot + 1) * TINY_QUANTUM, free_list_count(rack, (free_list_t){ .p = ptr })); } slot++; } _simple_sappend(b, "\n"); } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%s\n", _simple_string(b)); _simple_sfree(b); } } void print_tiny_region(boolean_t verbose, region_t region, size_t bytes_at_start, size_t bytes_at_end) { unsigned counts[1024]; unsigned in_use = 0; uintptr_t start = (uintptr_t)TINY_REGION_ADDRESS(region); uintptr_t current = start + bytes_at_start; uintptr_t limit = (uintptr_t)TINY_REGION_END(region) - bytes_at_end; boolean_t is_free; msize_t msize; unsigned ci; _SIMPLE_STRING b; uintptr_t pgTot = 0; if (region == HASHRING_REGION_DEALLOCATED) { if ((b = _simple_salloc()) != NULL) { _simple_sprintf(b, "Tiny region [unknown address] was returned to the OS\n"); malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%s\n", _simple_string(b)); _simple_sfree(b); } return; } memset(counts, 0, sizeof(counts)); while (current < limit) { msize = get_tiny_meta_header((void *)current, &is_free); if (is_free && !msize && (current == start)) { // first block is all free uintptr_t pgLo = round_page_quanta(start + sizeof(tiny_free_list_t) + sizeof(msize_t)); uintptr_t pgHi = trunc_page_quanta(start + TINY_REGION_SIZE - sizeof(msize_t)); if (pgLo < pgHi) { pgTot += (pgHi - pgLo); } break; } if (!msize) { malloc_report(ASL_LEVEL_ERR, "*** error with %p: msize=%d\n", (void *)current, (unsigned)msize); break; } if (!is_free) { // block in use if (msize > NUM_TINY_SLOTS) { malloc_report(ASL_LEVEL_ERR, "*** error at %p msize for in_use is %d\n", (void *)current, msize); } if (msize < 1024) { counts[msize]++; } in_use++; } else { uintptr_t pgLo = round_page_quanta(current + sizeof(tiny_free_list_t) + sizeof(msize_t)); uintptr_t pgHi = trunc_page_quanta(current + TINY_BYTES_FOR_MSIZE(msize) - sizeof(msize_t)); if (pgLo < pgHi) { pgTot += (pgHi - pgLo); } } current += TINY_BYTES_FOR_MSIZE(msize); } if ((b = _simple_salloc()) != NULL) { _simple_sprintf(b, "Tiny region [%p-%p, %y] \t", (void *)start, TINY_REGION_END(region), (int)TINY_REGION_SIZE); _simple_sprintf(b, "Magazine=%d \t", MAGAZINE_INDEX_FOR_TINY_REGION(region)); _simple_sprintf(b, "Allocations in use=%d \t Bytes in use=%ly \t", in_use, BYTES_USED_FOR_TINY_REGION(region)); if (bytes_at_end || bytes_at_start) { _simple_sprintf(b, "Untouched=%ly ", bytes_at_end + bytes_at_start); } if (DEPOT_MAGAZINE_INDEX == MAGAZINE_INDEX_FOR_TINY_REGION(region)) { _simple_sprintf(b, "Advised MADV_FREE=%ly", pgTot); } else { _simple_sprintf(b, "Fragments subject to reclamation=%ly", pgTot); } if (verbose && in_use) { _simple_sappend(b, "\n\tSizes in use: "); for (ci = 0; ci < 1024; ci++) { if (counts[ci]) { _simple_sprintf(b, "%d[%d] ", TINY_BYTES_FOR_MSIZE(ci), counts[ci]); } } } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%s\n", _simple_string(b)); _simple_sfree(b); } } static char *tiny_freelist_fail_msg = "check: tiny free list incorrect "; #define TINY_FREELIST_FAIL(fmt, ...) \ malloc_zone_check_fail(tiny_freelist_fail_msg, \ " (slot=%u), counter=%d\n" fmt, slot, counter, __VA_ARGS__); boolean_t tiny_free_list_check(rack_t *rack, grain_t slot, unsigned counter) { mag_index_t mag_index; for (mag_index = -1; mag_index < rack->num_magazines; mag_index++) { magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]); SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr); unsigned count = 0; tiny_free_list_t *ptr = rack->magazines[mag_index].mag_free_list[slot].p; boolean_t is_free; tiny_free_list_t *previous = NULL; while (ptr) { is_free = tiny_meta_header_is_free(ptr); if (!is_free) { TINY_FREELIST_FAIL("*** in-use ptr in free list slot=%u count=%d ptr=%p\n", slot, count, ptr); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return 0; } if (((uintptr_t)ptr) & (TINY_QUANTUM - 1)) { TINY_FREELIST_FAIL("*** unaligned ptr in free list slot=%u count=%d ptr=%p\n", slot, count, ptr); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return 0; } if (!tiny_region_for_ptr_no_lock(rack, ptr)) { TINY_FREELIST_FAIL("*** ptr not in szone slot=%d count=%u ptr=%p\n", slot, count, ptr); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return 0; } if (free_list_unchecksum_ptr(rack, &ptr->previous) != previous) { TINY_FREELIST_FAIL("*** previous incorrectly set slot=%u count=%d ptr=%p\n", slot, count, ptr); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); return 0; } previous = ptr; ptr = free_list_unchecksum_ptr(rack, &ptr->next); count++; } SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); } return 1; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magazine_zone.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __MAGAZINE_ZONE_H #define __MAGAZINE_ZONE_H /********************* DEFINITIONS ************************/ // Out-of-band free list entry. Out-of-band free list entries are used // in specific cases where a free-list entry is the *only* data on a given page, // and the presence of that entry causes the page to stay dirty. // // `ptr` is all 16-bit quantum-sized index and packed, as that references a // block address inside the current region. `next` and `prev` have to be pointer // sized references, as these values can point to entries outside the current // region, so it's not safe to compact them. typedef struct { uintptr_t prev; uintptr_t next; uint16_t ptr; } MALLOC_PACKED oob_free_entry_s, *oob_free_entry_t; // In-place free list entry. Unlike the out-of-band entry, the in-place entries // are stored at the start of the range that has been freed. typedef struct _inplace_free_entry_s *inplace_free_entry_t; typedef struct { void *ptr; uint8_t checksum; } inplace_linkage_s; typedef union { inplace_free_entry_t p; uintptr_t u; } inplace_union; typedef struct _inplace_free_entry_s { inplace_union previous; inplace_union next; } inplace_free_entry_s, *inplace_free_entry_t; #ifdef __LP64__ MALLOC_STATIC_ASSERT(sizeof(inplace_free_entry_s) == 16, "inplace free list must be 16-bytes long"); #else MALLOC_STATIC_ASSERT(sizeof(inplace_free_entry_s) == 8, "inplace free list must be 8-bytes long"); #endif typedef struct _small_inplace_free_entry_s { inplace_linkage_s previous; inplace_linkage_s next; } small_inplace_free_entry_s, *small_inplace_free_entry_t; typedef union { small_inplace_free_entry_t small_inplace; inplace_free_entry_t inplace; oob_free_entry_t oob; void *p; } free_list_t; typedef struct { inplace_union previous; inplace_union next; } tiny_free_list_t; typedef unsigned int grain_t; // N.B. wide enough to index all free slots typedef int mag_index_t; #define CHECK_REGIONS (1 << 31) #define DISABLE_ASLR (1 << 30) #define MAX_RECORDER_BUFFER 256 /********************* DEFINITIONS for tiny ************************/ /* * Memory in the Tiny range is allocated from regions (heaps) pointed to by the * szone's hashed_regions pointer. * * Each region is laid out as a heap, followed by a header block, all within * a 1MB (2^20) block. This means there are 64520 16-byte blocks and the header * is 16138 bytes, making the total 1048458 bytes, leaving 118 bytes unused. * * The header block is arranged as in struct tiny_region defined just below, and * consists of two bitfields (or bit arrays) interleaved 32 bits by 32 bits. * * Each bitfield comprises NUM_TINY_BLOCKS bits, and refers to the corresponding * TINY_QUANTUM block within the heap. * * The bitfields are used to encode the state of memory within the heap. The header bit indicates * that the corresponding quantum is the first quantum in a block (either in use or free). The * in-use bit is set for the header if the block has been handed out (allocated). If the header * bit is not set, the in-use bit is invalid. * * The szone maintains an array of NUM_TINY_SLOTS freelists, each of which is used to hold * free objects of the corresponding quantum size. * * A free block is laid out depending on its size, in order to fit all free * blocks in 16 bytes, on both 32 and 64 bit platforms. One quantum blocks do * not store their size in the block, instead relying on the header information * to determine their size. Blocks of two or more quanta have room to store * their size in the block, and store it both after the 'next' pointer, and in * the last 2 bytes of the block. * * 1-quantum block * Offset (32-bit mode) (64-bit mode) * 0x0 0x0 : previous * 0x4 0x08 : next * end end * * >1-quantum block * Offset (32-bit mode) (64-bit mode) * 0x0 0x0 : previous * 0x4 0x08 : next * 0x8 0x10 : size (in quantum counts) * end - 2 end - 2 : size (in quantum counts) * end end * * All fields are pointer-sized, except for the size which is an unsigned short. * */ #define FOLLOWING_TINY_PTR(ptr, msize) (((unsigned char *)(ptr)) + ((msize) << SHIFT_TINY_QUANTUM)) #define TINY_BLOCKS_ALIGN (SHIFT_TINY_CEIL_BLOCKS + SHIFT_TINY_QUANTUM) // 20 #define TINY_ENTROPY_BITS 15 #define TINY_ENTROPY_MASK ((1 << TINY_ENTROPY_BITS) - 1) /* * Avoid having so much entropy that the end of a valid tiny allocation * might overrun the end of the tiny region. */ #if TINY_ENTROPY_MASK + NUM_TINY_SLOTS > NUM_TINY_BLOCKS #error Too many entropy bits for tiny region requested #endif /* * Enough room for the data, followed by the bit arrays (2-bits per block) * plus rounding to the nearest page. */ #define CEIL_NUM_TINY_BLOCKS_WORDS (((NUM_TINY_BLOCKS + 31) & ~31) >> 5) #define TINY_METADATA_SIZE (sizeof(region_trailer_t) + sizeof(tiny_header_inuse_pair_t) * CEIL_NUM_TINY_BLOCKS_WORDS) #define TINY_REGION_SIZE ((NUM_TINY_BLOCKS * TINY_QUANTUM + TINY_METADATA_SIZE + PAGE_MAX_SIZE - 1) & ~(PAGE_MAX_SIZE - 1)) #define TINY_METADATA_START (NUM_TINY_BLOCKS * TINY_QUANTUM) /* * Beginning and end pointers for a region's heap. */ #define TINY_REGION_ADDRESS(region) ((void *)(region)) #define TINY_REGION_END(region) ((void *)(((uintptr_t)(region)) + (NUM_TINY_BLOCKS * TINY_QUANTUM))) /* * Locate the heap base for a pointer known to be within a tiny region. */ #define TINY_REGION_FOR_PTR(_p) ((void *)((uintptr_t)(_p) & ~((1 << TINY_BLOCKS_ALIGN) - 1))) /* * Convert between byte and msize units. */ #define TINY_BYTES_FOR_MSIZE(_m) ((_m) << SHIFT_TINY_QUANTUM) #define TINY_MSIZE_FOR_BYTES(_b) ((_b) >> SHIFT_TINY_QUANTUM) #if MALLOC_TARGET_64BIT #define TINY_FREE_SIZE(ptr) (((msize_t *)(ptr))[8]) #else // MALLOC_TARGET_64BIT #define TINY_FREE_SIZE(ptr) (((msize_t *)(ptr))[4]) #endif // MALLOC_TARGET_64BIT #define TINY_PREVIOUS_MSIZE(ptr) ((msize_t *)(ptr))[-1] /* * Layout of a tiny region */ typedef uint32_t tiny_block_t[4]; // assert(TINY_QUANTUM == sizeof(tiny_block_t)) typedef struct tiny_header_inuse_pair { uint32_t header; uint32_t inuse; } tiny_header_inuse_pair_t; typedef struct region_trailer { struct region_trailer *prev; struct region_trailer *next; boolean_t recirc_suitable; volatile int pinned_to_depot; unsigned bytes_used; mag_index_t mag_index; } region_trailer_t; typedef struct tiny_region { tiny_block_t blocks[NUM_TINY_BLOCKS]; region_trailer_t trailer; // The interleaved bit arrays comprising the header and inuse bitfields. // The unused bits of each component in the last pair will be initialized to sentinel values. tiny_header_inuse_pair_t pairs[CEIL_NUM_TINY_BLOCKS_WORDS]; uint8_t pad[TINY_REGION_SIZE - (NUM_TINY_BLOCKS * sizeof(tiny_block_t)) - TINY_METADATA_SIZE]; } * tiny_region_t; /* * Per-region meta data for tiny allocator */ #define REGION_TRAILER_FOR_TINY_REGION(r) (&(((tiny_region_t)(r))->trailer)) #define MAGAZINE_INDEX_FOR_TINY_REGION(r) (REGION_TRAILER_FOR_TINY_REGION(r)->mag_index) #define BYTES_USED_FOR_TINY_REGION(r) (REGION_TRAILER_FOR_TINY_REGION(r)->bytes_used) /* * Locate the block header for a pointer known to be within a tiny region. */ #define TINY_BLOCK_HEADER_FOR_PTR(_p) ((void *)&(((tiny_region_t)TINY_REGION_FOR_PTR(_p))->pairs)) /* * Locate the inuse map for a given block header pointer. */ #define TINY_INUSE_FOR_HEADER(_h) ((void *)&(((tiny_header_inuse_pair_t *)(_h))->inuse)) /* * Compute the bitmap index for a pointer known to be within a tiny region. */ #define TINY_INDEX_FOR_PTR(_p) (((uintptr_t)(_p) >> SHIFT_TINY_QUANTUM) & (NUM_TINY_CEIL_BLOCKS - 1)) /* * Offset back to an szone_t given prior knowledge that this rack_t * is contained within an szone_t. * * Note: the only place this is used, the dtrace probes, only occurs * when the rack has been set up inside a scalable zone. Should * this ever be used somewhere that this does not hold true * (say, the test cases) then the pointer returned will be junk. */ #define TINY_SZONE_FROM_RACK(_r) \ (szone_t *)((uintptr_t)(_r) - offsetof(struct szone_s, tiny_rack)) #if !CONFIG_TINY_CACHE #warning CONFIG_TINY_CACHE turned off #endif #define TINY_REGION_PAYLOAD_BYTES (NUM_TINY_BLOCKS * TINY_QUANTUM) /********************* DEFINITIONS for small ************************/ /* * Memory in the Small range is allocated from regions (heaps) pointed to by the szone's hashed_regions * pointer. * * Each region is laid out as a heap, followed by the metadata array, all within an 8MB (2^23) block. * The array is arranged as an array of shorts, one for each SMALL_QUANTUM in the heap. There are * 16319 512-blocks and the array is 16319*2 bytes, which totals 8387966, leaving 642 bytes unused. * Once the region trailer is accounted for, there is room for 61 out-of-band free list entries in * the remaining padding (or 6, if the region was split into 16320 blocks, not 16319). * * The 16-bit shorts in the region are used for allocation metadata. The MSB bit marks a block as * either free, or not. The remaining 15-bits give the size of the allocation, defined in "msize", the * quantum-shifted size of the allocation. * * The metadata table either: * * 1. Stores the allocation size in the first short for the block, with the MSB cleared to indicate * that the block is allocated and in-use, or, * * 2. Stores the free-allocation size in the first and last shorts for the block, with the MSB set * in both places to indicate that the block is freed. (Storing the range in last block allows * for coalescing of adjacent free entries). * * 3. Zero, or "middle", meaning that this block in the region is not the start or end of an * allocated block. * * The small zone represents the free list in one of two ways: * * 1. In-line free list entries. These are stored at the starting address of the just-freed memory * and both the previous and next pointer are checksummed to attempt to detect use-after-free * writes. * * An in-line free list entry is laid out as: * |prev (uintptr_t)|checksum (uint8_t)|next (uintptr_t)|checksum (uint8_t) * * 2. Out-of-band free list entries. These utilitise the remaining padding in the 8mb region that * follows the blocks, metadata and region trailer. Out-of-band entries are used *iff* the * freed address lies on a page boundary and the freed region spans more than a page. If we were * to store the free list entry in-line in that memory, it would keep the entire page dirty, * so an out-of-band entry is used. * * An out-of-band free list entry is laid out as: * |prev (uintptr_t)|next (uintptr_t)|ptr (uint16_t)| * * The szone maintains an array of 32 freelists, each of which is used to hold free objects * of the corresponding quantum size. */ #define SMALL_IS_FREE (1 << 15) #define FOLLOWING_SMALL_PTR(ptr, msize) (((unsigned char *)(ptr)) + ((msize) << SHIFT_SMALL_QUANTUM)) /* * SMALL_IS_OOB is used mark to the MSB of OOB free list entries to show that they are in use, and * distinguish them from their initial, empty, state. */ #define SMALL_IS_OOB (1 << 15) #define SMALL_ENTROPY_BITS 13 #define SMALL_ENTROPY_MASK ((1 << SMALL_ENTROPY_BITS) - 1) /* * Avoid having so much entropy that the end of a valid small allocation * might overrun the end of the small region. */ #if SMALL_ENTROPY_MASK + NUM_SMALL_SLOTS > NUM_SMALL_BLOCKS #error Too many entropy bits for small region requested #endif #define SMALL_METADATA_SIZE (sizeof(region_trailer_t) + NUM_SMALL_BLOCKS * sizeof(msize_t)) #define SMALL_REGION_SIZE ((NUM_SMALL_BLOCKS * SMALL_QUANTUM + SMALL_METADATA_SIZE + PAGE_MAX_SIZE - 1) & ~(PAGE_MAX_SIZE - 1)) #define SMALL_METADATA_START (NUM_SMALL_BLOCKS * SMALL_QUANTUM) /* * Beginning and end pointers for a region's heap. */ #define SMALL_REGION_ADDRESS(region) ((unsigned char *)region) #define SMALL_REGION_END(region) (SMALL_REGION_ADDRESS(region) + (NUM_SMALL_BLOCKS * SMALL_QUANTUM)) /* * Locate the heap base for a pointer known to be within a small region. */ #define SMALL_REGION_FOR_PTR(_p) ((void *)((uintptr_t)(_p) & ~((1 << SMALL_BLOCKS_ALIGN) - 1))) #define SMALL_OFFSET_FOR_PTR(_p) ((uintptr_t)(_p) & ((1 << SMALL_BLOCKS_ALIGN) - 1)) /* * Convert between byte and msize units. */ #define SMALL_BYTES_FOR_MSIZE(_m) ((uint32_t)(_m) << SHIFT_SMALL_QUANTUM) #define SMALL_MSIZE_FOR_BYTES(_b) ((_b) >> SHIFT_SMALL_QUANTUM) #define SMALL_PREVIOUS_MSIZE(ptr) (*SMALL_METADATA_FOR_PTR(ptr - 1) & ~SMALL_IS_FREE) /* * Convert from msize unit to free list slot. */ #define SMALL_FREE_SLOT_COUNT(_r) \ (((_r)->debug_flags & MALLOC_EXTENDED_SMALL_SLOTS) ? \ NUM_SMALL_SLOTS_LARGEMEM + 1 : NUM_SMALL_SLOTS + 1) #define SMALL_FREE_SLOT_FOR_MSIZE(_r, _m) \ (((_m) <= SMALL_FREE_SLOT_COUNT(_r)) ? ((_m) - 1) : (SMALL_FREE_SLOT_COUNT(_r) - 1)) /* compare with MAGAZINE_FREELIST_BITMAP_WORDS */ #define SMALL_FREELIST_BITMAP_WORDS(_r) ((SMALL_FREE_SLOT_COUNT(_r) + 31) >> 5) /* * Offset back to an szone_t given prior knowledge that this rack_t * is contained within an szone_t. * * Note: the only place this is used, the dtrace probes, only occurs * when the rack has been set up inside a scalable zone. Should * this ever be used somewhere that this does not hold true * (say, the test cases) then the pointer returned will be junk. */ #define SMALL_SZONE_FROM_RACK(_r) \ (szone_t *)((uintptr_t)(_r) - offsetof(struct szone_s, small_rack)) /* * Layout of a small region */ typedef uint32_t small_block_t[SMALL_QUANTUM / sizeof(uint32_t)]; #define SMALL_HEAP_SIZE (NUM_SMALL_BLOCKS * sizeof(small_block_t)) #define SMALL_OOB_COUNT ((SMALL_REGION_SIZE - SMALL_HEAP_SIZE - SMALL_METADATA_SIZE) / sizeof(oob_free_entry_s)) #define SMALL_OOB_SIZE (SMALL_OOB_COUNT * sizeof(oob_free_entry_s)) #define SMALL_REGION_PAD (SMALL_REGION_SIZE - SMALL_HEAP_SIZE - SMALL_METADATA_SIZE - SMALL_OOB_SIZE) typedef struct small_region { small_block_t blocks[NUM_SMALL_BLOCKS]; region_trailer_t trailer; msize_t small_meta_words[NUM_SMALL_BLOCKS]; oob_free_entry_s small_oob_free_entries[SMALL_OOB_COUNT]; uint8_t pad[SMALL_REGION_PAD]; } * small_region_t; // The layout described above should result in a small_region_t being 8MB. MALLOC_STATIC_ASSERT(sizeof(struct small_region) == 8388608, "incorrect small_region_size"); /* * Per-region meta data for small allocator */ #define REGION_TRAILER_FOR_SMALL_REGION(r) (&(((small_region_t)(r))->trailer)) #define MAGAZINE_INDEX_FOR_SMALL_REGION(r) (REGION_TRAILER_FOR_SMALL_REGION(r)->mag_index) #define BYTES_USED_FOR_SMALL_REGION(r) (REGION_TRAILER_FOR_SMALL_REGION(r)->bytes_used) /* * Locate the metadata base for a pointer known to be within a small region. */ #define SMALL_META_HEADER_FOR_PTR(_p) (((small_region_t)SMALL_REGION_FOR_PTR(_p))->small_meta_words) /* * Compute the metadata index for a pointer known to be within a small region. */ #define SMALL_META_INDEX_FOR_PTR(_p) (((uintptr_t)(_p) >> SHIFT_SMALL_QUANTUM) & (NUM_SMALL_CEIL_BLOCKS - 1)) /* * Find the metadata word for a pointer known to be within a small region. */ #define SMALL_METADATA_FOR_PTR(_p) (SMALL_META_HEADER_FOR_PTR(_p) + SMALL_META_INDEX_FOR_PTR(_p)) /* * Determine whether a pointer known to be within a small region points to memory which is free. */ #define SMALL_PTR_IS_FREE(_p) (*SMALL_METADATA_FOR_PTR(_p) & SMALL_IS_FREE) /* * Extract the msize value for a pointer known to be within a small region. */ #define SMALL_PTR_SIZE(_p) (*SMALL_METADATA_FOR_PTR(_p) & ~SMALL_IS_FREE) #if !CONFIG_SMALL_CACHE #warning CONFIG_SMALL_CACHE turned off #endif #define SMALL_REGION_PAYLOAD_BYTES (NUM_SMALL_BLOCKS * SMALL_QUANTUM) /************************* DEFINITIONS for large ****************************/ typedef struct large_entry_s { vm_address_t address; vm_size_t size; boolean_t did_madvise_reusable; } large_entry_t; #if !CONFIG_LARGE_CACHE && DEBUG_MALLOC #warning CONFIG_LARGE_CACHE turned off #endif /******************************************************************************* * Per-processor magazine for tiny and small allocators ******************************************************************************/ typedef struct magazine_s { // vm_allocate()'d, so the array of magazines is page-aligned to begin with. // Take magazine_lock first, Depot lock when needed for recirc, then szone->{tiny,small}_regions_lock when needed for alloc _malloc_lock_s magazine_lock MALLOC_CACHE_ALIGN; // Protection for the crtical section that does allocate_pages outside the magazine_lock volatile boolean_t alloc_underway; // One element deep "death row", optimizes malloc/free/malloc for identical size. void *mag_last_free; msize_t mag_last_free_msize; // msize for mag_last_free #if MALLOC_TARGET_64BIT uint32_t _pad; #endif region_t mag_last_free_rgn; // holds the region for mag_last_free free_list_t mag_free_list[MAGAZINE_FREELIST_SLOTS]; uint32_t mag_bitmap[MAGAZINE_FREELIST_BITMAP_WORDS]; // the first and last free region in the last block are treated as big blocks in use that are not accounted for size_t mag_bytes_free_at_end; size_t mag_bytes_free_at_start; region_t mag_last_region; // Valid iff mag_bytes_free_at_end || mag_bytes_free_at_start > 0 // bean counting ... size_t mag_num_bytes_in_objects; size_t num_bytes_in_magazine; unsigned mag_num_objects; // recirculation list -- invariant: all regions owned by this magazine that meet the emptiness criteria // are located nearer to the head of the list than any region that doesn't satisfy that criteria. // Doubly linked list for efficient extraction. unsigned recirculation_entries; region_trailer_t *firstNode; region_trailer_t *lastNode; #if MALLOC_TARGET_64BIT uintptr_t pad[320 - 14 - MAGAZINE_FREELIST_SLOTS - (MAGAZINE_FREELIST_BITMAP_WORDS + 1) / 2]; #else uintptr_t pad[320 - 16 - MAGAZINE_FREELIST_SLOTS - MAGAZINE_FREELIST_BITMAP_WORDS]; #endif } magazine_t; #if MALLOC_TARGET_64BIT MALLOC_STATIC_ASSERT(sizeof(magazine_t) == 2560, "Incorrect padding in magazine_t"); #else MALLOC_STATIC_ASSERT(sizeof(magazine_t) == 1280, "Incorrect padding in magazine_t"); #endif #define TINY_MAX_MAGAZINES 64 /* MUST BE A POWER OF 2! */ #define TINY_MAGAZINE_PAGED_SIZE \ (((sizeof(magazine_t) * (TINY_MAX_MAGAZINES + 1)) + vm_page_quanta_size - 1) & \ ~(vm_page_quanta_size - 1)) /* + 1 for the Depot */ #define SMALL_MAX_MAGAZINES 64 /* MUST BE A POWER OF 2! */ #define SMALL_MAGAZINE_PAGED_SIZE \ (((sizeof(magazine_t) * (SMALL_MAX_MAGAZINES + 1)) + vm_page_quanta_size - 1) & \ ~(vm_page_quanta_size - 1)) /* + 1 for the Depot */ #define DEPOT_MAGAZINE_INDEX -1 /****************************** zone itself ***********************************/ /* * Note that objects whose adddress are held in pointers here must be pursued * individually in the {tiny,small}_in_use_enumeration() routines. See for * example the treatment of region_hash_generation and tiny_magazines below. */ typedef struct szone_s { // vm_allocate()'d, so page-aligned to begin with. malloc_zone_t basic_zone; // first page will be given read-only protection uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)]; unsigned long cpu_id_key; // unused // remainder of structure is R/W (contains no function pointers) unsigned debug_flags; void *log_address; /* Allocation racks per allocator type. */ struct rack_s tiny_rack; struct rack_s small_rack; /* large objects: all the rest */ _malloc_lock_s large_szone_lock MALLOC_CACHE_ALIGN; // One customer at a time for large unsigned num_large_objects_in_use; unsigned num_large_entries; large_entry_t *large_entries; // hashed by location; null entries don't count size_t num_bytes_in_large_objects; #if CONFIG_LARGE_CACHE int large_entry_cache_oldest; int large_entry_cache_newest; large_entry_t large_entry_cache[LARGE_ENTRY_CACHE_SIZE]; // "death row" for large malloc/free boolean_t large_legacy_reset_mprotect; size_t large_entry_cache_reserve_bytes; size_t large_entry_cache_reserve_limit; size_t large_entry_cache_bytes; // total size of death row, bytes #endif /* flag and limits pertaining to altered malloc behavior for systems with * large amounts of physical memory */ unsigned is_largemem; unsigned large_threshold; unsigned vm_copy_threshold; /* security cookie */ uintptr_t cookie; /* The purgeable zone constructed by create_purgeable_zone() would like to hand off tiny and small * allocations to the default scalable zone. Record the latter as the "helper" zone here. */ struct szone_s *helper_zone; boolean_t flotsam_enabled; } szone_t; #define SZONE_PAGED_SIZE round_page_quanta((sizeof(szone_t))) #endif // __MAGAZINE_ZONE_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/magmallocProvider.d ================================================ provider magmalloc { probe refreshIndex(void *, int, int); probe depotRegion(void *, int, void *, int, int); probe recircRegion(void *, int, void *, int, int); probe allocRegion(void *, int, void *, int); probe deallocRegion(void *, void *, int); probe madvfreeRegion(void *, void *, void *, int); probe pressureReliefBegin(void *, char *, int); probe pressureReliefEnd(void *, char *, int, int); probe mallocErrorBreak(); }; #pragma D attributes Evolving/Evolving/ISA provider magmalloc provider #pragma D attributes Private/Private/Unknown provider magmalloc module #pragma D attributes Private/Private/Unknown provider magmalloc function #pragma D attributes Evolving/Evolving/ISA provider magmalloc name #pragma D attributes Evolving/Evolving/ISA provider magmalloc args ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/malloc.c ================================================ /* * Copyright (c) 1999, 2000, 2003, 2005, 2008, 2012 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" #if TARGET_OS_IPHONE // malloc_report(ASL_LEVEL_INFO...) on iOS doesn't show up in the Xcode Console log of the device, // but ASL_LEVEL_NOTICE does. So raising the log level is helpful. #undef ASL_LEVEL_INFO #define ASL_LEVEL_INFO ASL_LEVEL_NOTICE #endif // TARGET_OS_IPHONE #define USE_SLEEP_RATHER_THAN_ABORT 0 /* MAX_LITE_MALLOCS If msl lite is turned on due to a memory resource exception use this value as the maximum number of allocations allowed before msl lite is turned off. This prevents msl lite from being enabled indefinitely if the process never reaches 100% of its jetsam limit. See rdar://problem/25950426 for a discussion of how this number was determined. */ #define MAX_LITE_MALLOCS 100000000 typedef void(malloc_logger_t)(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t num_hot_frames_to_skip); extern malloc_logger_t *__syscall_logger; // use this to set up syscall logging (e.g., vm_allocate, vm_deallocate, mmap, munmap) static _malloc_lock_s _malloc_lock = _MALLOC_LOCK_INIT; #define MALLOC_LOCK() _malloc_lock_lock(&_malloc_lock) #define MALLOC_TRY_LOCK() _malloc_lock_trylock(&_malloc_lock) #define MALLOC_UNLOCK() _malloc_lock_unlock(&_malloc_lock) #define MALLOC_REINIT_LOCK() _malloc_lock_init(&_malloc_lock) /* The following variables are exported for the benefit of performance tools * * It should always be safe to first read malloc_num_zones, then read * malloc_zones without taking the lock, if only iteration is required and * provided that when malloc_destroy_zone is called all prior operations on that * zone are complete and no further calls referencing that zone can be made. */ int32_t malloc_num_zones = 0; int32_t malloc_num_zones_allocated = 0; malloc_zone_t **malloc_zones = 0; malloc_logger_t *malloc_logger = NULL; static malloc_zone_t *initial_default_zone = NULL; unsigned malloc_debug_flags = 0; boolean_t malloc_tracing_enabled = false; unsigned malloc_check_start = 0; // 0 means don't check unsigned malloc_check_counter = 0; unsigned malloc_check_each = 1000; static int malloc_check_sleep = 100; // default 100 second sleep static int malloc_check_abort = 0; // default is to sleep, not abort static os_once_t _malloc_initialize_pred; // Used by memory resource exceptions and enabling/disabling malloc stack logging via malloc_memory_event_handler static boolean_t warn_mode_entered = false; static boolean_t warn_mode_disable_retries = false; static stack_logging_mode_type msl_type_enabled_at_runtime = stack_logging_mode_none; /* * Counters that coordinate zone destruction (in malloc_zone_unregister) with * find_registered_zone (here abbreviated as FRZ). */ static int32_t volatile counterAlice = 0, counterBob = 0; static int32_t volatile * volatile pFRZCounterLive = &counterAlice; static int32_t volatile * volatile pFRZCounterDrain = &counterBob; unsigned int _os_cpu_number_override = -1; static inline malloc_zone_t *inline_malloc_default_zone(void) __attribute__((always_inline)); #define MALLOC_LOG_TYPE_ALLOCATE stack_logging_type_alloc #define MALLOC_LOG_TYPE_DEALLOCATE stack_logging_type_dealloc #define MALLOC_LOG_TYPE_HAS_ZONE stack_logging_flag_zone #define MALLOC_LOG_TYPE_CLEARED stack_logging_flag_cleared #define DEFAULT_MALLOC_ZONE_STRING "DefaultMallocZone" #define DEFAULT_PUREGEABLE_ZONE_STRING "DefaultPurgeableMallocZone" #define MALLOC_HELPER_ZONE_STRING "MallocHelperZone" MALLOC_NOEXPORT unsigned int phys_ncpus; MALLOC_NOEXPORT unsigned int logical_ncpus; MALLOC_NOEXPORT unsigned int hyper_shift; // Boot argument for max magazine control static const char max_magazines_boot_arg[] = "malloc_max_magazines"; /********* Utilities ************/ static bool _malloc_entropy_initialized; void __malloc_init(const char *apple[]); static int __entropy_from_kernel(const char *str) { unsigned long long val; char tmp[20], *p; int idx = 0; /* Skip over key to the first value */ str = strchr(str, '='); if (str == NULL) { return 0; } str++; while (str && idx < sizeof(malloc_entropy) / sizeof(malloc_entropy[0])) { strlcpy(tmp, str, 20); p = strchr(tmp, ','); if (p) { *p = '\0'; } val = strtoull_l(tmp, NULL, 0, NULL); malloc_entropy[idx] = (uint64_t)val; idx++; if ((str = strchr(str, ',')) != NULL) { str++; } } return idx; } static void __malloc_init_from_bootargs(const char *bootargs) { // The maximum number of magazines can be set either via a // boot argument or from the environment. Get the boot argument value // here and store it. We can't bounds check it until we have phys_ncpus, // which happens later in _malloc_initialize(), along with handling // of the environment value setting. char value_buf[256]; const char *flag = malloc_common_value_for_key_copy(bootargs, max_magazines_boot_arg, value_buf, sizeof(value_buf)); if (flag) { const char *endp; long value = malloc_common_convert_to_long(flag, &endp); if (!*endp && value >= 0) { max_magazines = (unsigned int)value; } else { malloc_report(ASL_LEVEL_ERR, "malloc_max_magazines must be positive - ignored.\n"); } } } /* TODO: Investigate adding _malloc_initialize() into this libSystem initializer */ void __malloc_init(const char *apple[]) { // We could try to be clever and cater for arbitrary length bootarg // strings, but it's probably not worth it, especially as we would need // to temporarily allocate at least a page of memory to read the bootargs // into. char bootargs[1024] = { '\0' }; size_t len = sizeof(bootargs) - 1; if (!sysctlbyname("kern.bootargs", bootargs, &len, NULL, 0) && len > 0) { bootargs[len + 1] = '\0'; } #if CONFIG_NANOZONE // TODO: envp should be passed down from Libsystem const char **envp = (const char **)*_NSGetEnviron(); nano_common_init(envp, apple, bootargs); #endif const char **p; for (p = apple; p && *p; p++) { if (strstr(*p, "malloc_entropy") == *p) { int count = __entropy_from_kernel(*p); bzero((void *)*p, strlen(*p)); if (sizeof(malloc_entropy) / sizeof(malloc_entropy[0]) == count) { _malloc_entropy_initialized = true; } break; } } if (!_malloc_entropy_initialized) { getentropy((void*)malloc_entropy, sizeof(malloc_entropy)); _malloc_entropy_initialized = true; } __malloc_init_from_bootargs(bootargs); mvm_aslr_init(); } static malloc_zone_t* lite_zone = NULL; MALLOC_ALWAYS_INLINE static inline malloc_zone_t * runtime_default_zone() { return (lite_zone) ? lite_zone : inline_malloc_default_zone(); } static size_t default_zone_size(malloc_zone_t *zone, const void *ptr) { zone = runtime_default_zone(); return zone->size(zone, ptr); } static void * default_zone_malloc(malloc_zone_t *zone, size_t size) { zone = runtime_default_zone(); return zone->malloc(zone, size); } static void * default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size) { zone = runtime_default_zone(); return zone->calloc(zone, num_items, size); } static void * default_zone_valloc(malloc_zone_t *zone, size_t size) { zone = runtime_default_zone(); return zone->valloc(zone, size); } static void default_zone_free(malloc_zone_t *zone, void *ptr) { zone = runtime_default_zone(); return zone->free(zone, ptr); } static void * default_zone_realloc(malloc_zone_t *zone, void *ptr, size_t new_size) { zone = runtime_default_zone(); return zone->realloc(zone, ptr, new_size); } static void default_zone_destroy(malloc_zone_t *zone) { zone = runtime_default_zone(); return zone->destroy(zone); } static unsigned default_zone_batch_malloc(malloc_zone_t *zone, size_t size, void **results, unsigned count) { zone = runtime_default_zone(); return zone->batch_malloc(zone, size, results, count); } static void default_zone_batch_free(malloc_zone_t *zone, void **to_be_freed, unsigned count) { zone = runtime_default_zone(); return zone->batch_free(zone, to_be_freed, count); } static void * default_zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size) { zone = runtime_default_zone(); return zone->memalign(zone, alignment, size); } static void default_zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size) { zone = runtime_default_zone(); return zone->free_definite_size(zone, ptr, size); } static size_t default_zone_pressure_relief(malloc_zone_t *zone, size_t goal) { zone = runtime_default_zone(); return zone->pressure_relief(zone, goal); } static boolean_t default_zone_malloc_claimed_address(malloc_zone_t *zone, void *ptr) { zone = runtime_default_zone(); return malloc_zone_claimed_address(zone, ptr); } static kern_return_t default_zone_ptr_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder) { malloc_zone_t *zone = runtime_default_zone(); return zone->introspect->enumerator(task, context, type_mask, (vm_address_t) zone, reader, recorder); } static size_t default_zone_good_size(malloc_zone_t *zone, size_t size) { zone = runtime_default_zone(); return zone->introspect->good_size(zone, size); } static boolean_t default_zone_check(malloc_zone_t *zone) { zone = runtime_default_zone(); return zone->introspect->check(zone); } static void default_zone_print(malloc_zone_t *zone, boolean_t verbose) { zone = runtime_default_zone(); return (void)zone->introspect->print(zone, verbose); } static void default_zone_log(malloc_zone_t *zone, void *log_address) { zone = runtime_default_zone(); return zone->introspect->log(zone, log_address); } static void default_zone_force_lock(malloc_zone_t *zone) { zone = runtime_default_zone(); return zone->introspect->force_lock(zone); } static void default_zone_force_unlock(malloc_zone_t *zone) { zone = runtime_default_zone(); return zone->introspect->force_unlock(zone); } static void default_zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { zone = runtime_default_zone(); return zone->introspect->statistics(zone, stats); } static boolean_t default_zone_locked(malloc_zone_t *zone) { zone = runtime_default_zone(); return zone->introspect->zone_locked(zone); } static void default_zone_reinit_lock(malloc_zone_t *zone) { zone = runtime_default_zone(); return zone->introspect->reinit_lock(zone); } static struct malloc_introspection_t default_zone_introspect = { default_zone_ptr_in_use_enumerator, default_zone_good_size, default_zone_check, default_zone_print, default_zone_log, default_zone_force_lock, default_zone_force_unlock, default_zone_statistics, default_zone_locked, NULL, NULL, NULL, NULL, default_zone_reinit_lock }; typedef struct { malloc_zone_t malloc_zone; uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)]; } virtual_default_zone_t; static virtual_default_zone_t virtual_default_zone __attribute__((section("__DATA,__v_zone"))) __attribute__((aligned(PAGE_MAX_SIZE))) = { NULL, NULL, default_zone_size, default_zone_malloc, default_zone_calloc, default_zone_valloc, default_zone_free, default_zone_realloc, default_zone_destroy, DEFAULT_MALLOC_ZONE_STRING, default_zone_batch_malloc, default_zone_batch_free, &default_zone_introspect, 10, default_zone_memalign, default_zone_free_definite_size, default_zone_pressure_relief, default_zone_malloc_claimed_address, }; static malloc_zone_t *default_zone = &virtual_default_zone.malloc_zone; static boolean_t has_default_zone0(void) { if (!malloc_zones) { return false; } return initial_default_zone == malloc_zones[0]; } static inline malloc_zone_t *find_registered_zone(const void *, size_t *) __attribute__((always_inline)); static inline malloc_zone_t * find_registered_zone(const void *ptr, size_t *returned_size) { // Returns a zone which contains ptr, else NULL if (0 == malloc_num_zones) { if (returned_size) { *returned_size = 0; } return NULL; } // first look in the lite zone if (lite_zone) { malloc_zone_t *zone = lite_zone; size_t size = zone->size(zone, ptr); if (size) { // Claimed by this zone? if (returned_size) { *returned_size = size; } // Return the virtual default zone instead of the lite zone - see return default_zone; } } // The default zone is registered in malloc_zones[0]. There's no danger that it will ever be unregistered. // So don't advance the FRZ counter yet. malloc_zone_t *zone = malloc_zones[0]; size_t size = zone->size(zone, ptr); if (size) { // Claimed by this zone? if (returned_size) { *returned_size = size; } // Asan and others replace the zone at position 0 with their own zone. // In that case just return that zone as they need this information. // Otherwise return the virtual default zone, not the actual zone in position 0. if (!has_default_zone0()) { return zone; } else { return default_zone; } } int32_t volatile *pFRZCounter = pFRZCounterLive; // Capture pointer to the counter of the moment OSAtomicIncrement32Barrier(pFRZCounter); // Advance this counter -- our thread is in FRZ unsigned index; int32_t limit = *(int32_t volatile *)&malloc_num_zones; malloc_zone_t **zones = &malloc_zones[1]; // From this point on, FRZ is accessing the malloc_zones[] array without locking // in order to avoid contention on common operations (such as non-default-zone free()). // In order to ensure that this is actually safe to do, register/unregister take care // to: // // 1. Register ensures that newly inserted pointers in malloc_zones[] are visible // when malloc_num_zones is incremented. At the moment, we're relying on that store // ordering to work without taking additional steps here to ensure load memory // ordering. // // 2. Unregister waits for all readers in FRZ to complete their iteration before it // returns from the unregister call (during which, even unregistered zone pointers // are still valid). It also ensures that all the pointers in the zones array are // valid until it returns, so that a stale value in limit is not dangerous. for (index = 1; index < limit; ++index, ++zones) { zone = *zones; size = zone->size(zone, ptr); if (size) { // Claimed by this zone? goto out; } } // Unclaimed by any zone. zone = NULL; size = 0; out: if (returned_size) { *returned_size = size; } OSAtomicDecrement32Barrier(pFRZCounter); // our thread is leaving FRZ return zone; } void malloc_error_break(void) { // Provides a non-inlined place for various malloc error procedures to call // that will be called after an error message appears. It does not make // sense for developers to call this function, so it is marked // hidden to prevent it from becoming API. MAGMALLOC_MALLOCERRORBREAK(); // DTrace USDT probe } int malloc_gdb_po_unsafe(void) { // In order to implement "po" other data formatters in gdb, the debugger // calls functions that call malloc. The debugger will only run one thread // of the program in this case, so if another thread is holding a zone lock, // gdb may deadlock in this case. // // Iterate over the zones in malloc_zones, and call "trylock" on the zone // lock. If trylock succeeds, unlock it, otherwise return "locked". Returns // 0 == safe, 1 == locked/unsafe. if (__stack_logging_locked()) { return 1; } malloc_zone_t **zones = malloc_zones; unsigned i, e = malloc_num_zones; for (i = 0; i != e; ++i) { malloc_zone_t *zone = zones[i]; // Version must be >= 5 to look at the new introspection field. if (zone->version < 5) { continue; } if (zone->introspect->zone_locked && zone->introspect->zone_locked(zone)) { return 1; } } return 0; } /********* Creation and destruction ************/ static void set_flags_from_environment(void); static void malloc_zone_register_while_locked(malloc_zone_t *zone) { size_t protect_size; unsigned i; /* scan the list of zones, to see if this zone is already registered. If * so, print an error message and return. */ for (i = 0; i != malloc_num_zones; ++i) { if (zone == malloc_zones[i]) { malloc_report(ASL_LEVEL_ERR, "Attempted to register zone more than once: %p\n", zone); return; } } if (malloc_num_zones == malloc_num_zones_allocated) { size_t malloc_zones_size = malloc_num_zones * sizeof(malloc_zone_t *); mach_vm_size_t alloc_size = round_page(malloc_zones_size + vm_page_size); mach_vm_address_t vm_addr; int alloc_flags = VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MALLOC); vm_addr = vm_page_size; kern_return_t kr = mach_vm_allocate(mach_task_self(), &vm_addr, alloc_size, alloc_flags); if (kr) { malloc_report(ASL_LEVEL_ERR, "malloc_zone_register allocation failed: %d\n", kr); return; } malloc_zone_t **new_zones = (malloc_zone_t **)vm_addr; /* If there were previously allocated malloc zones, we need to copy them * out of the previous array and into the new zones array */ if (malloc_zones) { memcpy(new_zones, malloc_zones, malloc_zones_size); vm_addr = (mach_vm_address_t)malloc_zones; mach_vm_size_t dealloc_size = round_page(malloc_zones_size); mach_vm_deallocate(mach_task_self(), vm_addr, dealloc_size); } /* Update the malloc_zones pointer, which we leak if it was previously * allocated, and the number of zones allocated */ protect_size = (size_t)alloc_size; malloc_zones = new_zones; malloc_num_zones_allocated = (int32_t)(alloc_size / sizeof(malloc_zone_t *)); } else { /* If we don't need to reallocate zones, we need to briefly change the * page protection the malloc zones to allow writes */ protect_size = malloc_num_zones_allocated * sizeof(malloc_zone_t *); mprotect(malloc_zones, protect_size, PROT_READ | PROT_WRITE); } /* This store-increment needs to be visible in the correct * order to any threads in find_registered_zone, such that if the incremented value * in malloc_num_zones is visible then the pointer write before it must also be visible. * * While we could be slightly more efficent here with atomic ops the cleanest way to * ensure the proper store-release operation is performed is to use OSAtomic*Barrier * to update malloc_num_zones. */ malloc_zones[malloc_num_zones] = zone; OSAtomicIncrement32Barrier(&malloc_num_zones); /* Finally, now that the zone is registered, disallow write access to the * malloc_zones array */ mprotect(malloc_zones, protect_size, PROT_READ); //malloc_report(ASL_LEVEL_INFO, "Registered malloc_zone %p in malloc_zones %p [%u zones, %u bytes]\n", zone, malloc_zones, // malloc_num_zones, protect_size); } static void create_and_insert_lite_zone_while_locked() { malloc_zone_t *zone0 = malloc_zones[0]; malloc_zone_t *stack_logging_lite_zone = create_stack_logging_lite_zone(0, zone0, malloc_debug_flags); malloc_zone_register_while_locked(stack_logging_lite_zone); malloc_set_zone_name(stack_logging_lite_zone, MALLOC_STOCK_LOGGING_LITE_ZONE_NAME); lite_zone = stack_logging_lite_zone; } boolean_t turn_on_stack_logging(stack_logging_mode_type mode) { boolean_t ret = false; MALLOC_LOCK(); if (!stack_logging_enable_logging) { if (__uniquing_table_memory_was_deleted()) { // It would great to be able re-enable even if the uniquing table has been deleted // malloc stack logging should be able to recreate the uniquing table if needed } else { switch (mode) { case stack_logging_mode_all: __prepare_to_log_stacks(false); malloc_logger = __disk_stack_logging_log_stack; __syscall_logger = __disk_stack_logging_log_stack; stack_logging_mode = mode; stack_logging_enable_logging = 1; ret = true; malloc_report(ASL_LEVEL_INFO, "recording malloc and VM allocation stacks to disk using standard recorder\n"); break; case stack_logging_mode_malloc: __prepare_to_log_stacks(false); malloc_logger = __disk_stack_logging_log_stack; stack_logging_mode = mode; stack_logging_enable_logging = 1; ret = true; malloc_report(ASL_LEVEL_INFO, "recording malloc (but not VM allocation) stacks to disk using standard recorder\n"); break; case stack_logging_mode_vm: __prepare_to_log_stacks(false); __syscall_logger = __disk_stack_logging_log_stack; stack_logging_mode = mode; stack_logging_enable_logging = 1; ret = true; malloc_report(ASL_LEVEL_INFO, "recording VM allocation (but not malloc) stacks to disk using standard recorder\n"); break; case stack_logging_mode_lite: if (!has_default_zone0()) { malloc_report(ASL_LEVEL_ERR, "zone[0] is not the normal default zone so can't turn on lite mode.\n", mode); ret = false; } else { malloc_report(ASL_LEVEL_INFO, "recording malloc (and VM allocation) stacks using lite mode\n"); if (lite_zone) { enable_stack_logging_lite(); } else { if (__prepare_to_log_stacks(true)) { __syscall_logger = __disk_stack_logging_log_stack; stack_logging_mode = stack_logging_mode_lite; stack_logging_enable_logging = 1; __prepare_to_log_stacks_stage2(); create_and_insert_lite_zone_while_locked(); enable_stack_logging_lite(); } } ret = true; } break; case stack_logging_mode_vmlite: if (__prepare_to_log_stacks(true)) { __syscall_logger = __disk_stack_logging_log_stack; stack_logging_mode = mode; stack_logging_enable_logging = 1; malloc_report(ASL_LEVEL_INFO, "recording VM allocation (but not malloc) stacks using lite mode\n"); } break; default: malloc_report(ASL_LEVEL_ERR, "invalid mode %d passed to turn_on_stack_logging\n", mode); break; } } } else { malloc_report(ASL_LEVEL_ERR, "malloc stack logging already enabled.\n"); } MALLOC_UNLOCK(); return ret; } void turn_off_stack_logging() { MALLOC_LOCK(); if (stack_logging_enable_logging) { switch (stack_logging_mode) { case stack_logging_mode_all: malloc_logger = NULL; __syscall_logger = NULL; stack_logging_enable_logging = 0; malloc_report(ASL_LEVEL_INFO, "turning off recording malloc and VM allocation stacks to disk using standard recorder\n"); break; case stack_logging_mode_malloc: malloc_logger = NULL; stack_logging_enable_logging = 0; malloc_report(ASL_LEVEL_INFO, "turnning off recording malloc (but not VM allocation) stacks to disk using standard recorder\n"); break; case stack_logging_mode_vm: __syscall_logger = NULL; stack_logging_enable_logging = 0; malloc_report(ASL_LEVEL_INFO, "turning off recording VM allocation (but not malloc) stacks to disk using standard recorder\n"); break; case stack_logging_mode_lite: malloc_report(ASL_LEVEL_INFO, "turning off recording malloc (but not VM allocation) stacks using lite mode\n"); disable_stack_logging_lite(); stack_logging_enable_logging = 0; break; case stack_logging_mode_vmlite: __syscall_logger = NULL; stack_logging_enable_logging = 0; malloc_report(ASL_LEVEL_INFO, "turning off recording VM allocation stacks using lite mode\n"); break; default: malloc_report(ASL_LEVEL_ERR, "invalid stack_logging_mode %d in turn_off_stack_logging\n", stack_logging_mode); break; } } else { malloc_report(ASL_LEVEL_ERR, "malloc stack logging not enabled.\n"); } MALLOC_UNLOCK(); } // To be used in _malloc_initialize_once() only, call that function instead. static void _malloc_initialize(void *context __unused) { MALLOC_LOCK(); unsigned n; malloc_zone_t *zone = NULL; if (!_malloc_entropy_initialized) { // Lazy initialization may occur before __malloc_init (rdar://27075409) // TODO: make this a fatal error malloc_report(ASL_LEVEL_ERR, "*** malloc was initialized without entropy\n"); } phys_ncpus = *(uint8_t *)(uintptr_t)_COMM_PAGE_PHYSICAL_CPUS; logical_ncpus = *(uint8_t *)(uintptr_t)_COMM_PAGE_LOGICAL_CPUS; if (0 != (logical_ncpus % phys_ncpus)) { MALLOC_REPORT_FATAL_ERROR(logical_ncpus % phys_ncpus, "logical_ncpus %% phys_ncpus != 0\n"); } switch (logical_ncpus / phys_ncpus) { case 1: hyper_shift = 0; break; case 2: hyper_shift = 1; break; case 4: hyper_shift = 2; break; default: MALLOC_REPORT_FATAL_ERROR(logical_ncpus / phys_ncpus, "logical_ncpus / phys_ncpus not 1, 2, or 4"); } // max_magazines may already be set from a boot argument. Make sure that it // is bounded by the number of CPUs. if (max_magazines) { max_magazines = MIN(max_magazines, logical_ncpus); } else { max_magazines = logical_ncpus; } set_flags_from_environment(); // will only set flags up to two times n = malloc_num_zones; #if CONFIG_NANOZONE nano_common_configure(); malloc_zone_t *helper_zone = create_scalable_zone(0, malloc_debug_flags); if (_malloc_engaged_nano == NANO_V2) { zone = nanov2_create_zone(helper_zone, malloc_debug_flags); } else if (_malloc_engaged_nano == NANO_V1) { zone = nano_create_zone(helper_zone, malloc_debug_flags); } if (zone) { malloc_zone_register_while_locked(zone); malloc_zone_register_while_locked(helper_zone); // Must call malloc_set_zone_name() *after* helper and nano are hooked together. malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING); malloc_set_zone_name(helper_zone, MALLOC_HELPER_ZONE_STRING); } else { zone = helper_zone; malloc_zone_register_while_locked(zone); malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING); } #else zone = create_scalable_zone(0, malloc_debug_flags); malloc_zone_register_while_locked(zone); malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING); #endif initial_default_zone = zone; if (n != 0) { // make the default first, for efficiency unsigned protect_size = malloc_num_zones_allocated * sizeof(malloc_zone_t *); malloc_zone_t *hold = malloc_zones[0]; if (hold->zone_name && strcmp(hold->zone_name, DEFAULT_MALLOC_ZONE_STRING) == 0) { malloc_set_zone_name(hold, NULL); } mprotect(malloc_zones, protect_size, PROT_READ | PROT_WRITE); malloc_zones[0] = malloc_zones[n]; malloc_zones[n] = hold; mprotect(malloc_zones, protect_size, PROT_READ); } // Only setup stack logging hooks once lazy initialization is complete, the // malloc_zone calls above would otherwise initialize malloc stack logging, // which calls into malloc re-entrantly from Libc upcalls and so deadlocks // in the lazy initialization os_once(). rdar://13046853 if (stack_logging_enable_logging) { switch (stack_logging_mode) { case stack_logging_mode_malloc: malloc_logger = __disk_stack_logging_log_stack; break; case stack_logging_mode_vm: __syscall_logger = __disk_stack_logging_log_stack; break; case stack_logging_mode_all: malloc_logger = __disk_stack_logging_log_stack; __syscall_logger = __disk_stack_logging_log_stack; break; case stack_logging_mode_lite: __syscall_logger = __disk_stack_logging_log_stack; create_and_insert_lite_zone_while_locked(); enable_stack_logging_lite(); break; case stack_logging_mode_vmlite: __syscall_logger = __disk_stack_logging_log_stack; break; } } // malloc_report(ASL_LEVEL_INFO, "%d registered zones\n", malloc_num_zones); // malloc_report(ASL_LEVEL_INFO, "malloc_zones is at %p; malloc_num_zones is at %p\n", (unsigned)&malloc_zones, // (unsigned)&malloc_num_zones); MALLOC_UNLOCK(); } MALLOC_ALWAYS_INLINE static inline void _malloc_initialize_once(void) { os_once(&_malloc_initialize_pred, NULL, _malloc_initialize); } static inline malloc_zone_t * inline_malloc_default_zone(void) { _malloc_initialize_once(); // malloc_report(ASL_LEVEL_INFO, "In inline_malloc_default_zone with %d %d\n", malloc_num_zones, malloc_has_debug_zone); return malloc_zones[0]; } malloc_zone_t * malloc_default_zone(void) { return default_zone; } static inline malloc_zone_t *inline_malloc_default_scalable_zone(void) __attribute__((always_inline)); static inline malloc_zone_t * inline_malloc_default_scalable_zone(void) { unsigned index; _malloc_initialize_once(); // malloc_report(ASL_LEVEL_INFO, "In inline_malloc_default_scalable_zone with %d %d\n", malloc_num_zones, // malloc_has_debug_zone); MALLOC_LOCK(); #if CONFIG_NANOZONE for (index = 0; index < malloc_num_zones; ++index) { malloc_zone_t *z = malloc_zones[index]; if (z->zone_name && strcmp(z->zone_name, MALLOC_HELPER_ZONE_STRING) == 0) { MALLOC_UNLOCK(); return z; } } #endif for (index = 0; index < malloc_num_zones; ++index) { malloc_zone_t *z = malloc_zones[index]; if (z->zone_name && strcmp(z->zone_name, DEFAULT_MALLOC_ZONE_STRING) == 0) { MALLOC_UNLOCK(); return z; } } MALLOC_UNLOCK(); malloc_report(ASL_LEVEL_ERR, "*** malloc_default_scalable_zone() failed to find 'DefaultMallocZone'\n"); return NULL; // FIXME: abort() instead? } static void * legacy_zeroing_large_malloc(malloc_zone_t *zone, size_t size) { if (size > LARGE_THRESHOLD) { // Leopard and earlier returned a ZFOD range, so ... return default_zone_calloc(zone, 1, size); // Clear to zero always, ham-handedly touching in each page } else { return default_zone_malloc(zone, size); } } static void * legacy_zeroing_large_valloc(malloc_zone_t *zone, size_t size) { void *p = default_zone_valloc(zone, size); // Leopard and earlier returned a ZFOD range, so ... memset(p, 0, size); // Clear to zero always, ham-handedly touching in each page return p; } void zeroify_scalable_zone(malloc_zone_t *zone) { // this checkfix should replace the default zone's // allocation routines with the zeroing versions. Instead of getting in hot // water with the wrong zone, ensure that we're mutating the zone we expect. // // Additionally, the default_zone is no longer PROT_READ, so the two mprotect // calls that were here are no longer needed. if (zone == default_zone) { zone->malloc = (void *)legacy_zeroing_large_malloc; zone->valloc = (void *)legacy_zeroing_large_valloc; } } /* * Returns the version of the Nano allocator that's in use, or 0 if not. */ int malloc_engaged_nano(void) { #if CONFIG_NANOZONE return _malloc_engaged_nano; #else return 0; #endif } malloc_zone_t * malloc_default_purgeable_zone(void) { static malloc_zone_t *dpz; if (!dpz) { // // PR_7288598: Must pass a *scalable* zone (szone) as the helper for create_purgeable_zone(). // Take care that the zone so obtained is not subject to interposing. // malloc_zone_t *tmp = create_purgeable_zone(0, inline_malloc_default_scalable_zone(), malloc_debug_flags); malloc_zone_register(tmp); malloc_set_zone_name(tmp, DEFAULT_PUREGEABLE_ZONE_STRING); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, tmp, (void**)&dpz)) { malloc_destroy_zone(tmp); } } return dpz; } static void set_flags_from_environment(void) { const char *flag; char **env = *_NSGetEnviron(); char **p; char *c; #if __LP64__ malloc_debug_flags = MALLOC_ABORT_ON_CORRUPTION; // Set always on 64-bit processes #else int libSystemVersion = NSVersionOfLinkTimeLibrary("System"); if ((-1 != libSystemVersion) && ((libSystemVersion >> 16) < 126) /* Lion or greater */) { malloc_debug_flags = 0; } else { malloc_debug_flags = MALLOC_ABORT_ON_CORRUPTION; } #endif stack_logging_enable_logging = 0; stack_logging_dontcompact = 0; malloc_logger = NULL; malloc_check_start = 0; malloc_check_each = 1000; malloc_check_abort = 0; malloc_check_sleep = 100; /* * Given that all environment variables start with "Malloc" we optimize by scanning quickly * first the environment, therefore avoiding repeated calls to getenv(). * If we are setu/gid these flags are ignored to prevent a malicious invoker from changing * our behaviour. */ for (p = env; (c = *p) != NULL; ++p) { if (!strncmp(c, "Malloc", 6)) { if (issetugid()) { return; } break; } } /* * Deny certain flags for entitled processes rdar://problem/13521742 * MallocLogFile & MallocCorruptionAbort * as these provide the ability to turn *off* aborting in error cases. */ bool restricted = dyld_process_is_restricted(); malloc_print_configure(restricted); if (c == NULL) { return; } if (getenv("MallocGuardEdges")) { malloc_debug_flags |= MALLOC_ADD_GUARD_PAGES; malloc_report(ASL_LEVEL_INFO, "protecting edges\n"); if (getenv("MallocDoNotProtectPrelude")) { malloc_debug_flags |= MALLOC_DONT_PROTECT_PRELUDE; malloc_report(ASL_LEVEL_INFO, "... but not protecting prelude guard page\n"); } if (getenv("MallocDoNotProtectPostlude")) { malloc_debug_flags |= MALLOC_DONT_PROTECT_POSTLUDE; malloc_report(ASL_LEVEL_INFO, "... but not protecting postlude guard page\n"); } } flag = getenv("MallocStackLogging"); if (!flag) { flag = getenv("MallocStackLoggingNoCompact"); stack_logging_dontcompact = 1; } if (flag) { // Set up stack logging as early as possible to catch all ensuing VM allocations, // including those from malloc_report and malloc zone setup. Make sure to set // __syscall_logger after this, because prepare_to_log_stacks() itself makes VM // allocations that we aren't prepared to log yet. boolean_t lite_or_vmlite_mode = strcmp(flag, "lite") == 0 || strcmp(flag, "vmlite") == 0; __prepare_to_log_stacks(lite_or_vmlite_mode); if (strcmp(flag, "lite") == 0) { stack_logging_mode = stack_logging_mode_lite; malloc_report(ASL_LEVEL_INFO, "recording malloc and VM allocation stacks using lite mode\n"); } else if (strcmp(flag,"malloc") == 0) { stack_logging_mode = stack_logging_mode_malloc; malloc_report(ASL_LEVEL_INFO, "recording malloc (but not VM allocation) stacks to disk using standard recorder\n"); } else if (strcmp(flag, "vm") == 0) { stack_logging_mode = stack_logging_mode_vm; malloc_report(ASL_LEVEL_INFO, "recording VM allocation (but not malloc) stacks to disk using standard recorder\n"); } else if (strcmp(flag, "vmlite") == 0) { stack_logging_mode = stack_logging_mode_vmlite; malloc_report(ASL_LEVEL_NOTICE, "recording VM allocation (but not malloc) stacks using lite mode\n"); } else { stack_logging_mode = stack_logging_mode_all; malloc_report(ASL_LEVEL_INFO, "recording malloc and VM allocation stacks to disk using standard recorder\n"); } stack_logging_enable_logging = 1; if (stack_logging_dontcompact) { if (stack_logging_mode == stack_logging_mode_all || stack_logging_mode == stack_logging_mode_malloc) { malloc_report( ASL_LEVEL_INFO, "stack logging compaction turned off; size of log files on disk can increase rapidly\n"); } else { malloc_report(ASL_LEVEL_INFO, "stack logging compaction turned off; VM can increase rapidly\n"); } } } if (getenv("MallocScribble")) { malloc_debug_flags |= MALLOC_DO_SCRIBBLE; malloc_report(ASL_LEVEL_INFO, "enabling scribbling to detect mods to free blocks\n"); } if (getenv("MallocErrorAbort")) { malloc_debug_flags |= MALLOC_ABORT_ON_ERROR; malloc_report(ASL_LEVEL_INFO, "enabling abort() on bad malloc or free\n"); } if (getenv("MallocTracing")) { malloc_tracing_enabled = true; } #if __LP64__ /* initialization above forces MALLOC_ABORT_ON_CORRUPTION of 64-bit processes */ #else flag = getenv("MallocCorruptionAbort"); if (!restricted && flag && (flag[0] == '0')) { // Set from an environment variable in 32-bit processes malloc_debug_flags &= ~MALLOC_ABORT_ON_CORRUPTION; } else if (flag) { malloc_debug_flags |= MALLOC_ABORT_ON_CORRUPTION; } #endif flag = getenv("MallocCheckHeapStart"); if (flag) { malloc_check_start = (unsigned)strtoul(flag, NULL, 0); if (malloc_check_start == 0) { malloc_check_start = 1; } if (malloc_check_start == -1) { malloc_check_start = 1; } flag = getenv("MallocCheckHeapEach"); if (flag) { malloc_check_each = (unsigned)strtoul(flag, NULL, 0); if (malloc_check_each == 0) { malloc_check_each = 1; } if (malloc_check_each == -1) { malloc_check_each = 1; } } malloc_report(ASL_LEVEL_INFO, "checks heap after operation #%d and each %d operations\n", malloc_check_start, malloc_check_each); flag = getenv("MallocCheckHeapAbort"); if (flag) { malloc_check_abort = (unsigned)strtol(flag, NULL, 0); } if (malloc_check_abort) { malloc_report(ASL_LEVEL_INFO, "will abort on heap corruption\n"); } else { flag = getenv("MallocCheckHeapSleep"); if (flag) { malloc_check_sleep = (unsigned)strtol(flag, NULL, 0); } if (malloc_check_sleep > 0) { malloc_report(ASL_LEVEL_INFO, "will sleep for %d seconds on heap corruption\n", malloc_check_sleep); } else if (malloc_check_sleep < 0) { malloc_report(ASL_LEVEL_INFO, "will sleep once for %d seconds on heap corruption\n", -malloc_check_sleep); } else { malloc_report(ASL_LEVEL_INFO, "no sleep on heap corruption\n"); } } } flag = getenv("MallocMaxMagazines"); if (flag) { int value = (unsigned)strtol(flag, NULL, 0); if (value == 0) { malloc_report(ASL_LEVEL_INFO, "Maximum magazines defaulted to %d\n", max_magazines); } else if (value < 0) { malloc_report(ASL_LEVEL_ERR, "MallocMaxMagazines must be positive - ignored.\n"); } else if (value > logical_ncpus) { max_magazines = logical_ncpus; malloc_report(ASL_LEVEL_INFO, "Maximum magazines limited to number of logical CPUs (%d)\n", max_magazines); } else { max_magazines = value; malloc_report(ASL_LEVEL_INFO, "Maximum magazines set to %d\n", max_magazines); } } #if CONFIG_RECIRC_DEPOT flag = getenv("MallocRecircRetainedRegions"); if (flag) { int value = (int)strtol(flag, NULL, 0); if (value > 0) { recirc_retained_regions = value; } else { malloc_report(ASL_LEVEL_ERR, "MallocRecircRetainedRegions must be positive - ignored.\n"); } } #endif // CONFIG_RECIRC_DEPOT if (getenv("MallocHelp")) { malloc_report(ASL_LEVEL_INFO, "environment variables that can be set for debug:\n" "- MallocLogFile to create/append messages to file instead of stderr\n" "- MallocGuardEdges to add 2 guard pages for each large block\n" "- MallocDoNotProtectPrelude to disable protection (when previous flag set)\n" "- MallocDoNotProtectPostlude to disable protection (when previous flag set)\n" "- MallocStackLogging to record all stacks. Tools like leaks can then be applied\n" "- MallocStackLoggingNoCompact to record all stacks. Needed for malloc_history\n" "- MallocStackLoggingDirectory to set location of stack logs, which can grow large; default is /tmp\n" "- MallocScribble to detect writing on free blocks and missing initializers:\n" " 0x55 is written upon free and 0xaa is written on allocation\n" "- MallocCheckHeapStart to start checking the heap after operations\n" "- MallocCheckHeapEach to repeat the checking of the heap after operations\n" "- MallocCheckHeapSleep to sleep seconds on heap corruption\n" "- MallocCheckHeapAbort to abort on heap corruption if is non-zero\n" "- MallocCorruptionAbort to abort on malloc errors, but not on out of memory for 32-bit processes\n" " MallocCorruptionAbort is always set on 64-bit processes\n" "- MallocErrorAbort to abort on any malloc error, including out of memory\n"\ "- MallocTracing to emit kdebug trace points on malloc entry points\n"\ "- MallocHelp - this help!\n"); } } malloc_zone_t * malloc_create_zone(vm_size_t start_size, unsigned flags) { malloc_zone_t *zone; /* start_size doesn't actually appear to be used, but we test anyway. */ if (start_size > MALLOC_ABSOLUTE_MAX_SIZE) { return NULL; } _malloc_initialize_once(); zone = create_scalable_zone(start_size, flags | malloc_debug_flags); malloc_zone_register(zone); return zone; } /* * For use by CheckFix: establish a new default zone whose behavior is, apart from * the use of death-row and per-CPU magazines, that of Leopard. */ void malloc_create_legacy_default_zone(void) { malloc_zone_t *zone; int i; _malloc_initialize_once(); zone = create_legacy_scalable_zone(0, malloc_debug_flags); MALLOC_LOCK(); malloc_zone_register_while_locked(zone); // // Establish the legacy scalable zone just created as the default zone. // malloc_zone_t *hold = malloc_zones[0]; if (hold->zone_name && strcmp(hold->zone_name, DEFAULT_MALLOC_ZONE_STRING) == 0) { malloc_set_zone_name(hold, NULL); } malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING); unsigned protect_size = malloc_num_zones_allocated * sizeof(malloc_zone_t *); mprotect(malloc_zones, protect_size, PROT_READ | PROT_WRITE); // assert(zone == malloc_zones[malloc_num_zones - 1]; for (i = malloc_num_zones - 1; i > 0; --i) { malloc_zones[i] = malloc_zones[i - 1]; } malloc_zones[0] = zone; mprotect(malloc_zones, protect_size, PROT_READ); MALLOC_UNLOCK(); } void malloc_destroy_zone(malloc_zone_t *zone) { malloc_set_zone_name(zone, NULL); // Deallocate zone name wherever it may reside PR_7701095 malloc_zone_unregister(zone); zone->destroy(zone); } static vm_address_t *frames = NULL; static unsigned num_frames; MALLOC_NOINLINE void malloc_zone_check_fail(const char *msg, const char *fmt, ...) { _SIMPLE_STRING b = _simple_salloc(); if (b) { _simple_sprintf(b, "*** MallocCheckHeap: FAILED check at operation #%d\n", malloc_check_counter - 1); } else { malloc_report(MALLOC_REPORT_NOLOG, "*** MallocCheckHeap: FAILED check at operation #%d\n", malloc_check_counter - 1); } if (frames) { unsigned index = 1; if (b) { _simple_sappend(b, "Stack for last operation where the malloc check succeeded: "); while (index < num_frames) _simple_sprintf(b, "%p ", (void*)frames[index++]); malloc_report(MALLOC_REPORT_NOLOG, "%s\n(Use 'atos' for a symbolic stack)\n", _simple_string(b)); } else { /* * Should only get here if vm_allocate() can't get a single page of * memory, implying _simple_asl_log() would also fail. So we just * print to the file descriptor. */ malloc_report(MALLOC_REPORT_NOLOG, "Stack for last operation where the malloc check succeeded: "); while (index < num_frames) { malloc_report(MALLOC_REPORT_NOLOG, "%p ", (void *)frames[index++]); } malloc_report(MALLOC_REPORT_NOLOG, "\n(Use 'atos' for a symbolic stack)\n"); } } if (malloc_check_each > 1) { unsigned recomm_each = (malloc_check_each > 10) ? malloc_check_each / 10 : 1; unsigned recomm_start = (malloc_check_counter > malloc_check_each + 1) ? malloc_check_counter - 1 - malloc_check_each : 1; malloc_report(MALLOC_REPORT_NOLOG, "*** Recommend using 'setenv MallocCheckHeapStart %d; setenv MallocCheckHeapEach %d' to narrow down failure\n", recomm_start, recomm_each); } if (b) { _simple_sfree(b); } // Use malloc_vreport() to: // * report the error // * call malloc_error_break() for a breakpoint // * sleep or stop for debug // * set the crash message and crash if malloc_check_abort is set. unsigned sleep_time = 0; uint32_t report_flags = ASL_LEVEL_ERR | MALLOC_REPORT_DEBUG | MALLOC_REPORT_NOLOG; if (malloc_check_abort) { report_flags |= MALLOC_REPORT_CRASH; } else { if (malloc_check_sleep > 0) { malloc_report(ASL_LEVEL_NOTICE, "*** Will sleep for %d seconds to leave time to attach\n", malloc_check_sleep); sleep_time = malloc_check_sleep; } else if (malloc_check_sleep < 0) { malloc_report(ASL_LEVEL_NOTICE, "*** Will sleep once for %d seconds to leave time to attach\n", -malloc_check_sleep); sleep_time = -malloc_check_sleep; malloc_check_sleep = 0; } } va_list ap; va_start(ap, fmt); malloc_vreport(report_flags, sleep_time, msg, NULL, fmt, ap); va_end(ap); } /********* Block creation and manipulation ************/ static void internal_check(void) { if (malloc_zone_check(NULL)) { if (!frames) { vm_allocate(mach_task_self(), (void *)&frames, vm_page_size, 1); } thread_stack_pcs(frames, (unsigned)(vm_page_size / sizeof(vm_address_t) - 1), &num_frames); } malloc_check_start += malloc_check_each; } void * malloc_zone_malloc(malloc_zone_t *zone, size_t size) { MALLOC_TRACE(TRACE_malloc | DBG_FUNC_START, (uintptr_t)zone, size, 0, 0); void *ptr; if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } if (size > MALLOC_ABSOLUTE_MAX_SIZE) { return NULL; } ptr = zone->malloc(zone, size); // if lite zone is passed in then we still call the lite methods if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)size, 0, (uintptr_t)ptr, 0); } MALLOC_TRACE(TRACE_malloc | DBG_FUNC_END, (uintptr_t)zone, size, (uintptr_t)ptr, 0); return ptr; } void * malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size) { MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0); void *ptr; if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } ptr = zone->calloc(zone, num_items, size); if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone, (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0); } MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr); return ptr; } void * malloc_zone_valloc(malloc_zone_t *zone, size_t size) { MALLOC_TRACE(TRACE_valloc | DBG_FUNC_START, (uintptr_t)zone, size, 0, 0); void *ptr; if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } if (size > MALLOC_ABSOLUTE_MAX_SIZE) { return NULL; } ptr = zone->valloc(zone, size); if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)size, 0, (uintptr_t)ptr, 0); } MALLOC_TRACE(TRACE_valloc | DBG_FUNC_END, (uintptr_t)zone, size, (uintptr_t)ptr, 0); return ptr; } void * malloc_zone_realloc(malloc_zone_t *zone, void *ptr, size_t size) { MALLOC_TRACE(TRACE_realloc | DBG_FUNC_START, (uintptr_t)zone, (uintptr_t)ptr, size, 0); void *new_ptr; if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } if (size > MALLOC_ABSOLUTE_MAX_SIZE) { return NULL; } new_ptr = zone->realloc(zone, ptr, size); if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_DEALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)ptr, (uintptr_t)size, (uintptr_t)new_ptr, 0); } MALLOC_TRACE(TRACE_realloc | DBG_FUNC_END, (uintptr_t)zone, (uintptr_t)ptr, size, (uintptr_t)new_ptr); return new_ptr; } void malloc_zone_free(malloc_zone_t *zone, void *ptr) { MALLOC_TRACE(TRACE_free, (uintptr_t)zone, (uintptr_t)ptr, (ptr) ? *(uintptr_t*)ptr : 0, 0); if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_DEALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)ptr, 0, 0, 0); } if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } zone->free(zone, ptr); } static void malloc_zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size) { MALLOC_TRACE(TRACE_free, (uintptr_t)zone, (uintptr_t)ptr, size, (ptr && size) ? *(uintptr_t*)ptr : 0); if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_DEALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)ptr, 0, 0, 0); } if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } zone->free_definite_size(zone, ptr, size); } malloc_zone_t * malloc_zone_from_ptr(const void *ptr) { if (!ptr) { return NULL; } else { return find_registered_zone(ptr, NULL); } } void * malloc_zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size) { MALLOC_TRACE(TRACE_memalign | DBG_FUNC_START, (uintptr_t)zone, alignment, size, 0); void *ptr; if (zone->version < 5) { // Version must be >= 5 to look at the new memalign field. return NULL; } if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } if (size > MALLOC_ABSOLUTE_MAX_SIZE) { return NULL; } if (alignment < sizeof(void *) || // excludes 0 == alignment 0 != (alignment & (alignment - 1))) { // relies on sizeof(void *) being a power of two. return NULL; } if (!(zone->memalign)) { return NULL; } ptr = zone->memalign(zone, alignment, size); if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)size, 0, (uintptr_t)ptr, 0); } MALLOC_TRACE(TRACE_memalign | DBG_FUNC_END, (uintptr_t)zone, alignment, size, (uintptr_t)ptr); return ptr; } boolean_t malloc_zone_claimed_address(malloc_zone_t *zone, void *ptr) { if (!ptr) { // NULL is not a member of any zone. return false; } if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } if (zone->version < 10 || !zone->claimed_address) { // For zones that have not implemented claimed_address, we always have // to return true to avoid a false negative. return true; } return zone->claimed_address(zone, ptr); } /********* Functions for zone implementors ************/ void malloc_zone_register(malloc_zone_t *zone) { MALLOC_LOCK(); malloc_zone_register_while_locked(zone); MALLOC_UNLOCK(); } void malloc_zone_unregister(malloc_zone_t *z) { unsigned index; if (malloc_num_zones == 0) { return; } MALLOC_LOCK(); for (index = 0; index < malloc_num_zones; ++index) { if (z != malloc_zones[index]) { continue; } // Modify the page to be allow write access, so that we can update the // malloc_zones array. size_t protect_size = malloc_num_zones_allocated * sizeof(malloc_zone_t *); mprotect(malloc_zones, protect_size, PROT_READ | PROT_WRITE); // If we found a match, replace it with the entry at the end of the list, shrink the list, // and leave the end of the list intact to avoid racing with find_registered_zone(). malloc_zones[index] = malloc_zones[malloc_num_zones - 1]; --malloc_num_zones; mprotect(malloc_zones, protect_size, PROT_READ); // Exchange the roles of the FRZ counters. The counter that has captured the number of threads presently // executing *inside* find_regiatered_zone is swapped with the counter drained to zero last time through. // The former is then allowed to drain to zero while this thread yields. int32_t volatile *p = pFRZCounterLive; pFRZCounterLive = pFRZCounterDrain; pFRZCounterDrain = p; OSMemoryBarrier(); // Full memory barrier while (0 != *pFRZCounterDrain) { yield(); } MALLOC_UNLOCK(); return; } MALLOC_UNLOCK(); malloc_report(ASL_LEVEL_ERR, "*** malloc_zone_unregister() failed for %p\n", z); } void malloc_set_zone_name(malloc_zone_t *z, const char *name) { char *newName; mprotect(z, sizeof(malloc_zone_t), PROT_READ | PROT_WRITE); if (z->zone_name) { free((char *)z->zone_name); z->zone_name = NULL; } if (name) { size_t buflen = strlen(name) + 1; newName = malloc_zone_malloc(z, buflen); if (newName) { strlcpy(newName, name, buflen); z->zone_name = (const char *)newName; } else { z->zone_name = NULL; } } mprotect(z, sizeof(malloc_zone_t), PROT_READ); } const char * malloc_get_zone_name(malloc_zone_t *zone) { return zone->zone_name; } /********* Generic ANSI callouts ************/ void * malloc(size_t size) { void *retval; retval = malloc_zone_malloc(default_zone, size); if (retval == NULL) { errno = ENOMEM; } return retval; } void * calloc(size_t num_items, size_t size) { void *retval; retval = malloc_zone_calloc(default_zone, num_items, size); if (retval == NULL) { errno = ENOMEM; } return retval; } void free(void *ptr) { malloc_zone_t *zone; size_t size; if (!ptr) { return; } zone = find_registered_zone(ptr, &size); if (!zone) { int flags = MALLOC_REPORT_DEBUG | MALLOC_REPORT_NOLOG; if ((malloc_debug_flags & (MALLOC_ABORT_ON_CORRUPTION | MALLOC_ABORT_ON_ERROR))) { flags = MALLOC_REPORT_CRASH | MALLOC_REPORT_NOLOG; } malloc_report(flags, "*** error for object %p: pointer being freed was not allocated\n", ptr); } else if (zone->version >= 6 && zone->free_definite_size) { malloc_zone_free_definite_size(zone, ptr, size); } else { malloc_zone_free(zone, ptr); } } void * realloc(void *in_ptr, size_t new_size) { void *retval = NULL; void *old_ptr; malloc_zone_t *zone; // SUSv3: "If size is 0 and ptr is not a null pointer, the object // pointed to is freed. If the space cannot be allocated, the object // shall remain unchanged." Also "If size is 0, either a null pointer // or a unique pointer that can be successfully passed to free() shall // be returned." We choose to allocate a minimum size object by calling // malloc_zone_malloc with zero size, which matches "If ptr is a null // pointer, realloc() shall be equivalent to malloc() for the specified // size." So we only free the original memory if the allocation succeeds. old_ptr = (new_size == 0) ? NULL : in_ptr; if (!old_ptr) { retval = malloc_zone_malloc(default_zone, new_size); } else { zone = find_registered_zone(old_ptr, NULL); if (!zone) { int flags = MALLOC_REPORT_DEBUG | MALLOC_REPORT_NOLOG; if (malloc_debug_flags & (MALLOC_ABORT_ON_CORRUPTION | MALLOC_ABORT_ON_ERROR)) { flags = MALLOC_REPORT_CRASH | MALLOC_REPORT_NOLOG; } malloc_report(flags, "*** error for object %p: pointer being realloc'd was not allocated\n", in_ptr); } else { retval = malloc_zone_realloc(zone, old_ptr, new_size); } } if (retval == NULL) { errno = ENOMEM; } else if (new_size == 0) { free(in_ptr); } return retval; } void * valloc(size_t size) { void *retval; malloc_zone_t *zone = default_zone; retval = malloc_zone_valloc(zone, size); if (retval == NULL) { errno = ENOMEM; } return retval; } extern void vfree(void *ptr) { free(ptr); } size_t malloc_size(const void *ptr) { size_t size = 0; if (!ptr) { return size; } (void)find_registered_zone(ptr, &size); return size; } size_t malloc_good_size(size_t size) { malloc_zone_t *zone = default_zone; return zone->introspect->good_size(zone, size); } /* * The posix_memalign() function shall allocate size bytes aligned on a boundary specified by alignment, * and shall return a pointer to the allocated memory in memptr. * The value of alignment shall be a multiple of sizeof( void *), that is also a power of two. * Upon successful completion, the value pointed to by memptr shall be a multiple of alignment. * * Upon successful completion, posix_memalign() shall return zero; otherwise, * an error number shall be returned to indicate the error. * * The posix_memalign() function shall fail if: * EINVAL * The value of the alignment parameter is not a power of two multiple of sizeof( void *). * ENOMEM * There is insufficient memory available with the requested alignment. */ int posix_memalign(void **memptr, size_t alignment, size_t size) { void *retval; /* POSIX is silent on NULL == memptr !?! */ retval = malloc_zone_memalign(default_zone, alignment, size); if (retval == NULL) { // To avoid testing the alignment constraints redundantly, we'll rely on the // test made in malloc_zone_memalign to vet each request. Only if that test fails // and returns NULL, do we arrive here to detect the bogus alignment and give the // required EINVAL return. if (alignment < sizeof(void *) || // excludes 0 == alignment 0 != (alignment & (alignment - 1))) { // relies on sizeof(void *) being a power of two. return EINVAL; } return ENOMEM; } else { *memptr = retval; // Set iff allocation succeeded return 0; } } boolean_t malloc_claimed_address(void *ptr) { // We need to check with each registered zone whether it claims "ptr". // Use logic similar to that in find_registered_zone(). if (malloc_num_zones == 0) { return false; } // Start with the lite zone, if it's in use. if (lite_zone && malloc_zone_claimed_address(lite_zone, ptr)) { return true; } // Next, try the default zone, which is always present. if (malloc_zone_claimed_address(malloc_zones[0], ptr)) { return true; } // Try all the other zones. Increment the FRZ barrier so that we can // walk the zones array without a lock (see find_registered_zone() for // the details). int32_t volatile *pFRZCounter = pFRZCounterLive; OSAtomicIncrement32Barrier(pFRZCounter); int32_t limit = *(int32_t volatile *)&malloc_num_zones; malloc_zone_t **zones = &malloc_zones[1]; boolean_t result = false; for (unsigned index = 1; index < limit; ++index, ++zones) { malloc_zone_t *zone = *zones; if (malloc_zone_claimed_address(zone, ptr)) { result = true; break; } } OSAtomicDecrement32Barrier(pFRZCounter); return result; } void * reallocarray(void * in_ptr, size_t nmemb, size_t size){ size_t alloc_size; if (os_mul_overflow(nmemb, size, &alloc_size)){ errno = ENOMEM; return NULL; } return realloc(in_ptr, alloc_size); } void * reallocarrayf(void * in_ptr, size_t nmemb, size_t size){ size_t alloc_size; if (os_mul_overflow(nmemb, size, &alloc_size)){ errno = ENOMEM; return NULL; } return reallocf(in_ptr, alloc_size); } static malloc_zone_t * find_registered_purgeable_zone(void *ptr) { if (!ptr) { return NULL; } /* * Look for a zone which contains ptr. If that zone does not have the purgeable malloc flag * set, or the allocation is too small, do nothing. Otherwise, set the allocation volatile. * FIXME: for performance reasons, we should probably keep a separate list of purgeable zones * and only search those. */ size_t size = 0; malloc_zone_t *zone = find_registered_zone(ptr, &size); /* FIXME: would really like a zone->introspect->flags->purgeable check, but haven't determined * binary compatibility impact of changing the introspect struct yet. */ if (!zone) { return NULL; } /* Check to make sure pointer is page aligned and size is multiple of page size */ if ((size < vm_page_size) || ((size % vm_page_size) != 0)) { return NULL; } return zone; } void malloc_make_purgeable(void *ptr) { malloc_zone_t *zone = find_registered_purgeable_zone(ptr); if (!zone) { return; } int state = VM_PURGABLE_VOLATILE; vm_purgable_control(mach_task_self(), (vm_address_t)ptr, VM_PURGABLE_SET_STATE, &state); return; } /* Returns true if ptr is valid. Ignore the return value from vm_purgeable_control and only report * state. */ int malloc_make_nonpurgeable(void *ptr) { malloc_zone_t *zone = find_registered_purgeable_zone(ptr); if (!zone) { return 0; } int state = VM_PURGABLE_NONVOLATILE; vm_purgable_control(mach_task_self(), (vm_address_t)ptr, VM_PURGABLE_SET_STATE, &state); if (state == VM_PURGABLE_EMPTY) { return EFAULT; } return 0; } void malloc_enter_process_memory_limit_warn_mode(void) { // } #if ENABLE_MEMORY_RESOURCE_EXCEPTION_HANDLING // Is the system elible to turn on/off MSL lite in response to memory resource exceptions // // Return true if // - The user has not explicitly opted out // and // - Either the user has explicitly opted in or this is an Apple Internal enabled build static boolean_t check_is_eligible_for_lite_mode_mre_handling(void) { struct stat stat_buf; // User opted out if (stat("/var/db/disableLiteModeMemoryResourceExceptionHandling", &stat_buf) == 0) { return false; } // User opted in if (stat("/var/db/enableLiteModeMemoryResourceExceptionHandling", &stat_buf) == 0) { return true; } // Not enabled for everything else return false; } // Not thread-safe, but it's called from malloc_memory_event_handler which already assumes // single thread execution. static boolean_t is_eligible_for_lite_mode_mre_handling(void) { static boolean_t is_eligible = false; static boolean_t needs_check = true; if (needs_check) { is_eligible = check_is_eligible_for_lite_mode_mre_handling(); needs_check = false; } return is_eligible; } #endif static void handle_msl_memory_event(unsigned long event) { // don't mix and match enabling mechanisms if (warn_mode_entered) { return; } event &= NOTE_MEMORYSTATUS_MSL_STATUS; // sanity check if (event == 0) { return; } // first check if the disable bit is set if (event & MEMORYSTATUS_DISABLE_MSL) { turn_off_stack_logging(); return; } boolean_t msl_malloc = (event & MEMORYSTATUS_ENABLE_MSL_MALLOC); boolean_t msl_vm = (event & MEMORYSTATUS_ENABLE_MSL_VM); boolean_t msl_lite = (event & MEMORYSTATUS_ENABLE_MSL_LITE); // The following ensures it's not possible to enable two different modes // For instance this would not be allowed: // Enable lite // Disable // Enable full if (msl_lite) { if (msl_vm && msl_malloc) { if (msl_type_enabled_at_runtime == stack_logging_mode_none || msl_type_enabled_at_runtime == stack_logging_mode_lite) { msl_type_enabled_at_runtime = stack_logging_mode_lite; turn_on_stack_logging(stack_logging_mode_lite); } } else if (msl_vm) { if (msl_type_enabled_at_runtime == stack_logging_mode_none || msl_type_enabled_at_runtime == stack_logging_mode_vmlite) { msl_type_enabled_at_runtime = stack_logging_mode_vmlite; turn_on_stack_logging(stack_logging_mode_vmlite); } } return; } else if (msl_malloc && msl_vm) { if (msl_type_enabled_at_runtime == stack_logging_mode_none || msl_type_enabled_at_runtime == stack_logging_mode_all) { msl_type_enabled_at_runtime = stack_logging_mode_all; turn_on_stack_logging(stack_logging_mode_all); } return; } else if (msl_malloc) { if (msl_type_enabled_at_runtime == stack_logging_mode_none || msl_type_enabled_at_runtime == stack_logging_mode_malloc) { msl_type_enabled_at_runtime = stack_logging_mode_malloc; turn_on_stack_logging(stack_logging_mode_malloc); } return; } else if (msl_vm) { if (msl_type_enabled_at_runtime == stack_logging_mode_none || msl_type_enabled_at_runtime == stack_logging_mode_vm) { msl_type_enabled_at_runtime = stack_logging_mode_vm; turn_on_stack_logging(stack_logging_mode_vm); } return; } } // Note that malloc_memory_event_handler is not thread-safe, and we are relying on the callers of this for synchronization void malloc_memory_event_handler(unsigned long event) { if (event & NOTE_MEMORYSTATUS_PRESSURE_WARN) { malloc_zone_pressure_relief(0, 0); } // First check for enable/disable MSL - only recognize if all other bits are 0 // Don't attempt this if we've either entered or exited MRE mode if ((event & NOTE_MEMORYSTATUS_MSL_STATUS) != 0 && (event & ~NOTE_MEMORYSTATUS_MSL_STATUS) == 0 && !warn_mode_entered && !warn_mode_disable_retries) { handle_msl_memory_event(event); return; } #if ENABLE_MEMORY_RESOURCE_EXCEPTION_HANDLING // If we have reached EXC_RESOURCE, we no longer need stack log data. // If we are under system-wide memory pressure, we should jettison stack log data. if ((event & (NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL | NOTE_MEMORYSTATUS_PRESSURE_CRITICAL)) && !warn_mode_disable_retries) { // If we have crossed the EXC_RESOURCE limit once already, there is no point in // collecting stack logs in the future, even if we missed a previous chance to // collect data because nobody is going to ask us for it again. warn_mode_disable_retries = true; // Only try to clean up stack log data if it was enabled through a proc limit warning. // User initiated stack logging should proceed unimpeded. if (warn_mode_entered) { malloc_report(ASL_LEVEL_INFO, "malloc_memory_event_handler: stopping stack-logging\n"); turn_off_stack_logging(); __malloc_lock_stack_logging(); __delete_uniquing_table_memory_while_locked(); __malloc_unlock_stack_logging(); warn_mode_entered = false; } } // Enable stack logging if we are approaching the process limit, provided // we aren't under system wide memory pressure and we're allowed to try again. if ((event & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN) && !(event & NOTE_MEMORYSTATUS_PRESSURE_CRITICAL) && !warn_mode_entered && !warn_mode_disable_retries && is_eligible_for_lite_mode_mre_handling()) { malloc_report(ASL_LEVEL_INFO, "malloc_memory_event_handler: approaching memory limit. Starting stack-logging.\n"); if (turn_on_stack_logging(stack_logging_mode_lite)) { warn_mode_entered = true; // set the maximum allocation threshold max_lite_mallocs = MAX_LITE_MALLOCS; } } #endif } size_t malloc_zone_pressure_relief(malloc_zone_t *zone, size_t goal) { if (!zone) { unsigned index = 0; size_t total = 0; // Take lock to defend against malloc_destroy_zone() MALLOC_LOCK(); while (index < malloc_num_zones) { zone = malloc_zones[index++]; if (zone->version < 8) { continue; } if (NULL == zone->pressure_relief) { continue; } if (0 == goal) { /* Greedy */ total += zone->pressure_relief(zone, 0); } else if (goal > total) { total += zone->pressure_relief(zone, goal - total); } else { /* total >= goal */ break; } } MALLOC_UNLOCK(); return total; } else { // Assumes zone is not destroyed for the duration of this call if (zone->version < 8) { return 0; } if (NULL == zone->pressure_relief) { return 0; } return zone->pressure_relief(zone, goal); } } /********* Batch methods ************/ unsigned malloc_zone_batch_malloc(malloc_zone_t *zone, size_t size, void **results, unsigned num_requested) { if (!zone->batch_malloc) { return 0; } if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } unsigned batched = zone->batch_malloc(zone, size, results, num_requested); if (malloc_logger) { unsigned index = 0; while (index < batched) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)size, 0, (uintptr_t)results[index], 0); index++; } } return batched; } void malloc_zone_batch_free(malloc_zone_t *zone, void **to_be_freed, unsigned num) { if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } if (malloc_logger) { unsigned index = 0; while (index < num) { malloc_logger( MALLOC_LOG_TYPE_DEALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)to_be_freed[index], 0, 0, 0); index++; } } if (zone->batch_free) { zone->batch_free(zone, to_be_freed, num); } else { void (*free_fun)(malloc_zone_t *, void *) = zone->free; while (num--) { void *ptr = *to_be_freed++; free_fun(zone, ptr); } } } /********* Functions for performance tools ************/ static kern_return_t _malloc_default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr) { *ptr = (void *)address; return 0; } kern_return_t malloc_get_all_zones(task_t task, memory_reader_t reader, vm_address_t **addresses, unsigned *count) { // Note that the 2 following addresses are not correct if the address of the target is different from your own. This notably // occurs if the address of System.framework is slid (e.g. different than at B & I ) vm_address_t remote_malloc_zones = (vm_address_t)&malloc_zones; vm_address_t remote_malloc_num_zones = (vm_address_t)&malloc_num_zones; kern_return_t err; vm_address_t zones_address; vm_address_t *zones_address_ref; unsigned num_zones; unsigned *num_zones_ref; if (!reader) { reader = _malloc_default_reader; } // printf("Read malloc_zones at address %p should be %p\n", &malloc_zones, malloc_zones); err = reader(task, remote_malloc_zones, sizeof(void *), (void **)&zones_address_ref); // printf("Read malloc_zones[%p]=%p\n", remote_malloc_zones, *zones_address_ref); if (err) { malloc_report(ASL_LEVEL_ERR, "*** malloc_get_all_zones: error reading zones_address at %p\n", (void *)remote_malloc_zones); return err; } zones_address = *zones_address_ref; // printf("Reading num_zones at address %p\n", remote_malloc_num_zones); err = reader(task, remote_malloc_num_zones, sizeof(unsigned), (void **)&num_zones_ref); if (err) { malloc_report(ASL_LEVEL_ERR, "*** malloc_get_all_zones: error reading num_zones at %p\n", (void *)remote_malloc_num_zones); return err; } num_zones = *num_zones_ref; // printf("Read malloc_num_zones[%p]=%d\n", remote_malloc_num_zones, num_zones); *count = num_zones; // printf("malloc_get_all_zones succesfully found %d zones\n", num_zones); err = reader(task, zones_address, sizeof(malloc_zone_t *) * num_zones, (void **)addresses); if (err) { malloc_report(ASL_LEVEL_ERR, "*** malloc_get_all_zones: error reading zones at %p\n", &zones_address); return err; } // printf("malloc_get_all_zones succesfully read %d zones\n", num_zones); return err; } /********* Debug helpers ************/ void malloc_zone_print_ptr_info(void *ptr) { malloc_zone_t *zone; if (!ptr) { return; } zone = malloc_zone_from_ptr(ptr); if (zone) { printf("ptr %p in registered zone %p\n", ptr, zone); } else { printf("ptr %p not in heap\n", ptr); } } boolean_t malloc_zone_check(malloc_zone_t *zone) { boolean_t ok = 1; if (!zone) { unsigned index = 0; while (index < malloc_num_zones) { zone = malloc_zones[index++]; if (!zone->introspect->check(zone)) { ok = 0; } } } else { ok = zone->introspect->check(zone); } return ok; } void malloc_zone_print(malloc_zone_t *zone, boolean_t verbose) { if (!zone) { unsigned index = 0; while (index < malloc_num_zones) { zone = malloc_zones[index++]; zone->introspect->print(zone, verbose); } } else { zone->introspect->print(zone, verbose); } } void malloc_zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { if (!zone) { memset(stats, 0, sizeof(*stats)); unsigned index = 0; while (index < malloc_num_zones) { zone = malloc_zones[index++]; malloc_statistics_t this_stats; zone->introspect->statistics(zone, &this_stats); stats->blocks_in_use += this_stats.blocks_in_use; stats->size_in_use += this_stats.size_in_use; stats->max_size_in_use += this_stats.max_size_in_use; stats->size_allocated += this_stats.size_allocated; } } else { zone->introspect->statistics(zone, stats); } } void malloc_zone_log(malloc_zone_t *zone, void *address) { if (!zone) { unsigned index = 0; while (index < malloc_num_zones) { zone = malloc_zones[index++]; zone->introspect->log(zone, address); } } else { zone->introspect->log(zone, address); } } /********* Misc other entry points ************/ void mag_set_thread_index(unsigned int index) { _os_cpu_number_override = index; #if CONFIG_NANOZONE nano_common_cpu_number_override_set(); #endif // CONFIG_NANOZONE } static void DefaultMallocError(int x) { #if USE_SLEEP_RATHER_THAN_ABORT malloc_report(ASL_LEVEL_ERR, "*** error %d\n", x); sleep(3600); #else _SIMPLE_STRING b = _simple_salloc(); if (b) { _simple_sprintf(b, "*** error %d", x); malloc_report(MALLOC_REPORT_NOLOG, "%s\n", _simple_string(b)); _os_set_crash_log_message_dynamic(_simple_string(b)); } else { malloc_report(MALLOC_REPORT_NOLOG, "*** error %d\n", x); _os_set_crash_log_message("*** DefaultMallocError called"); } abort(); #endif } void (*malloc_error(void (*func)(int)))(int) { return DefaultMallocError; } static void _malloc_lock_all(void (*callout)(void)) { unsigned index = 0; MALLOC_LOCK(); while (index < malloc_num_zones) { malloc_zone_t *zone = malloc_zones[index++]; zone->introspect->force_lock(zone); } callout(); } static void _malloc_unlock_all(void (*callout)(void)) { unsigned index = 0; callout(); while (index < malloc_num_zones) { malloc_zone_t *zone = malloc_zones[index++]; zone->introspect->force_unlock(zone); } MALLOC_UNLOCK(); } static void _malloc_reinit_lock_all(void (*callout)(void)) { unsigned index = 0; callout(); while (index < malloc_num_zones) { malloc_zone_t *zone = malloc_zones[index++]; if (zone->version < 9) { // Version must be >= 9 to look at reinit_lock zone->introspect->force_unlock(zone); } else { zone->introspect->reinit_lock(zone); } } MALLOC_REINIT_LOCK(); } // Called prior to fork() to guarantee that malloc is not in any critical // sections during the fork(); prevent any locks from being held by non- // surviving threads after the fork. void _malloc_fork_prepare(void) { return _malloc_lock_all(&__stack_logging_fork_prepare); } // Called in the parent process after fork() to resume normal operation. void _malloc_fork_parent(void) { return _malloc_unlock_all(&__stack_logging_fork_parent); } // Called in the child process after fork() to resume normal operation. void _malloc_fork_child(void) { #if CONFIG_NANOZONE if (_malloc_initialize_pred) { if (_malloc_engaged_nano == NANO_V2) { nanov2_forked_zone((nanozonev2_t *)inline_malloc_default_zone()); } else if (_malloc_engaged_nano == NANO_V1) { nano_forked_zone((nanozone_t *)inline_malloc_default_zone()); } } #endif return _malloc_reinit_lock_all(&__stack_logging_fork_child); } /* * A Glibc-like mstats() interface. * * Note that this interface really isn't very good, as it doesn't understand * that we may have multiple allocators running at once. We just massage * the result from malloc_zone_statistics in any case. */ struct mstats mstats(void) { malloc_statistics_t s; struct mstats m; malloc_zone_statistics(NULL, &s); m.bytes_total = s.size_allocated; m.chunks_used = s.blocks_in_use; m.bytes_used = s.size_in_use; m.chunks_free = 0; m.bytes_free = m.bytes_total - m.bytes_used; /* isn't this somewhat obvious? */ return (m); } boolean_t malloc_zone_enable_discharge_checking(malloc_zone_t *zone) { if (zone->version < 7) { // Version must be >= 7 to look at the new discharge checking fields. return FALSE; } if (NULL == zone->introspect->enable_discharge_checking) { return FALSE; } return zone->introspect->enable_discharge_checking(zone); } void malloc_zone_disable_discharge_checking(malloc_zone_t *zone) { if (zone->version < 7) { // Version must be >= 7 to look at the new discharge checking fields. return; } if (NULL == zone->introspect->disable_discharge_checking) { return; } zone->introspect->disable_discharge_checking(zone); } void malloc_zone_discharge(malloc_zone_t *zone, void *memory) { if (NULL == zone) { zone = malloc_zone_from_ptr(memory); } if (NULL == zone) { return; } if (zone->version < 7) { // Version must be >= 7 to look at the new discharge checking fields. return; } if (NULL == zone->introspect->discharge) { return; } zone->introspect->discharge(zone, memory); } void malloc_zone_enumerate_discharged_pointers(malloc_zone_t *zone, void (^report_discharged)(void *memory, void *info)) { if (!zone) { unsigned index = 0; while (index < malloc_num_zones) { zone = malloc_zones[index++]; if (zone->version < 7) { continue; } if (NULL == zone->introspect->enumerate_discharged_pointers) { continue; } zone->introspect->enumerate_discharged_pointers(zone, report_discharged); } } else { if (zone->version < 7) { return; } if (NULL == zone->introspect->enumerate_discharged_pointers) { return; } zone->introspect->enumerate_discharged_pointers(zone, report_discharged); } } /***************** OBSOLETE ENTRY POINTS ********************/ #if PHASE_OUT_OLD_MALLOC #error PHASE OUT THE FOLLOWING FUNCTIONS #endif void set_malloc_singlethreaded(boolean_t single) { static boolean_t warned = 0; if (!warned) { #if PHASE_OUT_OLD_MALLOC malloc_report(ASL_LEVEL_ERR, "*** OBSOLETE: set_malloc_singlethreaded(%d)\n", single); #endif warned = 1; } } void malloc_singlethreaded(void) { static boolean_t warned = 0; if (!warned) { malloc_report(ASL_LEVEL_ERR, "*** OBSOLETE: malloc_singlethreaded()\n"); warned = 1; } } int malloc_debug(int level) { malloc_report(ASL_LEVEL_ERR, "*** OBSOLETE: malloc_debug()\n"); return 0; } /* vim: set noet:ts=4:sw=4:cindent: */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/malloc_common.c ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" #pragma mark - #pragma mark Utility Functions // libplatform does not have strstr() and we don't want to add any new // dependencies on libc, so we have to implement a version of strntr() // here. Fortunately, as it's only used to look for boot arguments, it does not // have to be efficient. We can also assume that the source string is // nul-terminated. Eventually, we will move the function to a more central // location and use it to replace other uses of strstr(). const char * malloc_common_strstr(const char *src, const char *target, size_t target_len) { const char *next = src; while (*next) { if (!strncmp(next, target, target_len)) { return next; } next++; } return NULL; } // Converts a string to a long. If a non-numeric value is found, the // return value is whatever has been accumulated so far. end_ptr always points // to the character that caused the conversion to stop. We can't use strtol() // etc because that would add a new dependency on libc. Eventually, this // function could be made generally available within the library and used to // replace the existing calls to strtol(). Currenly only handles non-negative // numbers and does not detect overflow. long malloc_common_convert_to_long(const char *ptr, const char **end_ptr) { long value = 0; while (*ptr) { char c = *ptr; if (c < '0' || c > '9') { break; } value = value * 10 + (c - '0'); ptr++; } *end_ptr = ptr; return value; } // Looks for a sequence of the form "key=value" in the string 'src' and // returns the location of the first character of 'value', or NULL if not // found. No spaces are permitted around the "=". const char * malloc_common_value_for_key(const char *src, const char *key) { const char *ptr = src; size_t keylen = strlen(key); while ((ptr = malloc_common_strstr(ptr, key, keylen)) != NULL) { ptr += keylen; if (*ptr == '=') { return ptr + 1; } } return NULL; } // Looks for a sequence of the form "key=value" in the string 'src' and // returns the location of the first character of 'value'. No spaces are // permitted around the "=". The value is copied to 'bufp', up to the first // whitespace or nul character and bounded by maxlen, and nul-terminated. // Returns bufp if the key was found, NULL if not. const char * malloc_common_value_for_key_copy(const char *src, const char *key, char *bufp, size_t maxlen) { const char *ptr = malloc_common_value_for_key(src, key); if (ptr) { char *to = bufp; while (maxlen > 1) { // Always leave room for a '\0' char c = *ptr++; if (c == '\0' || c == ' ' || c == '\t' || c == '\n') { break; } *to++ = c; maxlen--; } *to = '\0'; // Always nul-terminate return bufp; } return NULL; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/malloc_common.h ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __MALLOC_COMMON_H #define __MALLOC_COMMON_H MALLOC_NOEXPORT const char * malloc_common_strstr(const char *src, const char *target, size_t target_len); MALLOC_NOEXPORT long malloc_common_convert_to_long(const char *ptr, const char **end_ptr); MALLOC_NOEXPORT const char * malloc_common_value_for_key(const char *src, const char *key); MALLOC_NOEXPORT const char * malloc_common_value_for_key_copy(const char *src, const char *key, char *bufp, size_t maxlen); #endif // __MALLOC_COMMON_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/malloc_printf.c ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" /* global flag to suppress ASL logging e.g. for syslogd */ int _malloc_no_asl_log = 0; typedef enum { DEBUG_WRITE_NONE, DEBUG_WRITE_ON_CRASH, DEBUG_WRITE_ALWAYS, } write_debug_mode_t; static const char Malloc_Facility[] = "com.apple.Libsystem.malloc"; static int malloc_debug_file = STDERR_FILENO; static write_debug_mode_t debug_mode = DEBUG_WRITE_NONE; static boolean_t malloc_error_stop; // Stop when reporting error. static boolean_t malloc_error_sleep; // Sleep after reporting error. static const int default_sleep_time = 3600; // Gets the default time to sleep for when reporting an error. Returns 0 // (meaning do not sleep) if malloc_error_sleep is 0 (that is, if sleeping on // error is not configured). MALLOC_INLINE MALLOC_ALWAYS_INLINE static unsigned _malloc_default_debug_sleep_time() { return malloc_error_sleep ? default_sleep_time : 0; } #define WRITE_TO_DEBUG_FILE(flags) \ ((debug_mode == DEBUG_WRITE_ALWAYS) || \ (debug_mode == DEBUG_WRITE_ON_CRASH && (flags & MALLOC_REPORT_CRASH))) #define MALLOC_REPORT_LEVEL_MASK 0x0f #pragma mark - #pragma mark Configuration void malloc_print_configure(bool restricted) { char *flag = getenv("MallocDebugReport"); if (flag) { if (!strcmp(flag, "stderr")) { debug_mode = DEBUG_WRITE_ALWAYS; } else if (!strcmp(flag, "crash")) { debug_mode = DEBUG_WRITE_ON_CRASH; } else if (!strcmp(flag, "none")) { debug_mode = DEBUG_WRITE_NONE; } else { debug_mode = DEBUG_WRITE_ALWAYS; malloc_printf("Unrecognized value for MallocDebugReport (%s) - using 'stderr'\n", flag); } } else { // Default is to write to stderr only if it's a tty. if (isatty(STDERR_FILENO)) { debug_mode = DEBUG_WRITE_ALWAYS; } } if (getenv("MallocErrorStop")) { malloc_error_stop = TRUE; } if (getenv("MallocErrorSleep")) { malloc_error_sleep = TRUE; } } #pragma mark - #pragma mark Low level debug output /* * The functions that follow use _simple_*printf. They deal with a * subset of printf format specifiers and do not call malloc internally. */ static void _malloc_put(uint32_t flags, const char *msg) { _SIMPLE_STRING b; if ((b = _simple_salloc()) == NULL) { if (WRITE_TO_DEBUG_FILE(flags)) { if (!(flags & MALLOC_REPORT_NOPREFIX)) { void *self = _os_tsd_get_direct(__TSD_THREAD_SELF); _simple_dprintf(malloc_debug_file, "%s(%d,%p) malloc: ", getprogname(), getpid(), self); } write(malloc_debug_file, msg, strlen(msg)); } return; } if (!(flags & MALLOC_REPORT_NOPREFIX)) { void *self = _os_tsd_get_direct(__TSD_THREAD_SELF); _simple_sprintf(b, "%s(%d,%p) malloc: ", getprogname(), getpid(), self); } _simple_sprintf(b, "%s", msg); if (WRITE_TO_DEBUG_FILE(flags)) { _simple_put(b, malloc_debug_file); } if (_malloc_no_asl_log & !(flags & MALLOC_REPORT_NOLOG)) { _simple_asl_log(flags & MALLOC_REPORT_LEVEL_MASK, Malloc_Facility, _simple_string(b)); } _simple_sfree(b); } #pragma mark - #pragma mark High-Level Reporting Functions MALLOC_NOINLINE void malloc_vreport(uint32_t flags, unsigned sleep_time, const char *prefix_msg, const void *prefix_arg, const char *fmt, va_list ap) { const char *crash_msg = NULL; _SIMPLE_STRING b = NULL; if ((b = _simple_salloc()) == NULL) { if (WRITE_TO_DEBUG_FILE(flags)) { if (!(flags & MALLOC_REPORT_NOPREFIX)) { void *self = _os_tsd_get_direct(__TSD_THREAD_SELF); _simple_dprintf(malloc_debug_file, "%s(%d,%p) malloc: ", getprogname(), getpid(), self); } if (prefix_msg) { _simple_dprintf(malloc_debug_file, prefix_msg, prefix_arg); } _simple_vdprintf(malloc_debug_file, fmt, ap); } if (flags & MALLOC_REPORT_CRASH) { crash_msg = fmt; } } else { if (!(flags & MALLOC_REPORT_NOPREFIX)) { void *self = _os_tsd_get_direct(__TSD_THREAD_SELF); _simple_sprintf(b, "%s(%d,%p) malloc: ", getprogname(), getpid(), self); } if (prefix_msg) { _simple_sprintf(b, prefix_msg, prefix_arg); } _simple_vsprintf(b, fmt, ap); if (WRITE_TO_DEBUG_FILE(flags)) { _simple_put(b, malloc_debug_file); } if (!_malloc_no_asl_log && !(flags & MALLOC_REPORT_NOLOG)) { _simple_asl_log(flags & MALLOC_REPORT_LEVEL_MASK, Malloc_Facility, _simple_string(b)); } if (flags & MALLOC_REPORT_CRASH) { crash_msg = _simple_string(b); } else { _simple_sfree(b); } } if (flags & (MALLOC_REPORT_DEBUG | MALLOC_REPORT_CRASH)) { _malloc_put(flags, "*** set a breakpoint in malloc_error_break to debug\n"); malloc_error_break(); if (malloc_error_stop) { _malloc_put(ASL_LEVEL_NOTICE, "*** sending SIGSTOP to help debug\n"); kill(getpid(), SIGSTOP); } else if (sleep_time) { _malloc_put(ASL_LEVEL_NOTICE, "*** sleeping to help debug\n"); sleep(sleep_time); } } if (flags & MALLOC_REPORT_CRASH) { _os_set_crash_log_message_dynamic(crash_msg); abort(); } } MALLOC_NOEXPORT void malloc_report(uint32_t flags, const char *fmt, ...) { va_list ap; va_start(ap, fmt); malloc_vreport(flags, _malloc_default_debug_sleep_time(), NULL, NULL, fmt, ap); va_end(ap); } #pragma mark - #pragma mark Zone Error Reporing void malloc_zone_error(uint32_t flags, bool is_corruption, const char *fmt, ...) { va_list ap; va_start(ap, fmt); uint32_t report_flags = MALLOC_REPORT_DEBUG | MALLOC_REPORT_NOLOG; if ((is_corruption && (flags & MALLOC_ABORT_ON_CORRUPTION)) || (flags & MALLOC_ABORT_ON_ERROR)) { report_flags = MALLOC_REPORT_CRASH; } malloc_vreport(report_flags | ASL_LEVEL_ERR, _malloc_default_debug_sleep_time(), NULL, NULL, fmt, ap); va_end(ap); } #pragma mark - #pragma mark Malloc Output API. // malloc_printf() needs to be retained and exported because it's API (defined // in malloc/malloc.h). It's equivalent to calling malloc_report() with // a flags value of ASL_LEVEL_ERR, so does not result in a crash or any prompts // for diagnostics or breakpoints. // Do not use in malloc code. void malloc_printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); malloc_vreport(ASL_LEVEL_ERR, 0, NULL, NULL, fmt, ap); va_end(ap); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nano_malloc.c ================================================ /* * Copyright (c) 1999, 2000, 2003, 2005, 2008, 2012 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" /* nano_malloc for 64bit ABI */ #if CONFIG_NANOZONE /********************* PROTOTYPES ***********************/ static void nano_statistics(nanozone_t *nanozone, malloc_statistics_t *stats); /********************* VERY LOW LEVEL UTILITIES ************************/ // msg prints after fmt, ... static MALLOC_ALWAYS_INLINE unsigned int nano_mag_index(const nanozone_t *nanozone) { if (os_likely(_os_cpu_number_override == -1)) { return (_os_cpu_number() >> hyper_shift) % nano_common_max_magazines; } return (_os_cpu_number_override >> hyper_shift) % nano_common_max_magazines; } #if NANO_PREALLOCATE_BAND_VM static boolean_t nano_preallocate_band_vm(void) { nano_blk_addr_t u; uintptr_t s, e; u.fields.nano_signature = NANOZONE_SIGNATURE; u.fields.nano_mag_index = 0; u.fields.nano_band = 0; u.fields.nano_slot = 0; u.fields.nano_offset = 0; s = u.addr; // start of first possible band u.fields.nano_mag_index = (1 << NANO_MAG_BITS) - 1; u.fields.nano_band = (1 << NANO_BAND_BITS) - 1; e = u.addr + BAND_SIZE; // end of last possible band return nano_common_allocate_vm_space(s, e - s); } #endif /* * We maintain separate free lists for each (quantized) size. The literature * calls this the "segregated policy". */ static boolean_t segregated_band_grow(nanozone_t *nanozone, nano_meta_admin_t pMeta, size_t slot_bytes, unsigned int mag_index) { nano_blk_addr_t u; // the compiler holds this in a register uintptr_t p, s; size_t watermark, hiwater; if (0 == pMeta->slot_current_base_addr) { // First encounter? u.fields.nano_signature = NANOZONE_SIGNATURE; u.fields.nano_mag_index = mag_index; u.fields.nano_band = 0; u.fields.nano_slot = (slot_bytes >> SHIFT_NANO_QUANTUM) - 1; u.fields.nano_offset = 0; p = u.addr; pMeta->slot_bytes = (unsigned int)slot_bytes; pMeta->slot_objects = SLOT_IN_BAND_SIZE / slot_bytes; } else { p = pMeta->slot_current_base_addr + BAND_SIZE; // Growing, so stride ahead by BAND_SIZE u.addr = (uint64_t)p; if (0 == u.fields.nano_band) { // Did the band index wrap? return FALSE; } assert(slot_bytes == pMeta->slot_bytes); } pMeta->slot_current_base_addr = p; mach_vm_address_t vm_addr = p & ~((uintptr_t)(BAND_SIZE - 1)); // Address of the (2MB) band covering this (128KB) slot if (nanozone->band_max_mapped_baseaddr[mag_index] < vm_addr) { #if !NANO_PREALLOCATE_BAND_VM // Obtain the next band to cover this slot kern_return_t kr = mach_vm_map(mach_task_self(), &vm_addr, BAND_SIZE, 0, VM_MAKE_TAG(VM_MEMORY_MALLOC_NANO), MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT); void *q = (void *)vm_addr; if (kr || q != (void *)(p & ~((uintptr_t)(BAND_SIZE - 1)))) { // Must get exactly what we asked for if (!kr) { mach_vm_deallocate(mach_task_self(), vm_addr, BAND_SIZE); } return FALSE; } #endif nanozone->band_max_mapped_baseaddr[mag_index] = vm_addr; } // Randomize the starting allocation from this slot (introduces 11 to 14 bits of entropy) if (0 == pMeta->slot_objects_mapped) { // First encounter? pMeta->slot_objects_skipped = (malloc_entropy[1] % (SLOT_IN_BAND_SIZE / slot_bytes)); pMeta->slot_bump_addr = p + (pMeta->slot_objects_skipped * slot_bytes); } else { pMeta->slot_bump_addr = p; } pMeta->slot_limit_addr = p + (SLOT_IN_BAND_SIZE / slot_bytes) * slot_bytes; pMeta->slot_objects_mapped += (SLOT_IN_BAND_SIZE / slot_bytes); u.fields.nano_signature = NANOZONE_SIGNATURE; u.fields.nano_mag_index = mag_index; u.fields.nano_band = 0; u.fields.nano_slot = 0; u.fields.nano_offset = 0; s = u.addr; // Base for this core. // Set the high water mark for this CPU's entire magazine, if this resupply raised it. watermark = nanozone->core_mapped_size[mag_index]; hiwater = MAX(watermark, p - s + SLOT_IN_BAND_SIZE); nanozone->core_mapped_size[mag_index] = hiwater; return TRUE; } static inline unsigned long divrem(unsigned long a, unsigned int b, unsigned int *remainder) { // Encapsulating the modulo and division in an in-lined function convinces the compiler // to issue just a single divide instruction to obtain quotient and remainder. Go figure. *remainder = a % b; return a / b; } static MALLOC_INLINE void * segregated_next_block(nanozone_t *nanozone, nano_meta_admin_t pMeta, size_t slot_bytes, unsigned int mag_index) { while (1) { uintptr_t theLimit = pMeta->slot_limit_addr; // Capture the slot limit that bounds slot_bump_addr right now uintptr_t b = OSAtomicAdd64Barrier(slot_bytes, (volatile int64_t *)&(pMeta->slot_bump_addr)); b -= slot_bytes; // Atomic op returned addr of *next* free block. Subtract to get addr for *this* allocation. if (b < theLimit) { // Did we stay within the bound of the present slot allocation? return (void *)b; // Yep, so the slot_bump_addr this thread incremented is good to go } else { if (pMeta->slot_exhausted) { // exhausted all the bands availble for this slot? pMeta->slot_bump_addr = theLimit; return 0; // We're toast } else { // One thread will grow the heap, others will see its been grown and retry allocation _malloc_lock_lock(&nanozone->band_resupply_lock[mag_index]); // re-check state now that we've taken the lock if (pMeta->slot_exhausted) { _malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]); return 0; // Toast } else if (b < pMeta->slot_limit_addr) { _malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]); continue; // ... the slot was successfully grown by first-taker (not us). Now try again. } else if (segregated_band_grow(nanozone, pMeta, slot_bytes, mag_index)) { _malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]); continue; // ... the slot has been successfully grown by us. Now try again. } else { pMeta->slot_exhausted = TRUE; pMeta->slot_bump_addr = theLimit; _malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]); return 0; } } } } } static MALLOC_INLINE size_t segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey) { size_t k, slot_bytes; if (0 == size) { size = NANO_REGIME_QUANTA_SIZE; // Historical behavior } k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size *pKey = k - 1; // Zero-based! return slot_bytes; } static MALLOC_INLINE index_t offset_to_index(nanozone_t *nanozone, nano_meta_admin_t pMeta, uintptr_t offset) { unsigned int slot_bytes = pMeta->slot_bytes; unsigned int slot_objects = pMeta->slot_objects; // SLOT_IN_BAND_SIZE / slot_bytes; unsigned int rem; unsigned long quo = divrem(offset, BAND_SIZE, &rem); assert(0 == rem % slot_bytes || pMeta->slot_exhausted); return (index_t)((quo * slot_objects) + (rem / slot_bytes)); } static MALLOC_INLINE uintptr_t index_to_offset(nanozone_t *nanozone, nano_meta_admin_t pMeta, index_t i) { unsigned int slot_bytes = pMeta->slot_bytes; unsigned int slot_objects = pMeta->slot_objects; // SLOT_IN_BAND_SIZE / slot_bytes; unsigned int rem; unsigned long quo = divrem(i, slot_objects, &rem); return (quo * BAND_SIZE) + (rem * slot_bytes); } static kern_return_t segregated_in_use_enumerator(task_t task, void *context, unsigned type_mask, nanozone_t *nanozone, memory_reader_t reader, vm_range_recorder_t recorder) { unsigned int mag_index, slot_key; vm_range_t ptr_range; vm_range_t buffer[MAX_RECORDER_BUFFER]; kern_return_t err; unsigned count = 0; for (mag_index = 0; mag_index < nano_common_max_magazines; mag_index++) { uintptr_t clone_magazine; // magazine base for ourselves nano_blk_addr_t p; // slot base for remote uintptr_t clone_slot_base; // slot base for ourselves (tracks with "p") // Establish p as base address for slot 0 in remote p.fields.nano_signature = NANOZONE_SIGNATURE; p.fields.nano_mag_index = mag_index; p.fields.nano_band = 0; p.fields.nano_slot = 0; p.fields.nano_offset = 0; if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) { mach_vm_address_t vm_addr; mach_vm_size_t alloc_size = nanozone->core_mapped_size[mag_index]; int alloc_flags = VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MALLOC); vm_addr = vm_page_size; kern_return_t kr = mach_vm_allocate(mach_task_self(), &vm_addr, alloc_size, alloc_flags); if (kr) { return kr; } clone_magazine = (uintptr_t)vm_addr; clone_slot_base = clone_magazine; // base for slot 0 in this local magazine } else { clone_slot_base = clone_magazine = 0; // and won't be used in this loop } for (slot_key = 0; slot_key < SLOT_KEY_LIMIT; p.addr += SLOT_IN_BAND_SIZE, // Advance to next slot base for remote clone_slot_base += SLOT_IN_BAND_SIZE, // Advance to next slot base for ourselves slot_key++) { nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]); size_t slot_objects_mapped = pMeta->slot_objects_mapped; // capture this volatile count if (0 == slot_objects_mapped) { // Nothing allocated in this magazine for this slot? continue; } if (type_mask & MALLOC_ADMIN_REGION_RANGE_TYPE) { /* do NOTHING as there is no distinct admin region */ } if (type_mask & (MALLOC_PTR_REGION_RANGE_TYPE | MALLOC_ADMIN_REGION_RANGE_TYPE)) { nano_blk_addr_t q = p; uintptr_t skip_adj = index_to_offset(nanozone, pMeta, (index_t)pMeta->slot_objects_skipped); while (q.addr < pMeta->slot_limit_addr) { ptr_range.address = q.addr + skip_adj; ptr_range.size = SLOT_IN_BAND_SIZE - skip_adj; skip_adj = 0; recorder(task, context, MALLOC_PTR_REGION_RANGE_TYPE, &ptr_range, 1); q.addr += BAND_SIZE; } } if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) { nano_blk_addr_t q = p; uintptr_t slot_band, clone_slot_band_base = clone_slot_base; uintptr_t skip_adj = index_to_offset(nanozone, pMeta, (index_t)pMeta->slot_objects_skipped); // Copy the bitarray_t denoting madvise()'d pages (if any) into *this* task's address space bitarray_t madv_page_bitarray; int log_page_count; if (pMeta->slot_madvised_pages) { log_page_count = pMeta->slot_madvised_log_page_count; err = reader(task, (vm_address_t)(pMeta->slot_madvised_pages), bitarray_size(log_page_count), (void **)&madv_page_bitarray); if (err) { return err; } } else { madv_page_bitarray = NULL; log_page_count = 0; } while (q.addr < pMeta->slot_limit_addr) { // read slot in each remote band. Lands in some random location. Do not read // parts of the slot that are in madvised pages. if (!madv_page_bitarray) { // Nothing madvised yet - read everything in one go. size_t len = MIN(pMeta->slot_bump_addr - q.addr, SLOT_IN_BAND_SIZE) - skip_adj; err = reader(task, (vm_address_t)(q.addr + skip_adj), len, (void **)&slot_band); if (err) { return err; } // Place the data just read in the correct position relative to the local magazine. memcpy((void *)(clone_slot_band_base + skip_adj), (void *)slot_band, len); } else { // We madvised at least one page. Read only the pages that // have not been madvised. If bitarray_t had operations // like "get next bit set after a given bit" and "find // next unset bit after a given bit", we could do this more // efficiently but given that it doesn't, we have to walk // through each page individually. In practice this is not // much of an issue because this code is only used by // sampling tools and the additional time required is not // really noticeable. size_t len = MIN(pMeta->slot_bump_addr - q.addr, SLOT_IN_BAND_SIZE) - skip_adj; vm_address_t start_addr = (vm_address_t)(q.addr + skip_adj); vm_address_t end_addr = (vm_address_t)(start_addr + len); void *target_addr = (void *)(clone_slot_band_base + skip_adj); for (vm_address_t addr = start_addr; addr < end_addr;) { vm_address_t next_page_addr = trunc_page_kernel(addr + vm_kernel_page_size); size_t read_size = MIN(len, next_page_addr - addr); boolean_t madvised = false; nano_blk_addr_t r; r.addr = addr; index_t pgnum = ((((unsigned)r.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)r.fields.nano_offset)) >> vm_kernel_page_shift; unsigned int log_page_count = pMeta->slot_madvised_log_page_count; madvised = (pgnum < (1 << log_page_count)) && bitarray_get(madv_page_bitarray, log_page_count, pgnum); if (!madvised) { // This is not an madvised page - grab the data. err = reader(task, addr, read_size, (void **)&slot_band); if (err) { return err; } // Place the data just read in the correct position relative to the local magazine. memcpy(target_addr, (void *)slot_band, read_size); } else { // This is an madvised page - there should be nothing in here that's // on the freelist, so just write garbage to the target memory. memset(target_addr, (char)0xee, read_size); } addr = next_page_addr; target_addr += read_size; len -= read_size; } } // Simultaneously advance pointers in remote and ourselves to the next band. q.addr += BAND_SIZE; clone_slot_band_base += BAND_SIZE; skip_adj = 0; } // Walk the slot free list and populate a bitarray_t int log_size = 64 - __builtin_clzl(slot_objects_mapped); bitarray_t slot_bitarray = bitarray_create(log_size); if (!slot_bitarray) { return errno; } chained_block_t t; unsigned stoploss = (unsigned)slot_objects_mapped; while ((t = OSAtomicDequeue( &(pMeta->slot_LIFO), offsetof(struct chained_block_s, next) + (clone_slot_base - p.addr)))) { if (0 == stoploss) { malloc_report(ASL_LEVEL_ERR, "Free list walk in segregated_in_use_enumerator exceeded object count.\n"); break; } stoploss--; uintptr_t offset = ((uintptr_t)t - p.addr); // offset from beginning of slot, task-independent index_t block_index = offset_to_index(nanozone, pMeta, offset); if (block_index < slot_objects_mapped) { bitarray_set(slot_bitarray, log_size, block_index); } } // N.B. pMeta->slot_LIFO in *this* task is now drained (remote free list has *not* been disturbed) // Enumerate all the block indices issued to date, and report those not on the free list index_t i; for (i = (index_t)pMeta->slot_objects_skipped; i < slot_objects_mapped; ++i) { uintptr_t block_offset = index_to_offset(nanozone, pMeta, i); if (p.addr + block_offset >= pMeta->slot_bump_addr) { break; } // blocks falling on madvise()'d pages are free! So not enumerated. if (madv_page_bitarray) { nano_blk_addr_t q; index_t pgnum, pgnum_end; q.addr = p.addr + block_offset; pgnum = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; q.addr += pMeta->slot_bytes - 1; pgnum_end = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; if (pgnum < (1 << log_page_count)) { // bounds check for bitarray_get()'s that follow if (bitarray_get(madv_page_bitarray, log_page_count, pgnum) || bitarray_get(madv_page_bitarray, log_page_count, pgnum_end)) { continue; } } } if (!bitarray_get(slot_bitarray, log_size, i)) { buffer[count].address = p.addr + block_offset; buffer[count].size = (slot_key + 1) << SHIFT_NANO_QUANTUM; count++; if (count >= MAX_RECORDER_BUFFER) { recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count); count = 0; } } } if (count) { recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count); count = 0; } free(slot_bitarray); } } if (clone_magazine) { mach_vm_address_t vm_addr = clone_magazine; mach_vm_size_t alloc_size = nanozone->core_mapped_size[mag_index]; mach_vm_deallocate(mach_task_self(), vm_addr, alloc_size); } } return 0; } /****************** nanozone methods **********************/ /* * These methods are called with "ptr" known to possess the nano signature (from * which we can additionally infer "ptr" is not NULL), and with "size" bounded to * the extent of the nano allocation regime -- (0, 256]. */ static MALLOC_INLINE MALLOC_UNUSED boolean_t _nano_block_inuse_p(nanozone_t *nanozone, const void *ptr) { nano_blk_addr_t p; // happily, the compiler holds this in a register nano_meta_admin_t pMeta; chained_block_t head = NULL, tail = NULL, t; boolean_t inuse = TRUE; p.addr = (uint64_t)ptr; // place ptr on the dissecting table pMeta = &(nanozone->meta_data[p.fields.nano_mag_index][p.fields.nano_slot]); // pop elements off the free list all the while looking for ptr. unsigned stoploss = (unsigned)pMeta->slot_objects_mapped; while ((t = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next)))) { if (0 == stoploss) { malloc_zone_error(nanozone->debug_flags, true, "Free list walk for slot %p in _nano_block_inuse_p exceeded object count.\n", (void *)&(pMeta->slot_LIFO)); } stoploss--; if (NULL == head) { head = t; } else { tail->next = t; } tail = t; if (ptr == t) { inuse = FALSE; break; } } if (tail) { tail->next = NULL; } // push the free list extracted above back onto the LIFO, all at once if (head) { OSAtomicEnqueue(&(pMeta->slot_LIFO), head, (uintptr_t)tail - (uintptr_t)head + offsetof(struct chained_block_s, next)); } return inuse; } static MALLOC_INLINE size_t __nano_vet_and_size_inner(nanozone_t *nanozone, const void *ptr, boolean_t inner) { // Extracts the size of the block in bytes. Checks for a plausible ptr. nano_blk_addr_t p; // the compiler holds this in a register nano_meta_admin_t pMeta; p.addr = (uint64_t)ptr; // Begin the dissection of ptr if (NANOZONE_SIGNATURE != p.fields.nano_signature) { return 0; } if (nano_common_max_magazines <= p.fields.nano_mag_index) { return 0; } if (!inner && p.fields.nano_offset & NANO_QUANTA_MASK) { // stray low-order bits? return 0; } pMeta = &(nanozone->meta_data[p.fields.nano_mag_index][p.fields.nano_slot]); if ((void *)(pMeta->slot_bump_addr) <= ptr) { return 0; // Beyond what's ever been allocated! } if (!inner && ((p.fields.nano_offset % pMeta->slot_bytes) != 0)) { return 0; // Not an exact multiple of the block size for this slot } return pMeta->slot_bytes; } static MALLOC_INLINE size_t __nano_vet_and_size(nanozone_t *nanozone, const void *ptr) { return __nano_vet_and_size_inner(nanozone, ptr, false); } static MALLOC_ALWAYS_INLINE boolean_t _nano_block_has_canary_value(nanozone_t *nanozone, const void *ptr) { return (((chained_block_t)ptr)->double_free_guard ^ nanozone->cookie) == (uintptr_t)ptr; } static MALLOC_ALWAYS_INLINE void _nano_block_set_canary_value(nanozone_t *nanozone, const void *ptr) { ((chained_block_t)ptr)->double_free_guard = ((uintptr_t)ptr) ^ nanozone->cookie; } static MALLOC_INLINE size_t _nano_vet_and_size_of_live(nanozone_t *nanozone, const void *ptr) { size_t size = __nano_vet_and_size(nanozone, ptr); if (0 == size) { // ptr fails sanity check? return 0; } // We have the invariant: If ptr is on a free list, then ptr->double_free_guard is the canary. // So if ptr->double_free_guard is NOT the canary, then ptr is not on a free list, hence is live. if (!_nano_block_has_canary_value(nanozone, ptr)) { return size; // Common case: not on a free list, hence live. Return its size. } else { // confirm that ptr is live despite ptr->double_free_guard having the canary value if (_nano_block_inuse_p(nanozone, ptr)) { return size; // live block that exhibits canary } else { return 0; // ptr wasn't live after all (likely a double free) } } } static MALLOC_INLINE size_t _nano_vet_and_size_of_free(nanozone_t *nanozone, const void *ptr) { size_t size = __nano_vet_and_size(nanozone, ptr); if (0 == size) { // ptr fails sanity check? return 0; } // ptr was just dequed from a free list, so ptr->double_free_guard must have the canary value. if (_nano_block_has_canary_value(nanozone, ptr)) { return size; // return the size of this well formed free block. } else { return 0; // Broken invariant: If ptr is on a free list, then ptr->double_free_guard is the canary. (likely use after free) } } static void * _nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested) { MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0); void *ptr; size_t slot_key; size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here mag_index_t mag_index = nano_mag_index(nanozone); nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]); ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next)); if (ptr) { unsigned debug_flags = nanozone->debug_flags; #if NANO_FREE_DEQUEUE_DILIGENCE size_t gotSize; nano_blk_addr_t p; // the compiler holds this in a register p.addr = (uint64_t)ptr; // Begin the dissection of ptr if (NANOZONE_SIGNATURE != p.fields.nano_signature) { malloc_zone_error(debug_flags, true, "Invalid signature for pointer %p dequeued from free list\n", ptr); } if (mag_index != p.fields.nano_mag_index) { malloc_zone_error(debug_flags, true, "Mismatched magazine for pointer %p dequeued from free list\n", ptr); } gotSize = _nano_vet_and_size_of_free(nanozone, ptr); if (0 == gotSize) { malloc_zone_error(debug_flags, true, "Invalid pointer %p dequeued from free list\n", ptr); } if (gotSize != slot_bytes) { malloc_zone_error(debug_flags, true, "Mismatched size for pointer %p dequeued from free list\n", ptr); } if (!_nano_block_has_canary_value(nanozone, ptr)) { malloc_zone_error(debug_flags, true, "Heap corruption detected, free list canary is damaged for %p\n" "*** Incorrect guard value: %lu\n", ptr, ((chained_block_t)ptr)->double_free_guard); } #if defined(DEBUG) void *next = (void *)(((chained_block_t)ptr)->next); if (next) { p.addr = (uint64_t)next; // Begin the dissection of next if (NANOZONE_SIGNATURE != p.fields.nano_signature) { malloc_zone_error(debug_flags, true, "Invalid next signature for pointer %p dequeued from free " "list, next = %p\n", ptr, "next"); } if (mag_index != p.fields.nano_mag_index) { malloc_zone_error(debug_flags, true, "Mismatched next magazine for pointer %p dequeued from " "free list, next = %p\n", ptr, next); } gotSize = _nano_vet_and_size_of_free(nanozone, next); if (0 == gotSize) { malloc_zone_error(debug_flags, true, "Invalid next for pointer %p dequeued from free list, " "next = %p\n", ptr, next); } if (gotSize != slot_bytes) { malloc_zone_error(debug_flags, true, "Mismatched next size for pointer %p dequeued from free " "list, next = %p\n", ptr, next); } } #endif /* DEBUG */ #endif /* NANO_FREE_DEQUEUE_DILIGENCE */ ((chained_block_t)ptr)->double_free_guard = 0; ((chained_block_t)ptr)->next = NULL; // clear out next pointer to protect free list } else { ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index); } if (cleared_requested && ptr) { memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first? } return ptr; } static void * _nano_malloc_check_scribble(nanozone_t *nanozone, size_t size) { void *ptr = _nano_malloc_check_clear(nanozone, size, 0); /* * Scribble on allocated memory when requested. */ if ((nanozone->debug_flags & MALLOC_DO_SCRIBBLE) && ptr && size) { memset(ptr, SCRIBBLE_BYTE, _nano_vet_and_size_of_live(nanozone, ptr)); } return ptr; } static MALLOC_INLINE size_t _nano_size(nanozone_t *nanozone, const void *ptr) { return _nano_vet_and_size_of_live(nanozone, ptr); } static MALLOC_INLINE size_t _nano_good_size(nanozone_t *nanozone, size_t size) { return (size <= NANO_REGIME_QUANTA_SIZE) ? NANO_REGIME_QUANTA_SIZE : (((size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM) << SHIFT_NANO_QUANTUM); } static MALLOC_INLINE void _nano_free_trusted_size_check_scribble(nanozone_t *nanozone, void *ptr, size_t trusted_size, boolean_t do_scribble) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE void _nano_free_trusted_size_check_scribble(nanozone_t *nanozone, void *ptr, size_t trusted_size, boolean_t do_scribble) { if (trusted_size) { nano_blk_addr_t p; // happily, the compiler holds this in a register nano_meta_admin_t pMeta; if (do_scribble) { (void)memset(ptr, SCRABBLE_BYTE, trusted_size); } _nano_block_set_canary_value(nanozone, ptr); p.addr = (uint64_t)ptr; // place ptr on the dissecting table pMeta = &(nanozone->meta_data[p.fields.nano_mag_index][p.fields.nano_slot]); OSAtomicEnqueue(&(pMeta->slot_LIFO), ptr, offsetof(struct chained_block_s, next)); } else { malloc_zone_error(nanozone->debug_flags, true, "Freeing unallocated pointer %p\n", ptr); } } static MALLOC_INLINE void _nano_free_check_scribble(nanozone_t *nanozone, void *ptr, boolean_t do_scribble) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE void _nano_free_check_scribble(nanozone_t *nanozone, void *ptr, boolean_t do_scribble) { _nano_free_trusted_size_check_scribble(nanozone, ptr, _nano_vet_and_size_of_live(nanozone, ptr), do_scribble); } static MALLOC_INLINE void * _nano_realloc(nanozone_t *nanozone, void *ptr, size_t new_size) { size_t old_size, new_good_size, valid_size; void *new_ptr; if (FALSE && NULL == ptr) { // ptr has our_signature so can't be NULL, but if it were Posix sez ... // If ptr is a null pointer, realloc() shall be equivalent to malloc() for the specified size. return _nano_malloc_check_scribble(nanozone, new_size); } else if (0 == new_size) { // If size is 0 and ptr is not a null pointer, the object pointed to is freed. _nano_free_check_scribble(nanozone, ptr, (nanozone->debug_flags & MALLOC_DO_SCRIBBLE)); // If size is 0, either a null pointer or a unique pointer that can be successfully passed // to free() shall be returned. return _nano_malloc_check_scribble(nanozone, 1); } old_size = _nano_vet_and_size_of_live(nanozone, ptr); if (!old_size) { malloc_zone_error(nanozone->debug_flags, true, "pointer %p being reallocated was not allocated\n", ptr); return NULL; } new_good_size = _nano_good_size(nanozone, new_size); if (new_good_size > old_size) { /* Must grow. FALL THROUGH to alloc/copy/free. */ } else if (new_good_size <= (old_size >> 1)) { /* Serious shrinkage (more than half). FALL THROUGH to alloc/copy/free. */ } else { /* Let's hang on to what we got. */ if (nanozone->debug_flags & MALLOC_DO_SCRIBBLE) { memset(ptr + new_size, SCRIBBLE_BYTE, old_size - new_size); } return ptr; } /* * Allocate a new buffer and copy. */ new_ptr = _nano_malloc_check_scribble(nanozone, new_good_size); if (new_ptr == NULL) { return NULL; } valid_size = MIN(old_size, new_good_size); memcpy(new_ptr, ptr, valid_size); _nano_free_check_scribble(nanozone, ptr, (nanozone->debug_flags & MALLOC_DO_SCRIBBLE)); return new_ptr; } static MALLOC_INLINE void _nano_destroy(nanozone_t *nanozone) { /* Now destroy the separate nanozone region */ nano_common_deallocate_pages((void *)nanozone, NANOZONE_PAGED_SIZE, nanozone->debug_flags); } /****************** nanozone dispatch **********************/ static void * nano_malloc(nanozone_t *nanozone, size_t size) { if (size <= NANO_MAX_SIZE) { void *p = _nano_malloc_check_clear(nanozone, size, 0); if (p) { return p; } else { /* FALLTHROUGH to helper zone */ } } malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->malloc(zone, size); } static void * nano_forked_malloc(nanozone_t *nanozone, size_t size) { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->malloc(zone, size); } static void * nano_malloc_scribble(nanozone_t *nanozone, size_t size) { if (size <= NANO_MAX_SIZE) { void *ptr = _nano_malloc_check_clear(nanozone, size, 0); if (ptr) { /* * Scribble on allocated memory. */ if (size) { memset(ptr, SCRIBBLE_BYTE, _nano_vet_and_size_of_live(nanozone, ptr)); } return ptr; } else { /* FALLTHROUGH to helper zone */ } } malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->malloc(zone, size); } static void * nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size) { size_t total_bytes; if (calloc_get_size(num_items, size, 0, &total_bytes)) { return NULL; } if (total_bytes <= NANO_MAX_SIZE) { void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1); if (p) { return p; } else { /* FALLTHROUGH to helper zone */ } } malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->calloc(zone, 1, total_bytes); } static void * nano_forked_calloc(nanozone_t *nanozone, size_t num_items, size_t size) { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->calloc(zone, num_items, size); } static void * nano_valloc(nanozone_t *nanozone, size_t size) { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->valloc(zone, size); } static MALLOC_INLINE void __nano_free_definite_size(nanozone_t *nanozone, void *ptr, size_t size, boolean_t do_scribble) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE void __nano_free_definite_size(nanozone_t *nanozone, void *ptr, size_t size, boolean_t do_scribble) { nano_blk_addr_t p; // happily, the compiler holds this in a register p.addr = (uint64_t)ptr; // place ptr on the dissecting table if (NANOZONE_SIGNATURE == p.fields.nano_signature) { if (size == ((p.fields.nano_slot + 1) << SHIFT_NANO_QUANTUM)) { // "Trust but verify." _nano_free_trusted_size_check_scribble(nanozone, ptr, size, do_scribble); return; } else { malloc_zone_error(nanozone->debug_flags, true, "Freeing pointer %p whose size was misdeclared\n", ptr); } } else { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); zone->free_definite_size(zone, ptr, size); return; } /* NOTREACHED */ } static void nano_free_definite_size(nanozone_t *nanozone, void *ptr, size_t size) { __nano_free_definite_size(nanozone, ptr, size, 0); } static void nano_free_definite_size_scribble(nanozone_t *nanozone, void *ptr, size_t size) { __nano_free_definite_size(nanozone, ptr, size, 1); } static MALLOC_INLINE void __nano_free(nanozone_t *nanozone, void *ptr, boolean_t do_scribble) MALLOC_ALWAYS_INLINE; static MALLOC_INLINE void __nano_free(nanozone_t *nanozone, void *ptr, boolean_t do_scribble) { MALLOC_TRACE(TRACE_nano_free, (uintptr_t)nanozone, (uintptr_t)ptr, do_scribble, 0); if (!ptr) { return; // Protect against malloc_zone_free() passing NULL. } // exhausting a slot may result in a pointer with // the nanozone prefix being given to nano_free via malloc_zone_free. Calling // vet_and_size here, instead of in _nano_free_check_scribble means we can // early-out into the helper_zone if it turns out nano does not own this ptr. size_t sz = _nano_vet_and_size_of_live(nanozone, ptr); if (sz) { _nano_free_trusted_size_check_scribble(nanozone, ptr, sz, do_scribble); return; } else { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); zone->free(zone, ptr); return; } /* NOTREACHED */ } static void nano_free(nanozone_t *nanozone, void *ptr) { __nano_free(nanozone, ptr, 0); } static void nano_forked_free(nanozone_t *nanozone, void *ptr) { if (!ptr) { return; // Protect against malloc_zone_free() passing NULL. } // exhausting a slot may result in a pointer with // the nanozone prefix being given to nano_free via malloc_zone_free. Calling // vet_and_size here, instead of in _nano_free_check_scribble means we can // early-out into the helper_zone if it turns out nano does not own this ptr. size_t sz = _nano_vet_and_size_of_live(nanozone, ptr); if (sz) { /* NOTHING. Drop it on the floor as nanozone metadata could be fouled by fork. */ return; } else { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); zone->free(zone, ptr); return; } /* NOTREACHED */ } static void nano_forked_free_definite_size(nanozone_t *nanozone, void *ptr, size_t size) { nano_forked_free(nanozone, ptr); } static void nano_free_scribble(nanozone_t *nanozone, void *ptr) { __nano_free(nanozone, ptr, 1); } static size_t nano_size(nanozone_t *nanozone, const void *ptr) { nano_blk_addr_t p; // happily, the compiler holds this in a register p.addr = (uint64_t)ptr; // place ptr on the dissecting table if (NANOZONE_SIGNATURE == p.fields.nano_signature) { // Our signature? return _nano_size(nanozone, ptr); } else { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->size(zone, ptr); // Not nano. Try other sizes. } /* NOTREACHED */ } static void * nano_realloc(nanozone_t *nanozone, void *ptr, size_t new_size) { // could occur through malloc_zone_realloc() path if (!ptr) { // If ptr is a null pointer, realloc() shall be equivalent to malloc() for the specified size. return nano_malloc(nanozone, new_size); } size_t old_size = _nano_vet_and_size_of_live(nanozone, ptr); if (!old_size) { // not-nano pointer, hand down to helper zone malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->realloc(zone, ptr, new_size); } else { if (new_size <= NANO_MAX_SIZE) { // nano to nano? void *q = _nano_realloc(nanozone, ptr, new_size); if (q) { return q; } else { // nano exhausted /* FALLTHROUGH to helper zone copying case */ } } malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); void *new_ptr = zone->malloc(zone, new_size); if (new_ptr) { size_t valid_size = MIN(old_size, new_size); memcpy(new_ptr, ptr, valid_size); _nano_free_check_scribble(nanozone, ptr, (nanozone->debug_flags & MALLOC_DO_SCRIBBLE)); return new_ptr; } else { /* Original ptr is left intact */ return NULL; } /* NOTREACHED */ } /* NOTREACHED */ } static void * nano_forked_realloc(nanozone_t *nanozone, void *ptr, size_t new_size) { // could occur through malloc_zone_realloc() path if (!ptr) { // If ptr is a null pointer, realloc() shall be equivalent to malloc() for the specified size. return nano_forked_malloc(nanozone, new_size); } size_t old_size = _nano_vet_and_size_of_live(nanozone, ptr); if (!old_size) { // not-nano pointer, hand down to helper zone malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->realloc(zone, ptr, new_size); } else { if (0 == new_size) { // If size is 0 and ptr is not a null pointer, the object pointed to is freed. // However as nanozone metadata could be fouled by fork, we'll intentionally leak it. // If size is 0, either a null pointer or a unique pointer that can be successfully passed // to free() shall be returned. return nano_forked_malloc(nanozone, 1); } malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); void *new_ptr = zone->malloc(zone, new_size); if (new_ptr) { size_t valid_size = MIN(old_size, new_size); memcpy(new_ptr, ptr, valid_size); /* Original pointer is intentionally leaked as nanozone metadata could be fouled by fork. */ return new_ptr; } else { /* Original ptr is left intact */ return NULL; } /* NOTREACHED */ } /* NOTREACHED */ } static void nano_destroy(nanozone_t *nanozone) { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); zone->destroy(zone); _nano_destroy(nanozone); } static unsigned nano_batch_malloc(nanozone_t *nanozone, size_t size, void **results, unsigned count) { unsigned found = 0; if (size <= NANO_MAX_SIZE) { while (found < count) { void *ptr = _nano_malloc_check_clear(nanozone, size, 0); if (!ptr) { break; } *results++ = ptr; found++; } if (found == count) { return found; } else { /* FALLTHROUGH to mop-up in the helper zone */ } } malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return found + zone->batch_malloc(zone, size, results, count - found); } static unsigned nano_forked_batch_malloc(nanozone_t *nanozone, size_t size, void **results, unsigned count) { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->batch_malloc(zone, size, results, count); } static void nano_batch_free(nanozone_t *nanozone, void **to_be_freed, unsigned count) { void *ptr; // frees all the pointers in to_be_freed // note that to_be_freed may be overwritten during the process if (!count) { return; } while (count--) { ptr = to_be_freed[count]; if (ptr) { nano_free(nanozone, ptr); } } } static void nano_forked_batch_free(nanozone_t *nanozone, void **to_be_freed, unsigned count) { void *ptr; // frees all the pointers in to_be_freed // note that to_be_freed may be overwritten during the process if (!count) { return; } while (count--) { ptr = to_be_freed[count]; if (ptr) { nano_forked_free(nanozone, ptr); } } } static void * nano_memalign(nanozone_t *nanozone, size_t alignment, size_t size) { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->memalign(zone, alignment, size); } static boolean_t nano_claimed_address(nanozone_t *nanozone, void *ptr) { nano_blk_addr_t p; p.addr = (uint64_t)ptr; if (NANOZONE_SIGNATURE != p.fields.nano_signature) { // Not a nano address - let the helper zone handle it. malloc_zone_t *helper_zone = nanozone->helper_zone; return malloc_zone_claimed_address(helper_zone, ptr); } return __nano_vet_and_size_inner(nanozone, ptr, true) != 0; } static boolean_t nano_forked_claimed_address(struct _malloc_zone_t *zone, void *ptr) { // This does not operate after fork - default to true to avoid // false negatives. return true; } static size_t nano_try_madvise(nanozone_t *nanozone, size_t goal) { unsigned int mag_index, slot_key; size_t bytes_toward_goal = 0; for (mag_index = 0; mag_index < nano_common_max_magazines; mag_index++) { nano_blk_addr_t p; // Establish p as base address for band 0, slot 0, offset 0 p.fields.nano_signature = NANOZONE_SIGNATURE; p.fields.nano_mag_index = mag_index; p.fields.nano_band = 0; p.fields.nano_slot = 0; p.fields.nano_offset = 0; for (slot_key = 0; slot_key < SLOT_KEY_LIMIT; p.addr += SLOT_IN_BAND_SIZE, // Advance to next slot base slot_key++) { // malloc_report(ASL_LEVEL_WARNING,"nano_try_madvise examining slot base %p\n", p.addr); nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]); uintptr_t slot_bump_addr = pMeta->slot_bump_addr; // capture this volatile pointer size_t slot_objects_mapped = pMeta->slot_objects_mapped; // capture this volatile count if (0 == slot_objects_mapped) { // Nothing allocated in this magazine for this slot? continue; } else { // Walk the slot free list and populate a bitarray_t int log_size = 64 - __builtin_clzl(slot_objects_mapped); bitarray_t slot_bitarray = bitarray_create(log_size); unsigned int slot_bytes = pMeta->slot_bytes; int log_page_count = 64 - __builtin_clzl((slot_objects_mapped * slot_bytes) / vm_kernel_page_size); log_page_count = 1 + MAX(0, log_page_count); bitarray_t page_bitarray = bitarray_create(log_page_count); // malloc_report(ASL_LEVEL_WARNING,"slot_bitarray: %db page_bitarray: %db\n", bitarray_size(log_size), // bitarray_size(log_page_count)); if (!slot_bitarray) { malloc_report(ASL_LEVEL_ERR, "bitarray_create(%d) in nano_try_madvise returned errno=%d.\n", log_size, errno); free(page_bitarray); return bytes_toward_goal; } if (!page_bitarray) { malloc_report(ASL_LEVEL_ERR, "bitarray_create(%d) in nano_try_madvise returned errno=%d.\n", log_page_count, errno); free(slot_bitarray); return bytes_toward_goal; } chained_block_t head = NULL, tail = NULL, t; unsigned stoploss = (unsigned)slot_objects_mapped; while ((t = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next)))) { if (0 == stoploss) { malloc_report(ASL_LEVEL_ERR, "Free list walk in nano_try_madvise exceeded object count.\n"); break; } stoploss--; uintptr_t offset = ((uintptr_t)t - p.addr); // offset from beginning of slot index_t block_index = offset_to_index(nanozone, pMeta, offset); // build a simple linked list of the free blocks we're able to obtain if (NULL == head) { head = t; } else { tail->next = t; } tail = t; // take note in a bitarray_t of each free block we're able to obtain (allows fast lookup below) if (block_index < slot_objects_mapped) { bitarray_set(slot_bitarray, log_size, block_index); } } if (tail) { tail->next = NULL; } if (NULL == head) { free(slot_bitarray); free(page_bitarray); continue; } index_t i; nano_blk_addr_t q; size_t pgnum; for (i = (index_t)pMeta->slot_objects_skipped; i < slot_objects_mapped; ++i) { uintptr_t block_offset = index_to_offset(nanozone, pMeta, i); if (p.addr + block_offset >= slot_bump_addr) { break; } if (!bitarray_get(slot_bitarray, log_size, i)) { // is block i allocated or already on an madvise'd page? // Mark the page(s) it resides on as live q.addr = p.addr + block_offset; pgnum = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; bitarray_set(page_bitarray, log_page_count, (index_t)pgnum); q.addr += slot_bytes - 1; pgnum = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; bitarray_set(page_bitarray, log_page_count, (index_t)pgnum); } } free(slot_bitarray); q.addr = p.addr + index_to_offset(nanozone, pMeta, (index_t)pMeta->slot_objects_skipped); index_t pgstart = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; q.addr = slot_bump_addr - slot_bytes; pgnum = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; // malloc_report(ASL_LEVEL_WARNING,"Examining %d pages. Slot base %p.\n", pgnum - pgstart + 1, p.addr); if (pMeta->slot_madvised_pages) { if (pMeta->slot_madvised_log_page_count < log_page_count) { bitarray_t new_madvised_pages = bitarray_create(log_page_count); index_t index; while (bitarray_zap_first_set(pMeta->slot_madvised_pages, pMeta->slot_madvised_log_page_count, &index)) { bitarray_set(new_madvised_pages, log_page_count, index); } free(pMeta->slot_madvised_pages); pMeta->slot_madvised_pages = new_madvised_pages; pMeta->slot_madvised_log_page_count = log_page_count; } } else { pMeta->slot_madvised_pages = bitarray_create(log_page_count); pMeta->slot_madvised_log_page_count = log_page_count; } bitarray_t will_madvise_pages = bitarray_create(log_page_count); int num_advised = 0; for (i = pgstart; i < pgnum; ++i) { if ((i < (1 << log_page_count)) && // bounds check for the bitarray_get()'s that follow. !bitarray_get(pMeta->slot_madvised_pages, log_page_count, i) && // already madvise'd? !bitarray_get(page_bitarray, log_page_count, i)) // no live allocations? { num_advised++; bitarray_set(will_madvise_pages, log_page_count, i); } } free(page_bitarray); if (num_advised) { chained_block_t new_head = NULL, new_tail = NULL; // malloc_report(ASL_LEVEL_WARNING,"Constructing residual free list starting at %p num_advised %d\n", head, // num_advised); t = head; while (t) { q.addr = (uintptr_t)t; index_t pgnum_start = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; q.addr += slot_bytes - 1; index_t pgnum_end = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; // bounds check for the bitarray_get()'s that follow. If the pgnum is beyond the // capacity of the will_madvise_pages just restore the block to the free list. if (pgnum_start >= (1 << log_page_count)) { if (NULL == new_head) { new_head = t; } else { new_tail->next = t; } new_tail = t; } // If the block nowhere lies on an madvise()'d page restore it to the slot free list. else if (!bitarray_get(will_madvise_pages, log_page_count, pgnum_start) && !bitarray_get(will_madvise_pages, log_page_count, pgnum_end)) { if (NULL == new_head) { new_head = t; } else { new_tail->next = t; } new_tail = t; } t = t->next; } if (new_tail) { new_tail->next = NULL; } // push the free list extracted above back onto the LIFO, all at once if (new_head) { OSAtomicEnqueue(&(pMeta->slot_LIFO), new_head, (uintptr_t)new_tail - (uintptr_t)new_head + offsetof(struct chained_block_s, next)); } } else { // malloc_report(ASL_LEVEL_WARNING,"Reinstating free list since no pages were madvised (%d).\n", num_advised); if (head) { OSAtomicEnqueue(&(pMeta->slot_LIFO), head, (uintptr_t)tail - (uintptr_t)head + offsetof(struct chained_block_s, next)); } } for (i = pgstart; i < pgnum; ++i) { if ((i < (1 << log_page_count)) && bitarray_get(will_madvise_pages, log_page_count, i)) { q = p; q.fields.nano_band = (i << vm_kernel_page_shift) >> NANO_OFFSET_BITS; q.fields.nano_offset = (i << vm_kernel_page_shift) & ((1 << NANO_OFFSET_BITS) - 1); // malloc_report(ASL_LEVEL_WARNING,"Entire page non-live: %d. Slot base %p, madvising %p\n", i, p.addr, // q.addr); if (nanozone->debug_flags & MALLOC_DO_SCRIBBLE) { memset((void *)q.addr, SCRUBBLE_BYTE, vm_kernel_page_size); } if (-1 == madvise((void *)q.addr, vm_kernel_page_size, MADV_FREE_REUSABLE)) { /* -1 return: VM map entry change makes this unfit for reuse. Something evil lurks. */ #if DEBUG_MADVISE nanozone_error(nanozone, 0, "madvise(..., MADV_FREE_REUSABLE) failed", (void *)cwq.addrpgLo, "length=%d\n", vm_page_size); #endif } else { bytes_toward_goal += vm_kernel_page_size; bitarray_set(pMeta->slot_madvised_pages, log_page_count, i); } } } free(will_madvise_pages); if (!bitarray_first_set(pMeta->slot_madvised_pages, log_page_count)) { free(pMeta->slot_madvised_pages); pMeta->slot_madvised_pages = NULL; pMeta->slot_madvised_log_page_count = 0; } if (goal && bytes_toward_goal >= goal) { return bytes_toward_goal; } } } } return bytes_toward_goal; } static size_t nano_pressure_relief(nanozone_t *nanozone, size_t goal) { MAGMALLOC_PRESSURERELIEFBEGIN((void *)nanozone, nanozone->basic_zone.zone_name, (int)goal); MALLOC_TRACE(TRACE_nano_memory_pressure | DBG_FUNC_START, (uint64_t)nanozone, goal, 0, 0); size_t total = nano_try_madvise(nanozone, goal); MAGMALLOC_PRESSURERELIEFEND((void *)nanozone, nanozone->basic_zone.zone_name, (int)goal, (int)total); MALLOC_TRACE(TRACE_nano_memory_pressure | DBG_FUNC_END, (uint64_t)nanozone, goal, total, 0); return total; } /**************** introspection methods *********************/ static kern_return_t nano_ptr_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder) { nanozone_t *nanozone; kern_return_t err; struct nanozone_s zone_copy; if (!reader) { reader = nano_common_default_reader; } err = reader(task, zone_address, sizeof(nanozone_t), (void **)&nanozone); if (err) { return err; } memcpy(&zone_copy, nanozone, sizeof(zone_copy)); err = segregated_in_use_enumerator(task, context, type_mask, &zone_copy, reader, recorder); return err; } static size_t nano_good_size(nanozone_t *nanozone, size_t size) { if (size <= NANO_MAX_SIZE) { return _nano_common_good_size(size); } else { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->introspect->good_size(zone, size); } } // TODO sanity checks unsigned nanozone_check_counter = 0; unsigned nanozone_check_start = 0; unsigned nanozone_check_modulo = 1; static boolean_t nano_check_all(nanozone_t *nanozone, const char *function) { return 1; } static boolean_t nanozone_check(nanozone_t *nanozone) { if ((++nanozone_check_counter % 10000) == 0) { malloc_report(ASL_LEVEL_NOTICE, "at nanozone_check counter=%d\n", nanozone_check_counter); } if (nanozone_check_counter < nanozone_check_start) { return 1; } if (nanozone_check_counter % nanozone_check_modulo) { return 1; } return nano_check_all(nanozone, ""); } static unsigned count_free(nanozone_t *nanozone, nano_meta_admin_t pMeta) { chained_block_t head = NULL, tail = NULL, t; unsigned count = 0; unsigned stoploss = (unsigned)pMeta->slot_objects_mapped; while ((t = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next)))) { if (0 == stoploss) { malloc_zone_error(nanozone->debug_flags, true, "Free list walk in count_free exceeded object count.\n", (void *)&(pMeta->slot_LIFO), NULL); } stoploss--; if (NULL == head) { head = t; } else { tail->next = t; } tail = t; count++; } if (tail) { tail->next = NULL; } // push the free list extracted above back onto the LIFO, all at once if (head) { OSAtomicEnqueue(&(pMeta->slot_LIFO), head, (uintptr_t)tail - (uintptr_t)head + offsetof(struct chained_block_s, next)); } return count; } static void nano_print(nanozone_t *nanozone, boolean_t verbose) { unsigned int mag_index, slot_key; malloc_statistics_t stats; nano_statistics(nanozone, &stats); malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Nanozone %p: inUse=%d(%lluKB) touched=%lluKB allocated=%lluMB\n", nanozone, stats.blocks_in_use, (uint64_t)stats.size_in_use >> 10, (uint64_t)stats.max_size_in_use >> 10, (uint64_t)stats.size_allocated >> 20); for (mag_index = 0; mag_index < nano_common_max_magazines; mag_index++) { nano_blk_addr_t p; // Establish p as base address for band 0, slot 0, offset 0 p.fields.nano_signature = NANOZONE_SIGNATURE; p.fields.nano_mag_index = mag_index; p.fields.nano_band = 0; p.fields.nano_slot = 0; p.fields.nano_offset = 0; for (slot_key = 0; slot_key < SLOT_KEY_LIMIT; p.addr += SLOT_IN_BAND_SIZE, // Advance to next slot base slot_key++) { nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]); uintptr_t slot_bump_addr = pMeta->slot_bump_addr; // capture this volatile pointer size_t slot_objects_mapped = pMeta->slot_objects_mapped; // capture this volatile count if (0 == slot_objects_mapped) { // Nothing allocated in this magazine for this slot? malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Magazine %2d(%3d) Unrealized\n", mag_index, (slot_key + 1) << SHIFT_NANO_QUANTUM); continue; } uintptr_t offset = (0 == slot_bump_addr ? 0 : slot_bump_addr - p.addr); unsigned blocks_touched = offset_to_index(nanozone, pMeta, offset) - (unsigned)pMeta->slot_objects_skipped; unsigned blocks_now_free = count_free(nanozone, pMeta); unsigned blocks_in_use = blocks_touched - blocks_now_free; size_t size_hiwater = ((slot_key + 1) << SHIFT_NANO_QUANTUM) * blocks_touched; size_t size_in_use = ((slot_key + 1) << SHIFT_NANO_QUANTUM) * blocks_in_use; size_t size_allocated = ((offset / BAND_SIZE) + 1) * SLOT_IN_BAND_SIZE; malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Magazine %2d(%3d) [%p, %3lluKB] \t Allocations in use=%4d \t Bytes in use=%llub \t Untouched=%lluKB\n", mag_index, (slot_key + 1) << SHIFT_NANO_QUANTUM, (void *)p.addr, (uint64_t)(size_allocated >> 10), blocks_in_use, (uint64_t)size_in_use, (uint64_t)((size_allocated - size_hiwater) >> 10)); if (!verbose) { continue; } else { // Walk the slot free list and populate a bitarray_t int log_size = 64 - __builtin_clzl(slot_objects_mapped); bitarray_t slot_bitarray = bitarray_create(log_size); if (!slot_bitarray) { malloc_report(ASL_LEVEL_ERR, "bitarray_create(%d) in nano_print returned errno=%d.\n", log_size, errno); return; } chained_block_t head = NULL, tail = NULL, t; unsigned stoploss = (unsigned)slot_objects_mapped; while ((t = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next)))) { if (0 == stoploss) { malloc_report(ASL_LEVEL_ERR, "Free list walk in nano_print exceeded object count.\n"); break; } stoploss--; uintptr_t offset = ((uintptr_t)t - p.addr); // offset from beginning of slot index_t block_index = offset_to_index(nanozone, pMeta, offset); if (NULL == head) { head = t; } else { tail->next = t; } tail = t; if (block_index < slot_objects_mapped) { bitarray_set(slot_bitarray, log_size, block_index); } } if (tail) { tail->next = NULL; } index_t i; for (i = 0; i < slot_objects_mapped; ++i) { nano_blk_addr_t q; size_t pgnum; uintptr_t block_offset = index_to_offset(nanozone, pMeta, i); if (p.addr + block_offset >= slot_bump_addr) { break; } q.addr = p.addr + block_offset; pgnum = ((((unsigned)q.fields.nano_band) << NANO_OFFSET_BITS) | ((unsigned)q.fields.nano_offset)) >> vm_kernel_page_shift; if (i < pMeta->slot_objects_skipped) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "_"); } else if (bitarray_get(slot_bitarray, log_size, i)) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "F"); } else if (pMeta->slot_madvised_pages && (pgnum < (1 << pMeta->slot_madvised_log_page_count)) && bitarray_get(pMeta->slot_madvised_pages, pMeta->slot_madvised_log_page_count, (index_t)pgnum)) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "M"); } else { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "."); } } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "\n"); free(slot_bitarray); // push the free list extracted above back onto the LIFO, all at once if (head) { OSAtomicEnqueue( &(pMeta->slot_LIFO), head, (uintptr_t)tail - (uintptr_t)head + offsetof(struct chained_block_s, next)); } } } } return; } static void nano_log(malloc_zone_t *zone, void *log_address) { } static void nano_force_lock(nanozone_t *nanozone) { int i; for (i = 0; i < nano_common_max_magazines; ++i) { _malloc_lock_lock(&nanozone->band_resupply_lock[i]); } } static void nano_force_unlock(nanozone_t *nanozone) { int i; for (i = 0; i < nano_common_max_magazines; ++i) { _malloc_lock_unlock(&nanozone->band_resupply_lock[i]); } } static void nano_reinit_lock(nanozone_t *nanozone) { int i; for (i = 0; i < nano_common_max_magazines; ++i) { _malloc_lock_init(&nanozone->band_resupply_lock[i]); } } static void nano_statistics(nanozone_t *nanozone, malloc_statistics_t *stats) { int i, j; bzero(stats, sizeof(*stats)); for (i = 0; i < nano_common_max_magazines; ++i) { nano_blk_addr_t p; // Establish p as base address for slot 0 in this CPU magazine p.fields.nano_signature = NANOZONE_SIGNATURE; p.fields.nano_mag_index = i; p.fields.nano_band = 0; p.fields.nano_slot = 0; p.fields.nano_offset = 0; for (j = 0; j < NANO_SLOT_SIZE; p.addr += SLOT_IN_BAND_SIZE, // Advance to next slot base ++j) { nano_meta_admin_t pMeta = &nanozone->meta_data[i][j]; uintptr_t offset = pMeta->slot_bump_addr - p.addr; if (0 == pMeta->slot_current_base_addr) { // Nothing allocated in this magazine for this slot? continue; } else { unsigned blocks_touched = offset_to_index(nanozone, pMeta, offset) - (unsigned)pMeta->slot_objects_skipped; unsigned blocks_now_free = count_free(nanozone, pMeta); unsigned blocks_in_use = blocks_touched - blocks_now_free; size_t size_hiwater = ((j + 1) << SHIFT_NANO_QUANTUM) * blocks_touched; size_t size_in_use = ((j + 1) << SHIFT_NANO_QUANTUM) * blocks_in_use; size_t size_allocated = ((offset / BAND_SIZE) + 1) * SLOT_IN_BAND_SIZE; stats->blocks_in_use += blocks_in_use; stats->max_size_in_use += size_hiwater; stats->size_in_use += size_in_use; stats->size_allocated += size_allocated; } } } } static boolean_t _nano_locked(nanozone_t *nanozone) { int i; for (i = 0; i < nano_common_max_magazines; ++i) { if (_malloc_lock_trylock(&nanozone->band_resupply_lock[i])) { _malloc_lock_unlock(&nanozone->band_resupply_lock[i]); return TRUE; } } return FALSE; } static boolean_t nano_locked(nanozone_t *nanozone) { malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return _nano_locked(nanozone) || zone->introspect->zone_locked(zone); } static const struct malloc_introspection_t nano_introspect = { (void *)nano_ptr_in_use_enumerator, (void *)nano_good_size, (void *)nanozone_check, (void *)nano_print, nano_log, (void *)nano_force_lock, (void *)nano_force_unlock, (void *)nano_statistics, (void *)nano_locked, NULL, NULL, NULL, NULL, /* Zone enumeration version 7 and forward. */ (void *)nano_reinit_lock, // reinit_lock version 9 and foward }; // marked as const to spare the DATA section void nano_forked_zone(nanozone_t *nanozone) { /* * Hobble the nano zone in the child of a fork prior to an exec since * the state of the zone can be made inconsistent by a parent thread while the * fork is underway. * All new allocations will be referred to the helper zone (which is more stable.) * All free()'s of existing nano objects will be leaked. */ mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ | PROT_WRITE); nanozone->basic_zone.size = (void *)nano_size; /* Unchanged. */ nanozone->basic_zone.malloc = (void *)nano_forked_malloc; nanozone->basic_zone.calloc = (void *)nano_forked_calloc; nanozone->basic_zone.valloc = (void *)nano_valloc; /* Unchanged, already always obtained from helper zone. */ nanozone->basic_zone.free = (void *)nano_forked_free; nanozone->basic_zone.realloc = (void *)nano_forked_realloc; nanozone->basic_zone.destroy = (void *)nano_destroy; /* Unchanged. */ nanozone->basic_zone.batch_malloc = (void *)nano_forked_batch_malloc; nanozone->basic_zone.batch_free = (void *)nano_forked_batch_free; nanozone->basic_zone.introspect = (struct malloc_introspection_t *)&nano_introspect; /* Unchanged. */ nanozone->basic_zone.memalign = (void *)nano_memalign; /* Unchanged. */ nanozone->basic_zone.free_definite_size = (void *)nano_forked_free_definite_size; nanozone->basic_zone.claimed_address = nano_forked_claimed_address; mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ); } malloc_zone_t * nano_create_zone(malloc_zone_t *helper_zone, unsigned debug_flags) { nanozone_t *nanozone; int i, j; /* Note: It is important that nano_create_zone resets _malloc_engaged_nano * if it is unable to enable the nanozone (and chooses not to abort). As * several functions rely on _malloc_engaged_nano to determine if they * should manipulate the nanozone, and these should not run if we failed * to create the zone. */ MALLOC_ASSERT(_malloc_engaged_nano == NANO_V1); /* get memory for the zone. */ nanozone = nano_common_allocate_based_pages(NANOZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC, 0); if (!nanozone) { _malloc_engaged_nano = NANO_NONE; return NULL; } /* set up the basic_zone portion of the nanozone structure */ nanozone->basic_zone.version = 10; nanozone->basic_zone.size = (void *)nano_size; nanozone->basic_zone.malloc = (debug_flags & MALLOC_DO_SCRIBBLE) ? (void *)nano_malloc_scribble : (void *)nano_malloc; nanozone->basic_zone.calloc = (void *)nano_calloc; nanozone->basic_zone.valloc = (void *)nano_valloc; nanozone->basic_zone.free = (debug_flags & MALLOC_DO_SCRIBBLE) ? (void *)nano_free_scribble : (void *)nano_free; nanozone->basic_zone.realloc = (void *)nano_realloc; nanozone->basic_zone.destroy = (void *)nano_destroy; nanozone->basic_zone.batch_malloc = (void *)nano_batch_malloc; nanozone->basic_zone.batch_free = (void *)nano_batch_free; nanozone->basic_zone.introspect = (struct malloc_introspection_t *)&nano_introspect; nanozone->basic_zone.memalign = (void *)nano_memalign; nanozone->basic_zone.free_definite_size = (debug_flags & MALLOC_DO_SCRIBBLE) ? (void *)nano_free_definite_size_scribble : (void *)nano_free_definite_size; nanozone->basic_zone.pressure_relief = (void *)nano_pressure_relief; nanozone->basic_zone.claimed_address = (void *)nano_claimed_address; nanozone->basic_zone.reserved1 = 0; /* Set to zero once and for all as required by CFAllocator. */ nanozone->basic_zone.reserved2 = 0; /* Set to zero once and for all as required by CFAllocator. */ mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ); /* Prevent overwriting the function pointers in basic_zone. */ /* Nano zone does not support MALLOC_ADD_GUARD_PAGES. */ if (debug_flags & MALLOC_ADD_GUARD_PAGES) { malloc_report(ASL_LEVEL_INFO, "nano zone does not support guard pages\n"); debug_flags &= ~MALLOC_ADD_GUARD_PAGES; } /* set up the remainder of the nanozone structure */ nanozone->debug_flags = debug_flags; if (phys_ncpus > sizeof(nanozone->core_mapped_size) / sizeof(nanozone->core_mapped_size[0])) { MALLOC_REPORT_FATAL_ERROR(phys_ncpus, "nanozone abandoned because NCPUS > max magazines.\n"); } /* Initialize slot queue heads and resupply locks. */ OSQueueHead q0 = OS_ATOMIC_QUEUE_INIT; for (i = 0; i < nano_common_max_magazines; ++i) { _malloc_lock_init(&nanozone->band_resupply_lock[i]); for (j = 0; j < NANO_SLOT_SIZE; ++j) { nanozone->meta_data[i][j].slot_LIFO = q0; } } /* Initialize the security token. */ nanozone->cookie = (uintptr_t)malloc_entropy[0] & 0x0000ffffffff0000ULL; // scramble central 32bits with this cookie nanozone->helper_zone = helper_zone; return (malloc_zone_t *)nanozone; } void nano_init(const char *envp[], const char *apple[], const char *bootargs MALLOC_UNUSED) { #if NANO_PREALLOCATE_BAND_VM // Unconditionally preallocate the VA space set aside for nano malloc to // reserve it in all configurations. rdar://problem/33392283 boolean_t preallocated = nano_preallocate_band_vm(); if (!preallocated) { malloc_report(ASL_LEVEL_NOTICE, "nano zone abandoned due to inability to preallocate reserved vm space.\n"); _malloc_engaged_nano = NANO_NONE; } #endif } // Second phase of initialization, called during _malloc_initialize(), after // environment variables have been read and processed. void nano_configure() { // Nothing to do. } #endif // CONFIG_NANOZONE /* vim: set noet:ts=4:sw=4:cindent: */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nano_malloc.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __NANO_MALLOC_H #define __NANO_MALLOC_H // Forward decl for the nanozone. typedef struct nanozone_s nanozone_t; MALLOC_NOEXPORT malloc_zone_t * nano_create_zone(malloc_zone_t *helper_zone, unsigned debug_flags); MALLOC_NOEXPORT void nano_forked_zone(nanozone_t *nanozone); MALLOC_NOEXPORT void nano_init(const char *envp[], const char *apple[], const char *bootargs); MALLOC_NOEXPORT void nano_configure(void); #endif // __NANO_MALLOC_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nano_malloc_common.c ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" // Code that is common to Nano V1 and Nano V2. When Nano V1 is removed, // most of this file will move to nanov2_malloc.c. #if CONFIG_NANOZONE // Possible enablement modes for Nano V2 typedef enum { NANO_INACTIVE, // Inactive, but can be selected with MallocNanoZone=V2 NANO_ENABLED, // Available and default if Nano is turned on. NANO_FORCED, // Force use of Nano V2 for all processes. } nanov2_mode_t; // Which version of Nano is engaged. By default, none. nano_version_t _malloc_engaged_nano = NANO_NONE; // Nano mode selection boot argument static const char mode_boot_arg[] = "nanov2_mode"; static const char inactive_mode[] = "inactive"; // Use Nano V1 for Nano static const char enabled_mode[] = "enabled"; // Use Nano V2 for Nano static const char forced_mode[] = "forced"; // Force Nano V2 everywhere // The maximum number of per-CPU allocation regions to use for Nano. unsigned int nano_common_max_magazines; boolean_t nano_common_max_magazines_is_ncpu; // Boot argument for nano_common_max_magazines static const char nano_max_magazines_boot_arg[] = "malloc_nano_max_magazines"; #pragma mark - #pragma mark Initialization // Shared initialization code. Determines which version of Nano should be used, // if any, and sets _malloc_engaged_nano. The Nano version is determined as // follows: // 1. If the nanov2_mode boot arg has value "forced", Nano V2 is used // unconditionally in every process, except in processes that have // the MallocNanoZone variable set to V1. // 2. If the nanov2_mode boot arg has value "enabled", Nano V2 is used if // the process wants to use Nano (i.e. the kernel opts the process in, or // the environment variable MallocNanoZone is 1). // 3. If the nanov2_mode boot arg is not present or has any other value, // Nano V1 is used if the process wants to use Nano (i.e. the kernel opts // the process in, or the environment variable MallocNanoZone is 1). // // In cases (2) and (3), the selection can be explicitly overridden by setting // the environment variable MallocNanoZone to V1 or V2. void nano_common_init(const char *envp[], const char *apple[], const char *bootargs) { // Use the nanov2_mode boot argument and MallocNanoZone to determine // which version of Nano to use, if any. nanov2_mode_t nanov2_mode = NANO_ENABLED; const char *p = malloc_common_value_for_key(bootargs, mode_boot_arg); if (p) { if (!strncmp(p, inactive_mode, sizeof(inactive_mode) - 1)) { nanov2_mode = NANO_INACTIVE; } else if (!strncmp(p, enabled_mode, sizeof(enabled_mode) - 1)) { nanov2_mode = NANO_ENABLED; } else if (!strncmp(p, forced_mode, sizeof(forced_mode) - 1)) { nanov2_mode = NANO_FORCED; } } if (nanov2_mode == NANO_FORCED) { // We will use Nano V2 unless MallocNanoZone is "V1". const char *flag = _simple_getenv(envp, "MallocNanoZone"); if (flag && (flag[0] == 'V' || flag[0] == 'v') && flag[1] == '1') { _malloc_engaged_nano = NANO_V1; } else { _malloc_engaged_nano = NANO_V2; } } else { const char *flag = _simple_getenv(apple, "MallocNanoZone"); if (flag && flag[0] == '1') { _malloc_engaged_nano = nanov2_mode == NANO_ENABLED ? NANO_V2 : NANO_V1; } /* Explicit overrides from the environment */ flag = _simple_getenv(envp, "MallocNanoZone"); if (flag) { if (flag[0] == '1') { _malloc_engaged_nano = nanov2_mode == NANO_ENABLED ? NANO_V2 : NANO_V1; } else if (flag[0] == '0') { _malloc_engaged_nano = NANO_NONE; } else if (flag[0] == 'V' || flag[0] == 'v') { if (flag[1] == '1') { _malloc_engaged_nano = NANO_V1; } else if (flag[1] == '2') { _malloc_engaged_nano = NANO_V2; } } } } if (_malloc_engaged_nano) { // The maximum number of nano magazines can be set either via a // boot argument or from the environment. Get the boot argument value // here and store it. We can't bounds check it until we have phys_ncpus, // which happens later in nano_common_configure(), along with handling // of the environment value setting. char value_buf[256]; const char *flag = malloc_common_value_for_key_copy(bootargs, nano_max_magazines_boot_arg, value_buf, sizeof(value_buf)); if (flag) { const char *endp; long value = malloc_common_convert_to_long(flag, &endp); if (!*endp && value >= 0) { nano_common_max_magazines = (unsigned int)value; } else { malloc_report(ASL_LEVEL_ERR, "malloc_nano_max_magazines must be positive - ignored.\n"); } } } switch (_malloc_engaged_nano) { case NANO_V1: nano_init(envp, apple, bootargs); break; case NANO_V2: nanov2_init(envp, apple, bootargs); break; default: break; } } // Second phase of initialization, called from _malloc_initialize(). Used for // code that depends on state set in _malloc_initialize(), such as the // number of physical CPUs. void nano_common_configure(void) { // Set nano_common_max_magazines. An initial (unvalidated) value may have // been set from the boot args. unsigned int magazines = nano_common_max_magazines > 0 ? nano_common_max_magazines : phys_ncpus; // Environment variable overrides boot arg, unless it's not valid. const char *flag = getenv("MallocNanoMaxMagazines"); if (flag) { int value = (int)strtol(flag, NULL, 0); if (value < 0) { malloc_report(ASL_LEVEL_ERR, "MallocNanoMaxMagazines must be positive - ignored.\n"); } else { magazines = value; } } if (magazines == 0) { magazines = phys_ncpus; } else if (magazines > phys_ncpus) { magazines = phys_ncpus; malloc_report(ASL_LEVEL_ERR, "Nano maximum magazines limited to number of physical " "CPUs [%d]\n", phys_ncpus); } nano_common_max_magazines = magazines; if (flag) { malloc_report(ASL_LEVEL_INFO, "Nano maximum magazines set to %d\n", nano_common_max_magazines); } nano_common_cpu_number_override_set(); switch (_malloc_engaged_nano) { case NANO_V1: nano_configure(); break; case NANO_V2: nanov2_configure(); break; default: break; } } #pragma mark - #pragma mark VM Helper Functions void * nano_common_allocate_based_pages(size_t size, unsigned char align, unsigned debug_flags, int vm_page_label, void *base_addr) { mach_vm_address_t vm_addr; uintptr_t addr; mach_vm_size_t allocation_size = round_page(size); mach_vm_offset_t allocation_mask = ((mach_vm_offset_t)1 << align) - 1; int alloc_flags = VM_FLAGS_ANYWHERE | VM_MAKE_TAG(vm_page_label); kern_return_t kr; if (!allocation_size) { allocation_size = vm_page_size; } if (allocation_size < size) { // size_t arithmetic wrapped! return NULL; } vm_addr = round_page((mach_vm_address_t)base_addr); if (!vm_addr) { vm_addr = vm_page_size; } kr = mach_vm_map(mach_task_self(), &vm_addr, allocation_size, allocation_mask, alloc_flags, MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT); if (kr) { malloc_zone_error(debug_flags, false, "*** can't allocate pages: " "mach_vm_map(size=%lu) failed (error code=%d)\n", size, kr); return NULL; } addr = (uintptr_t)vm_addr; return (void *)addr; } // Allocates virtual address from a given address for a given size. Succeeds // (and returns TRUE) only if we get exactly the range of addresses that we // asked for. boolean_t nano_common_allocate_vm_space(mach_vm_address_t base, mach_vm_size_t size) { mach_vm_address_t vm_addr = base; kern_return_t kr = mach_vm_map(mach_task_self(), &vm_addr, size, 0, VM_MAKE_TAG(VM_MEMORY_MALLOC_NANO), MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT); if (kr != KERN_SUCCESS || vm_addr != base) { // Failed or we got allocated somewhere else. if (!kr) { mach_vm_deallocate(mach_task_self(), vm_addr, size); } return FALSE; } return TRUE; } void nano_common_deallocate_pages(void *addr, size_t size, unsigned debug_flags) { mach_vm_address_t vm_addr = (mach_vm_address_t)addr; mach_vm_size_t allocation_size = size; kern_return_t kr; kr = mach_vm_deallocate(mach_task_self(), vm_addr, allocation_size); if (kr) { malloc_zone_error(debug_flags, false, "Can't deallocate_pages at %p\n", addr); } } #pragma mark - #pragma mark Introspection Helper Functions kern_return_t nano_common_default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr) { *ptr = (void *)address; return 0; } #pragma mark - #pragma mark Utility functions void nano_common_cpu_number_override_set() { // This facilitates a shortcut in nanov2_get_allocation_block_index() -- // if nano_common_max_magazines_is_ncpu is true, we can also assume that // _os_cpu_number_override == -1 (i.e. we are not in malloc_replay). nano_common_max_magazines_is_ncpu = _os_cpu_number_override == -1 && nano_common_max_magazines == phys_ncpus; } #endif // CONFIG_NANOZONE ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nano_malloc_common.h ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __NANO_MALLOC_COMMON_H #define __NANO_MALLOC_COMMON_H // Definitions that are common to Nano V1 and Nano V2. #if TARGET_OS_OSX || TARGET_OS_SIMULATOR #define NANO_PREALLOCATE_BAND_VM 0 #else // TARGET_OS_OSX || TARGET_OS_SIMULATOR #define NANO_PREALLOCATE_BAND_VM 1 // pre-allocate reserved vm range #endif // TARGET_OS_OSX || TARGET_OS_SIMULATOR typedef enum { NANO_NONE = 0, NANO_V1 = 1, NANO_V2 = 2, } nano_version_t; // Nano malloc enabled flag MALLOC_NOEXPORT extern nano_version_t _malloc_engaged_nano; // The maximum number of per-CPU allocation regions to use for Nano. MALLOC_NOEXPORT extern unsigned int nano_common_max_magazines; MALLOC_NOEXPORT extern boolean_t nano_common_max_magazines_is_ncpu; MALLOC_NOEXPORT void nano_common_cpu_number_override_set(void); MALLOC_NOEXPORT void nano_common_init(const char *envp[], const char *apple[], const char *bootargs); MALLOC_NOEXPORT void nano_common_configure(void); MALLOC_NOEXPORT void * nano_common_allocate_based_pages(size_t size, unsigned char align, unsigned debug_flags, int vm_page_label, void *base_addr); MALLOC_NOEXPORT boolean_t nano_common_allocate_vm_space(mach_vm_address_t base, mach_vm_size_t size); MALLOC_NOEXPORT void nano_common_deallocate_pages(void *addr, size_t size, unsigned debug_flags); MALLOC_NOEXPORT kern_return_t nano_common_default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr); #endif // __NANO_MALLOC_COMMON_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nano_zone.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __NANO_ZONE_H #define __NANO_ZONE_H #if CONFIG_NANOZONE /********************* DEFINITIONS ************************/ #define MAX_RECORDER_BUFFER 256 /************* nanozone address field layout ******************/ #if defined(__x86_64) #define NANO_MAG_BITS 6 #define NANO_BAND_BITS 17 #define NANO_SLOT_BITS 4 #define NANO_OFFSET_BITS 17 #else #error Unknown Architecture #endif // clang-format really dislikes the bitfields here // clang-format off #if defined(__BIG_ENDIAN__) struct nano_blk_addr_s { uint64_t nano_signature:NANOZONE_SIGNATURE_BITS, // the address range devoted to us. nano_mag_index:NANO_MAG_BITS, // the core that allocated this block nano_band:NANO_BAND_BITS, nano_slot:NANO_SLOT_BITS, // bucket of homogenous quanta-multiple blocks nano_offset:NANO_OFFSET_BITS; // locates the block }; #else // least significant bits declared first struct nano_blk_addr_s { uint64_t nano_offset:NANO_OFFSET_BITS, // locates the block nano_slot:NANO_SLOT_BITS, // bucket of homogenous quanta-multiple blocks nano_band:NANO_BAND_BITS, nano_mag_index:NANO_MAG_BITS, // the core that allocated this block nano_signature:NANOZONE_SIGNATURE_BITS; // the address range devoted to us. }; #endif // clang-format on typedef union { uint64_t addr; struct nano_blk_addr_s fields; } nano_blk_addr_t; #define SLOT_IN_BAND_SIZE (1 << NANO_OFFSET_BITS) #define SLOT_KEY_LIMIT (1 << NANO_SLOT_BITS) /* Must track nano_slot width */ #define BAND_SIZE (1 << (NANO_SLOT_BITS + NANO_OFFSET_BITS)) /* == Number of bytes covered by a page table entry */ #define NANO_MAG_SIZE (1 << NANO_MAG_BITS) #define NANO_SLOT_SIZE (1 << NANO_SLOT_BITS) #ifdef __INTERNAL_H /****************************** zone itself ***********************************/ /* * Note that objects whose adddress are held in pointers here must be pursued * individually in the nano_in_use_enumeration() routines. */ typedef struct chained_block_s { uintptr_t double_free_guard; struct chained_block_s *next; } *chained_block_t; typedef struct nano_meta_s { OSQueueHead slot_LIFO MALLOC_NANO_CACHE_ALIGN; unsigned int slot_madvised_log_page_count; volatile uintptr_t slot_current_base_addr; volatile uintptr_t slot_limit_addr; volatile size_t slot_objects_mapped; volatile size_t slot_objects_skipped; bitarray_t slot_madvised_pages; // position on cache line distinct from that of slot_LIFO volatile uintptr_t slot_bump_addr MALLOC_NANO_CACHE_ALIGN; volatile boolean_t slot_exhausted; unsigned int slot_bytes; unsigned int slot_objects; } *nano_meta_admin_t; // vm_allocate()'d, so page-aligned to begin with. typedef struct nanozone_s { // first page will be given read-only protection malloc_zone_t basic_zone; uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)]; // remainder of structure is R/W (contains no function pointers) // page-aligned // max: NANO_MAG_SIZE cores x NANO_SLOT_SIZE slots for nano blocks {16 .. 256} struct nano_meta_s meta_data[NANO_MAG_SIZE][NANO_SLOT_SIZE]; _malloc_lock_s band_resupply_lock[NANO_MAG_SIZE]; uintptr_t band_max_mapped_baseaddr[NANO_MAG_SIZE]; size_t core_mapped_size[NANO_MAG_SIZE]; unsigned debug_flags; /* security cookie */ uintptr_t cookie; /* * The nano zone constructed by create_nano_zone() would like to hand off tiny, small, and large * allocations to the default scalable zone. Record the latter as the "helper" zone here. */ malloc_zone_t *helper_zone; } nanozone_t; #define NANOZONE_PAGED_SIZE ((sizeof(nanozone_t) + vm_page_size - 1) & ~ (vm_page_size - 1)) #endif // __INTERNAL_H #endif // CONFIG_NANOZONE #endif // __NANO_ZONE_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nano_zone_common.h ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __NANO_ZONE_COMMON_H #define __NANO_ZONE_COMMON_H #define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */ #define SHIFT_NANO_QUANTUM 4 #define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16 #define NANO_QUANTA_MASK (NANO_REGIME_QUANTA_SIZE - 1) #define NANO_SIZE_CLASSES (NANO_MAX_SIZE/NANO_REGIME_QUANTA_SIZE) #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR // Nanozone follows the shared region. #define SHIFT_NANO_SIGNATURE 29 #define NANOZONE_SIGNATURE_BITS 35 #define NANOZONE_BASE_REGION_ADDRESS (SHARED_REGION_BASE + SHARED_REGION_SIZE) #define NANOZONE_SIGNATURE (NANOZONE_BASE_REGION_ADDRESS >> SHIFT_NANO_SIGNATURE) #else // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR #define SHIFT_NANO_SIGNATURE 44 #define NANOZONE_SIGNATURE_BITS 20 #define NANOZONE_SIGNATURE 0x6ULL #define NANOZONE_BASE_REGION_ADDRESS (NANOZONE_SIGNATURE << SHIFT_NANO_SIGNATURE) #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR static MALLOC_INLINE size_t _nano_common_good_size(size_t size) { return (size <= NANO_REGIME_QUANTA_SIZE) ? NANO_REGIME_QUANTA_SIZE : (((size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM) << SHIFT_NANO_QUANTUM); } #endif // __NANO_ZONE_COMMON_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nanov2_malloc.c ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "resolver.h" #include "internal.h" #if CONFIG_NANOZONE #pragma mark - #pragma mark Forward Declarations #if OS_VARIANT_NOTRESOLVED static void nanov2_statistics(nanozonev2_t *nanozone, malloc_statistics_t *stats); #endif // OS_VARIANT_NOTRESOLVED #pragma mark - #pragma mark Externals for resolved functions extern void *nanov2_allocate(nanozonev2_t *nanozone, size_t rounded_size, boolean_t clear); extern void nanov2_free_to_block(nanozonev2_t *nanozone, void *ptr, nanov2_size_class_t size_class); extern boolean_t nanov2_madvise_block(nanozonev2_t *nanozone, nanov2_block_meta_t *block_metap, nanov2_block_t *blockp, nanov2_size_class_t size_class); extern size_t nanov2_pointer_size(nanozonev2_t *nanozone, void *ptr, boolean_t allow_inner); extern size_t nanov2_pressure_relief(nanozonev2_t *nanozone, size_t goal); #if OS_VARIANT_RESOLVED extern boolean_t nanov2_allocate_new_region(nanozonev2_t *nanozone); #endif // OS_VARIANT_RESOLVED #pragma mark - #pragma mark Global Allocator State // -- Block scanning typedef enum { NANO_SCAN_FIRST_FIT = 0, NANO_SCAN_CAPACITY_BASED, } nanov2_block_scan_policy_t; // Minimum occupancy percentage for an ideal block. #define DEFAULT_SCAN_MIN_CAPACITY 20 // Maximum occupancy percentage for an ideal block. #define DEFAULT_SCAN_MAX_CAPACITY 80 // Maximum number of blocks to scan while looking for a best fit once a // candidate block has been found. Value 0 means no limit. #define DEFAULT_SCAN_LIMIT 10 // -- Madvise policy typedef enum { NANO_MADVISE_IMMEDIATE = 0, NANO_MADVISE_WARNING_PRESSURE, NANO_MADVISE_CRITICAL_PRESSURE, } nanov2_madvise_policy_t; typedef struct nanov2_policy_config_s { // Determines the algorithm for scanning for the next allocation block. // Used in conjunction with nanov2_block_scan_capacity_min, // nanov2_block_scan_capacity_max and nanov2_block_scan_limit. Set from the // MallocNanoScanPolicy environment variable. nanov2_block_scan_policy_t block_scan_policy; // Minimum occupancy percentage for an ideal block. int block_scan_min_capacity; // Maximum occupancy percentage for an ideal block. int block_scan_max_capacity; // Maximum number of blocks to scan while looking for a best fit once a // candidate block has been found. Value 0 means no limit. int block_scan_limit; // Bitmask for size classes that are only allowed a single arena. Set from // the MallocNanoSingleArena environment variable. uint16_t single_arena_size_classes; // Madvise policy. Set from the MallocNanoMadvisePolicy environment variable nanov2_madvise_policy_t madvise_policy; } nanov2_policy_config_t; #if OS_VARIANT_NOTRESOLVED // Madvise policy. Set from the MallocNanoMadvisePolicy environment variable. nanov2_madvise_policy_t nanov2_madvise_policy; nanov2_policy_config_t nanov2_policy_config = { .block_scan_policy = NANO_SCAN_CAPACITY_BASED, .block_scan_min_capacity = DEFAULT_SCAN_MIN_CAPACITY, .block_scan_max_capacity = DEFAULT_SCAN_MAX_CAPACITY, .block_scan_limit = DEFAULT_SCAN_LIMIT, .single_arena_size_classes = 0, .madvise_policy = NANO_MADVISE_IMMEDIATE, }; #else // OS_VARIANT_NOTRESOLVED extern nanov2_policy_config_t nanov2_policy_config; extern nanov2_madvise_policy_t nanov2_madvise_policy; #endif // OS_VARIANT_NOTRESOLVED // BLOCKS_PER_UNIT must be a power of two to make it possible to get the size // class from a pointer reasonably cheaply. Do not change the value without // fixing the code that depends on it. #define BLOCKS_PER_UNIT_SHIFT 6 #define BLOCKS_PER_UNIT (1 << BLOCKS_PER_UNIT_SHIFT) #if OS_VARIANT_NOTRESOLVED // Number of units of each size class in an arena. The numbers here must add // up to 64. One unit corresponds to BLOCKS_PER_UNIT blocks in the corresponding // size class, so 64 units maps to a total of 64 * 64 = 4096 blocks and each // block is 16K, making a total of 64MB, which is the size of an arena. int block_units_by_size_class[] = { 2, // 16-byte allocations (less 1 for the metadata block) 10, // 32-byte allocations 11, // 48-byte allocations 10, // 64-byte allocations 5, // 80-byte allocations 3, // 96-byte allocations 3, // 112-byte allocations 4, // 128-byte allocations 3, // 144-byte allocations 2, // 160-byte allocations 2, // 176-byte allocations 2, // 192-byte allocations 2, // 208-byte allocations 2, // 224-byte allocations 1, // 240-byte allocations 2, // 256-byte allocations }; MALLOC_STATIC_ASSERT( sizeof(block_units_by_size_class)/sizeof(block_units_by_size_class[0]) == NANO_SIZE_CLASSES, "Size of block_units_by_size_class is incorrect"); // Total of the number of blocks in all size classes. Currently this is 64. #define TOTAL_BLOCK_UNITS (NANOV2_BLOCKS_PER_ARENA/BLOCKS_PER_UNIT) // Offsets to the first and last blocks for each size class within an arena, in // the logical address space. These tables are constructed from the values in // the block_units_by_size_class table. int first_block_offset_by_size_class[NANO_SIZE_CLASSES]; int last_block_offset_by_size_class[NANO_SIZE_CLASSES]; // Table mapping the part of a logical address that depends on size class to // the size class. Also built from the block_units_by_size_class table. int ptr_offset_to_size_class[TOTAL_BLOCK_UNITS]; // Number of slots in a block, indexed by size class. Note that there is a small // amount of wastage in some size classes because the block size is not always // exactly divisible by the allocation size. The number of wasted bytes is shown // in parentheses in the comments below. const int slots_by_size_class[] = { NANOV2_BLOCK_SIZE/(1 * NANO_REGIME_QUANTA_SIZE), // 16 bytes: 1024 (0) NANOV2_BLOCK_SIZE/(2 * NANO_REGIME_QUANTA_SIZE), // 32 bytes: 512 (0) NANOV2_BLOCK_SIZE/(3 * NANO_REGIME_QUANTA_SIZE), // 48 bytes: 341 (16) NANOV2_BLOCK_SIZE/(4 * NANO_REGIME_QUANTA_SIZE), // 64 bytes: 256 (0) NANOV2_BLOCK_SIZE/(5 * NANO_REGIME_QUANTA_SIZE), // 80 bytes: 204 (64) NANOV2_BLOCK_SIZE/(6 * NANO_REGIME_QUANTA_SIZE), // 96 bytes: 170 (64) NANOV2_BLOCK_SIZE/(7 * NANO_REGIME_QUANTA_SIZE), // 112 bytes: 146 (32) NANOV2_BLOCK_SIZE/(8 * NANO_REGIME_QUANTA_SIZE), // 128 bytes: 128 (0) NANOV2_BLOCK_SIZE/(9 * NANO_REGIME_QUANTA_SIZE), // 144 bytes: 113 (112) NANOV2_BLOCK_SIZE/(10 * NANO_REGIME_QUANTA_SIZE), // 160 bytes: 102 (64) NANOV2_BLOCK_SIZE/(11 * NANO_REGIME_QUANTA_SIZE), // 176 bytes: 93 (16) NANOV2_BLOCK_SIZE/(12 * NANO_REGIME_QUANTA_SIZE), // 192 bytes: 85 (64) NANOV2_BLOCK_SIZE/(13 * NANO_REGIME_QUANTA_SIZE), // 208 bytes: 78 (160) NANOV2_BLOCK_SIZE/(14 * NANO_REGIME_QUANTA_SIZE), // 224 bytes: 73 (32) NANOV2_BLOCK_SIZE/(15 * NANO_REGIME_QUANTA_SIZE), // 240 bytes: 68 (64) NANOV2_BLOCK_SIZE/(16 * NANO_REGIME_QUANTA_SIZE), // 256 bytes: 64 (0) }; #else // OS_VARIANT_NOTRESOLVED extern int block_units_by_size_class[]; extern int ptr_offset_to_size_class[]; extern int first_block_offset_by_size_class[]; extern int last_block_offset_by_size_class[]; extern const int slots_by_size_class[]; #endif // OS_VARIANT_NOTRESOLVED #pragma mark - #pragma mark Conversion and Mapping Inlines // nanov2_block_index_to_meta_index() and nanov2_meta_index_to_block_index() // map between the index of a block in its arena and the index of the meta data // header for that block in the metadata block. The mapping is not direct // to avoid false sharing caused by CPUs that are using adjacent blocks // writing to what would otherwise be adjacent meta data headers. The effect of // these functions is to separate the meta data headers for adjacent blocks by // at least the size of a cache line (assumed to be 64 bytes). static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_meta_index_t nanov2_block_index_to_meta_index(nanov2_block_index_t block_index) { return ((block_index >> 6) | (block_index << 6)) & 0xFFF; } static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_index_t nanov2_meta_index_to_block_index(nanov2_meta_index_t block_meta_index) { return ((block_meta_index >> 6) | (block_meta_index << 6)) & 0xFFF; } static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_meta_index_t nanov2_metablock_meta_index(nanozonev2_t *nanozone) { return nanov2_block_index_to_meta_index((nanov2_block_index_t) nanozone->aslr_cookie); } // Given a block metadata pointer, returns whether the block is active (that is, // it is being used for allocations, it has allocations that have not been freed, // or is waiting to be madvised). static MALLOC_ALWAYS_INLINE MALLOC_INLINE boolean_t nanov2_is_block_active(nanov2_block_meta_t block_meta) { return block_meta.next_slot != SLOT_NULL && block_meta.next_slot != SLOT_MADVISING && block_meta.next_slot != SLOT_MADVISED; } #if OS_VARIANT_RESOLVED // Given a block metadata pointer, returns whether an allocation could be // attempted from it. Allocations are not allowed from blocks that have not yet // been used (since such a block has not been assigned), is full or has been // madvised. static MALLOC_ALWAYS_INLINE MALLOC_INLINE boolean_t nanov2_can_allocate_from_block(nanov2_block_meta_t block_meta) { return block_meta.in_use && block_meta.next_slot != SLOT_FULL; } // Given a pointer, returns whether it has the correct signature to be a // Nano V2 address. static MALLOC_ALWAYS_INLINE MALLOC_INLINE boolean_t nanov2_has_valid_signature(void *ptr) { return (((uintptr_t)ptr) >> SHIFT_NANO_SIGNATURE) == NANOZONE_SIGNATURE; } #endif // OS_VARIANT_RESOLVED // Converts a Nano V2 logical address to the corresponding real address. static MALLOC_ALWAYS_INLINE MALLOC_INLINE void * nanov2_logical_address_to_ptr(nanozonev2_t *nanozone, void *laddr) { return (void *)(((uintptr_t)laddr) ^ nanozone->aslr_cookie_aligned); } // Gets the maximum allocation size for a given size class. static MALLOC_ALWAYS_INLINE MALLOC_INLINE int nanov2_size_from_size_class(nanov2_size_class_t size_class) { return (size_class + 1) * NANO_REGIME_QUANTA_SIZE; } #if OS_VARIANT_RESOLVED // Given an allocation size, returns the corresponding size class. It is the // responsibility of the caller to ensure that the size is valid. Returned // value is zero-based. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_size_class_t nanov2_size_class_from_size(size_t size) { return (nanov2_size_class_t)howmany(size, NANO_REGIME_QUANTA_SIZE) - 1; } #endif // OS_VARIANT_RESOLVED #if OS_VARIANT_RESOLVED // Given a pointer that is assumed to be in the Nano zone, returns the address // of its containing block. Works for both real and logical pointers and returns // a pointer of the same type. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_t * nanov2_block_address_for_ptr(void *ptr) { return (void *)(((uintptr_t)ptr) & NANOV2_BLOCK_ADDRESS_MASK); } #endif // OS_VARIANT_RESOLVED // Given a pointer that is assumed to be in the Nano zone, returns the address // of its containing arena. Works for both real and logical pointers. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_arena_t * nanov2_arena_address_for_ptr(void *ptr) { return (void *)(((uintptr_t)ptr) & NANOV2_ARENA_ADDRESS_MASK); } #if OS_VARIANT_RESOLVED // Given a pointer that is assumed to be in the Nano zone, returns the address // of its containing region. Works for both real and logical pointers. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_region_t * nanov2_region_address_for_ptr(void *ptr) { return (nanov2_region_t *)(((uintptr_t)ptr) & NANOV2_REGION_ADDRESS_MASK); } #endif // OS_VARIANT_RESOLVED // Given a pointer that is assumed to be in the Nano zone, returns the real // address of its metadata block. Works for both real and logical pointers. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_arena_metablock_t * nanov2_metablock_address_for_ptr(nanozonev2_t *nanozone, void *ptr) { // The metadata block is the first logical block in the arena, so its // logical address is that of the arena. To get a real pointer, we map it // through nanov2_logical_address_to_ptr(). return (nanov2_arena_metablock_t *)nanov2_logical_address_to_ptr(nanozone, nanov2_arena_address_for_ptr(ptr)); } #if OS_VARIANT_RESOLVED // Given a pointer to a block_metap_t for a block, returns a pointer to the // block itself. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_t * nanov2_block_address_from_meta_ptr(nanozonev2_t *nanozone, nanov2_block_meta_t *block_metap) { nanov2_block_t *meta_block = nanov2_block_address_for_ptr(block_metap); nanov2_arena_t *arena = nanov2_arena_address_for_ptr(block_metap); // Get the block's index and use that to get the address of the block. nanov2_meta_index_t meta_index = (nanov2_meta_index_t)(block_metap - (nanov2_block_meta_t *)meta_block); nanov2_block_index_t block_index = nanov2_meta_index_to_block_index(meta_index); return &arena->blocks[block_index]; } #endif // OS_VARIANT_RESOLVED // Given the index of a block_metap_t for a block, returns a pointer to the // block itself. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_t * nanov2_block_address_from_meta_index(nanozonev2_t *nanozone, nanov2_arena_t *arena, nanov2_meta_index_t meta_index) { nanov2_block_index_t block_index = nanov2_meta_index_to_block_index(meta_index); return &arena->blocks[block_index]; } // Given a pointer that is assumed to be in the nanozone, returns the index // of its containing block within its hosting arena. Works for both logical and // real pointers and returns an index in the corresponding address space. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_index_t nanov2_block_index_for_ptr(void *ptr) { return (nanov2_block_index_t)(((uintptr_t)ptr) >> NANOV2_OFFSET_BITS) & ((1 << NANOV2_BLOCK_BITS) - 1); } #if OS_VARIANT_RESOLVED // Given a pointer that is assumed to be in the nanozone, returns a pointer to // the meta data for its containing block. Expects ptr be a real address. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_meta_t * nanov2_meta_ptr_for_ptr(nanozonev2_t *nanozone, void *ptr) { nanov2_arena_metablock_t *meta_block = nanov2_metablock_address_for_ptr( nanozone, ptr); nanov2_block_index_t block_index = nanov2_block_index_for_ptr(ptr); nanov2_meta_index_t meta_index = nanov2_block_index_to_meta_index(block_index); return &meta_block->arena_block_meta[meta_index]; } #endif // OS_VARIANT_RESOLVED // Given a region pointer, returns the address of the first arena in the region. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_arena_t * nanov2_first_arena_for_region(nanov2_region_t *region) { // The first arena is colocated with the region itself. return (nanov2_arena_t *)region; } // Given a region pointer, returns a pointer to the arena after the last // active arena in the region. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_arena_t * nanov2_limit_arena_for_region(nanozonev2_t *nanozone, nanov2_region_t *region) { // The first arena is colocated with the region itself. nanov2_arena_t *limit_arena; if (region == nanozone->current_region_base) { limit_arena = nanozone->current_region_next_arena; } else { limit_arena = nanov2_first_arena_for_region(region + 1); } return limit_arena; } // Given a region pointer, returns the address of the linkage structure for // that region. The linkage structure is stored in the first entry of the // metadata block of the first arena in the region. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_region_linkage_t * nanov2_region_linkage_for_region(nanozonev2_t *nanozone, nanov2_region_t *region) { nanov2_arena_metablock_t *first_metadata_block = nanov2_metablock_address_for_ptr(nanozone, region); return (nanov2_region_linkage_t *)&first_metadata_block->arena_block_meta[ nanov2_metablock_meta_index(nanozone)]; } // Given a pointer to a region, returns a pointer to the region that follows it, // or NULL if there isn't one. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_region_t * nanov2_next_region_for_region(nanozonev2_t *nanozone, nanov2_region_t *region) { nanov2_region_linkage_t *linkage = nanov2_region_linkage_for_region(nanozone, region); int offset = linkage->next_region_offset; return offset ? region + offset : NULL; } // Given the index of a slot in a block of a given size and the base address of // the block, returns a pointer to the start of the slot. This works for both // real and logical block pointers and returns a pointer of the same type. static MALLOC_ALWAYS_INLINE MALLOC_INLINE void * nanov2_slot_in_block_ptr(nanov2_block_t *block, nanov2_size_class_t size_class, int slot_index) { return (void *)((uintptr_t)block + nanov2_size_from_size_class(size_class) * slot_index); } #if OS_VARIANT_RESOLVED // Given the base address of a block, the size class for the block and a pointer, // returns the index of the slot represented by the pointer. It is assumed that // the pointer is slot-aligned and is within the bounds of the block. static MALLOC_ALWAYS_INLINE MALLOC_INLINE int nanov2_slot_index_in_block(nanov2_block_t *block, nanov2_size_class_t size_class, void *ptr) { return (int)((uintptr_t)ptr - (uintptr_t)block)/ (nanov2_size_from_size_class(size_class)); } #endif // OS_VARIANT_RESOLVED // Given a (real) pointer, gets the size class of its containing block. Assumes // that the pointer is in a valid region, arena and block. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_size_class_t nanov2_size_class_for_ptr(nanozonev2_t *nanozone, void *ptr) { // To get the size class, we need to convert the block number from // physical to logical, since the ptr_offset_to_size_class table is // indexed by logical block. nanov2_block_index_t block = (int)(nanov2_block_index_for_ptr(ptr) ^ nanozone->aslr_cookie); return ptr_offset_to_size_class[block >> BLOCKS_PER_UNIT_SHIFT]; } #if OS_VARIANT_NOTRESOLVED // Given a meta data index, gets the size class of the corresponding block. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_size_class_t nanov2_size_class_for_meta_index(nanozonev2_t *nanozone, nanov2_meta_index_t meta_index) { // To get the size class, we need to get the block index from meta index // and then convert it from real to logical, since the // ptr_offset_to_size_class table is indexed by logical block. nanov2_block_index_t block_index = nanov2_meta_index_to_block_index(meta_index); int logical_block_index = (int)(block_index ^ nanozone->aslr_cookie); return ptr_offset_to_size_class[logical_block_index >> BLOCKS_PER_UNIT_SHIFT]; } #endif // OS_VARIANT_NOTRESOLVED #if OS_VARIANT_RESOLVED // Given a size class and an arena, returns a pointer to the metadata for the // first block for that size class in the arena, ignoring the metadata block. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_meta_t * nanov2_first_block_for_size_class_in_arena(nanozonev2_t *nanozone, nanov2_size_class_t size_class, nanov2_arena_t *arena) { int block_offset = first_block_offset_by_size_class[size_class]; nanov2_arena_metablock_t *meta_blockp = nanov2_metablock_address_for_ptr(nanozone, arena); nanov2_block_index_t block_index = (nanov2_block_index_t)(block_offset ^ nanozone->aslr_cookie); nanov2_meta_index_t meta_index = nanov2_block_index_to_meta_index(block_index); return &meta_blockp->arena_block_meta[meta_index]; } // Given a pointer to the metadata for a block in a given size class, returns // a pointer to the metadata for the next block, wrapping from the last block // to the first if necessary. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_meta_t * nanov2_next_block_for_size_class(nanozonev2_t *nanozone, nanov2_size_class_t size_class, nanov2_block_meta_t *meta_blockp, boolean_t *wrapped) { // To find the next block, get the index of the current block, which is in // the real address space, unscramble it to get a logical block number, // add 1 to it, wrapping if necessary, then scramble the result. nanov2_block_meta_t *base_meta_blockp = (nanov2_block_meta_t *)(((uintptr_t)meta_blockp) & (NANOV2_BLOCK_ADDRESS_MASK)); nanov2_meta_index_t meta_index = (int)(meta_blockp - base_meta_blockp); nanov2_block_index_t block_index = nanov2_meta_index_to_block_index(meta_index); block_index ^= nanozone->aslr_cookie; // Unscramble int last_offset = last_block_offset_by_size_class[size_class]; if (wrapped) *wrapped = block_index == last_offset; block_index = block_index == last_offset ? first_block_offset_by_size_class[size_class] : block_index + 1; block_index = (nanov2_block_index_t)(block_index ^ nanozone->aslr_cookie); meta_index = nanov2_block_index_to_meta_index(block_index); return &base_meta_blockp[meta_index]; } // Given a pointer to the metadata for a block in a given size class, returns // a pointer to the metadata for the previous block, wrapping from the first // block to the last if necessary. static MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_meta_t * nanov2_previous_block_for_size_class(nanozonev2_t *nanozone, nanov2_size_class_t size_class, nanov2_block_meta_t *meta_blockp, boolean_t *wrapped) { // To find the previous block, get the index of the current block, which is // in the real address space, unscramble it to get a logical block number, // subtract 1 from it, wrapping if necessary, then scramble the result. nanov2_block_meta_t *base_meta_blockp = (nanov2_block_meta_t *)(((uintptr_t)meta_blockp) & (NANOV2_BLOCK_ADDRESS_MASK)); nanov2_meta_index_t meta_index = (int)(meta_blockp - base_meta_blockp); nanov2_block_index_t block_index = nanov2_meta_index_to_block_index(meta_index); block_index ^= nanozone->aslr_cookie; // Unscramble int first_offset = first_block_offset_by_size_class[size_class]; if (wrapped) *wrapped = block_index == first_offset; block_index = block_index == first_offset ? last_block_offset_by_size_class[size_class] : block_index - 1; block_index = (nanov2_block_index_t)(block_index ^ nanozone->aslr_cookie); meta_index = nanov2_block_index_to_meta_index(block_index); return &base_meta_blockp[meta_index]; } // Turns off the in-use bit in the meta data for a given block. static MALLOC_ALWAYS_INLINE MALLOC_INLINE void nanov2_turn_off_in_use(nanov2_block_meta_t *block_metap) { // TODO: find a way to do this that is more efficient and readable. static nanov2_block_meta_t mask = { .in_use = 0, .next_slot = ~0, .free_count = ~0, .gen_count = ~0, }; os_atomic_and((uint32_t *)block_metap, *(uint32_t *)&mask, relaxed); } #pragma mark - #pragma mark Policy Functions // Gets the index of the block in the zone's current_block array from which // allocations should be made. This function should be replaced to implement // a different strategy (e.g. for E- vs P-cores). static MALLOC_ALWAYS_INLINE MALLOC_INLINE int nanov2_get_allocation_block_index(void) { if (os_likely(nano_common_max_magazines_is_ncpu)) { // Default case is max magazines == physical number of CPUs, which // must be > _os_cpu_number() >> hyper_shift, so the modulo // operation is not required. return _os_cpu_number() >> hyper_shift; } if (os_likely(_os_cpu_number_override == -1)) { return (_os_cpu_number() >> hyper_shift) % nano_common_max_magazines; } return (_os_cpu_number_override >> hyper_shift) % nano_common_max_magazines; } #endif // OS_VARIANT_RESOLVED #pragma mark - #pragma mark Allocator Initialization #if OS_VARIANT_NOTRESOLVED static const char madvise_policy_env[] = "MallocNanoMadvisePolicy"; static const char madvise_policy_bootarg[] = "nanov2_madvise_policy"; static const char madvise_immediate[] = "immediate"; static const char madvise_warning[] = "warning"; static const char madvise_critical[] = "critical"; static const char single_arena_env[] = "MallocNanoSingleArena"; static const char single_arena_bootarg[] = "nanov2_single_arena"; static const char scan_policy_env[] = "MallocNanoScanPolicy"; static const char scan_policy_bootarg[] = "nanov2_scan_policy"; static const char size_class_blocks_env[] = "MallocNanoSizeClassBlocks"; static const char size_class_blocks_bootarg[] = "nanov2_size_class_blocks"; // Parse and set the madvise policy setting. If ptr is NULL, sets the default // policy. static void nanov2_set_madvise_policy(const char *name, const char *ptr) { nanov2_madvise_policy_t madvise_policy = NANO_MADVISE_IMMEDIATE; if (ptr) { if (!strncmp(ptr, madvise_immediate, sizeof(madvise_immediate) - 1)) { madvise_policy = NANO_MADVISE_IMMEDIATE; } else if (!strncmp(ptr, madvise_warning, sizeof(madvise_warning) - 1)) { madvise_policy = NANO_MADVISE_WARNING_PRESSURE; } else if (!strncmp(ptr, madvise_critical, sizeof(madvise_critical) - 1)) { madvise_policy = NANO_MADVISE_CRITICAL_PRESSURE; } else { malloc_report(ASL_LEVEL_ERR, "%s value (%s) invalid - ignored.\n", name, ptr); } } nanov2_madvise_policy = madvise_policy; } // Parse and set the list of size classes that are allowed only one arena. If // ptr is NULL, no size classes are restricted to a single arena, // Format is a list of sizes separated by colons (e.g. 16:240). Each size must // be a multiple of NANO_REGIME_QUANTA_SIZE and must be between 16 and 256. static void nanov2_set_single_arena_size_classes(const char *name, const char *ptr) { uint16_t single_arena_size_classes = 0; if (ptr) { const char *value = ptr; const char *endp; boolean_t failed = FALSE; while (*ptr) { long size = malloc_common_convert_to_long(ptr, &endp); if (endp != ptr) { if (*endp && *endp != ':') { failed = TRUE; break; } if (size > NANO_MAX_SIZE || size < NANO_REGIME_QUANTA_SIZE || (size % NANO_REGIME_QUANTA_SIZE) != 0) { failed = TRUE; break; } single_arena_size_classes |= 1 << ((size/NANO_REGIME_QUANTA_SIZE) - 1); } else { failed = true; break; } if (!*endp) { break; } ptr = endp + 1; } if (failed) { malloc_report(ASL_LEVEL_ERR, "%s value (%s) invalid - ignored.\n", name, value); single_arena_size_classes = 0; } } nanov2_policy_config.single_arena_size_classes = single_arena_size_classes; } // Parse and set the block scan policy setting. If ptr is NULL, the default // policy is used. Format is either "firstfit" or "minXX:maxYY:limZZ", where // XX, YY and ZZ are numbers, XX and YY must be between 0 and 100 and XX must // not be greater than YY. min, max and lim may appear in any order or may be // omitted to get default values. static void nanov2_set_block_scan_policy(const char *name, const char *ptr) { static char first_fit_key[] = "firstfit"; static char min_key[] = "min"; static char max_key[] = "max"; static char lim_key[] = "lim"; nanov2_block_scan_policy_t block_scan_policy = NANO_SCAN_CAPACITY_BASED; int scan_min_capacity = DEFAULT_SCAN_MIN_CAPACITY; int scan_max_capacity = DEFAULT_SCAN_MAX_CAPACITY; int scan_limit = DEFAULT_SCAN_LIMIT; const char *endp; boolean_t failed = FALSE; boolean_t min_found = FALSE; boolean_t max_found = FALSE; boolean_t lim_found = FALSE; const char *value = ptr; if (ptr) { if (!strcmp(ptr, first_fit_key)) { block_scan_policy = NANO_SCAN_FIRST_FIT; } else { while (!failed && ptr && *ptr) { if (!strncmp(ptr, min_key, sizeof(min_key) - 1) && !min_found) { min_found = TRUE; ptr += sizeof(min_key) - 1; long value = malloc_common_convert_to_long(ptr, &endp); if (ptr != endp && value >= 0 && value <= 100) { scan_min_capacity = (int)value; ptr = endp; } else { failed = TRUE; } } else if (!strncmp(ptr, max_key, sizeof(max_key) - 1) && !max_found) { max_found = TRUE; ptr += sizeof(max_key) - 1; long value = malloc_common_convert_to_long(ptr, &endp); if (ptr != endp && value >= 0 && value <= 100) { scan_max_capacity = (int)value; ptr = endp; } else { failed = TRUE; } } else if (!strncmp(ptr, lim_key, sizeof(lim_key) - 1) && !lim_found) { lim_found = TRUE; ptr += sizeof(lim_key) - 1; long value = malloc_common_convert_to_long(ptr, &endp); if (ptr != endp && value >= 0) { scan_limit = (int)value; ptr = endp; } else { failed = TRUE; } } else { failed = TRUE; } if (*ptr) { if (*ptr == ':') { ptr++; } else { failed = TRUE; } } } if (!failed && scan_min_capacity > scan_max_capacity) { failed = TRUE; } } } if (!failed) { nanov2_policy_config.block_scan_policy = block_scan_policy; nanov2_policy_config.block_scan_min_capacity = scan_min_capacity; nanov2_policy_config.block_scan_max_capacity = scan_max_capacity; nanov2_policy_config.block_scan_limit = scan_limit; } else { malloc_report(ASL_LEVEL_ERR, "%s value (%s) invalid - ignored.\n", name, value); } } // Configures the nanov2_blocks_by_size_class array. If ptr is not NULL and // *ptr is not empty, it is expected to be a list of 16 positive integers // separated by commas that sum to TOTAL_BLOCK_UNITS (which is currently 64). // For example, as an environment variable: // MallocNanoSizeClassBlocks=2,7,6,6,6,5,5,5,5,2,2,2,2,2,6,1 // or as a boot argument: // nanov2_size_class_blocks=2,7,6,6,6,5,5,5,5,2,2,2,2,2,6,1 static void nanov2_set_blocks_by_size_class(const char *name, const char *ptr) { int new_total_block_units = 0; int new_blocks_by_size_class[NANO_SIZE_CLASSES]; MALLOC_STATIC_ASSERT( sizeof(new_blocks_by_size_class) == sizeof(block_units_by_size_class), "Size mismatch in nanov2_set_blocks_by_size_class()"); const char *endp; const char *sptr = ptr; for (int i = 0; i < NANO_SIZE_CLASSES; i++) { int count = (int)malloc_common_convert_to_long(ptr, &endp); char separator = i == NANO_SIZE_CLASSES - 1 ? '\0' : ','; if (endp == ptr || *endp != separator || count > TOTAL_BLOCK_UNITS) { malloc_report(ASL_LEVEL_ERR, "%s value invalid: [%s] - ignored.\n", name, sptr); return; } new_blocks_by_size_class[i] = count; new_total_block_units += count; ptr = endp + 1; } if (new_total_block_units != TOTAL_BLOCK_UNITS) { malloc_report(ASL_LEVEL_ERR, "%s value invalid - values must sum to %d, not %d - ignored.\n", name, TOTAL_BLOCK_UNITS, new_total_block_units); } else { memcpy(block_units_by_size_class, new_blocks_by_size_class, sizeof(block_units_by_size_class)); } } // First stage initialization. Called during libSystem initialization. // Reads environment variables and boot arguments and sets the madvise policy, // single arena list and the block scan policy. Environment variables override // boot arguments. void nanov2_init(const char *envp[], const char *apple[], const char *bootargs) { // Get and process the boot args and environment variables. char value_buf[256]; const char *value = _simple_getenv(envp, madvise_policy_env); const char *name = madvise_policy_env; if (!value) { value = malloc_common_value_for_key(bootargs, madvise_policy_bootarg); if (value) { name = madvise_policy_bootarg; } } nanov2_set_madvise_policy(name, value); name = single_arena_env; value = _simple_getenv(envp, single_arena_env); if (!value) { value = malloc_common_value_for_key_copy(bootargs, single_arena_bootarg, value_buf, sizeof(value_buf)); if (value) { name = single_arena_bootarg; } } nanov2_set_single_arena_size_classes(name, value); name = scan_policy_env; value = _simple_getenv(envp, scan_policy_env); if (!value) { value = malloc_common_value_for_key_copy(bootargs, scan_policy_bootarg, value_buf, sizeof(value_buf)); if (value) { name = scan_policy_bootarg; } } nanov2_set_block_scan_policy(name, value); name = size_class_blocks_env; value = _simple_getenv(envp, size_class_blocks_env); if (!value) { value = malloc_common_value_for_key_copy(bootargs, size_class_blocks_bootarg, value_buf, sizeof(value_buf)); if (value) { name = size_class_blocks_bootarg; } } if (value) { nanov2_set_blocks_by_size_class(name, value); } } static void nanov2_configure_once(void *context MALLOC_UNUSED) { // Check that the block_units_by_size_class table is consistent. int total_blocks = 0; for (int i = 0; i < NANO_SIZE_CLASSES; i++) { total_blocks += block_units_by_size_class[i] * BLOCKS_PER_UNIT; } MALLOC_ASSERT(total_blocks == NANOV2_BLOCKS_PER_ARENA); // Build the first_block_offset_by_size_class and // last_block_offset_by_size_class tables. The first entry is special // because block 0 is reserved for the metadata block, so the first offset // is 1 and the number of blocks allocated is reduced by 1. int next_offset = 1; first_block_offset_by_size_class[0] = next_offset; next_offset = block_units_by_size_class[0] * BLOCKS_PER_UNIT; last_block_offset_by_size_class[0] = next_offset - 1; for (int i = 1; i < NANO_SIZE_CLASSES; i++) { first_block_offset_by_size_class[i] = next_offset; next_offset += block_units_by_size_class[i] * BLOCKS_PER_UNIT; last_block_offset_by_size_class[i] = next_offset - 1; } MALLOC_ASSERT(next_offset == NANOV2_BLOCKS_PER_ARENA); // Construct the ptr_offset_to_size_class map, which maps the part of the // logical address that depends on size class to the corresponding size // class. This would be a simple mask operation if all size classes were of // equal size, but sadly they are not. int next_index = 0; for (int i = 0; i < NANO_SIZE_CLASSES; i++) { int block_units = block_units_by_size_class[i]; for (int j = 0; j < block_units; j++) { ptr_offset_to_size_class[next_index++] = i; } } MALLOC_ASSERT(next_index == NANOV2_BLOCKS_PER_ARENA/BLOCKS_PER_UNIT); } static os_once_t nanov2_config_predicate; void nanov2_configure(void) { os_once(&nanov2_config_predicate, NULL, nanov2_configure_once); } #endif // OS_VARIANT_NOTRESOLVED #pragma mark - #pragma mark Zone Functions #if OS_VARIANT_RESOLVED // Returns the allocation size for a pointer. Uses nanov2_pointer_size() to // determine whether the pointer is for a Nano V2 allocation and, if not, // delegates to the helper zone. Returns 0 if the pointer is not to memory // allocated by Nano V2 or attributable to the helper zone. size_t nanov2_size(nanozonev2_t *nanozone, const void *ptr) { size_t size = nanov2_pointer_size(nanozone, (void *)ptr, FALSE); return size ? size : nanozone->helper_zone->size(nanozone->helper_zone, ptr); } void * nanov2_malloc(nanozonev2_t *nanozone, size_t size) { size_t rounded_size = _nano_common_good_size(size); if (rounded_size <= NANO_MAX_SIZE) { void *ptr = nanov2_allocate(nanozone, rounded_size, FALSE); if (ptr) { if (os_unlikely(size && (nanozone->debug_flags & MALLOC_DO_SCRIBBLE))) { memset(ptr, SCRIBBLE_BYTE, size); } return ptr; } } // If we reach this point, we couldn't allocate, so delegate to the // helper zone. return nanozone->helper_zone->malloc(nanozone->helper_zone, size); } void nanov2_free_definite_size(nanozonev2_t *nanozone, void *ptr, size_t size) { // Check whether it's a Nano pointer and get the size. We should only get // here if it is and furthermore we already know that "size" is the actual // rounded size, so don't waste time rechecking that. This is just a // sanity check. if (ptr && nanov2_has_valid_signature(ptr)) { if (os_unlikely(nanozone->debug_flags & MALLOC_DO_SCRIBBLE)) { memset(ptr, SCRABBLE_BYTE, size); } nanov2_free_to_block(nanozone, ptr, nanov2_size_class_from_size(size)); return; } return nanozone->helper_zone->free_definite_size(nanozone->helper_zone, ptr, size); } void nanov2_free(nanozonev2_t *nanozone, void *ptr) { if (ptr && nanov2_has_valid_signature(ptr)) { // Check whether it's a Nano pointer and get the size. If it's not // Nano, pass it to the helper zone. size_t size = nanov2_pointer_size(nanozone, ptr, FALSE); if (size) { if (os_unlikely(nanozone->debug_flags & MALLOC_DO_SCRIBBLE)) { memset(ptr, SCRABBLE_BYTE, size); } nanov2_free_to_block(nanozone, ptr, nanov2_size_class_from_size(size)); return; } } return nanozone->helper_zone->free(nanozone->helper_zone, ptr); } void * nanov2_calloc(nanozonev2_t *nanozone, size_t num_items, size_t size) { size_t total_bytes; if (calloc_get_size(num_items, size, 0, &total_bytes)) { return NULL; } size_t rounded_size = _nano_common_good_size(total_bytes); if (total_bytes <= NANO_MAX_SIZE) { void *ptr = nanov2_allocate(nanozone, rounded_size, TRUE); if (ptr) { return ptr; } } // If we reach this point, we couldn't allocate, so delegate to the // helper zone. return nanozone->helper_zone->calloc(nanozone->helper_zone, 1, total_bytes); } #endif // OS_VARIANT_RESOLVED #if OS_VARIANT_NOTRESOLVED static void * nanov2_valloc(nanozonev2_t *nanozone, size_t size) { // Always delegate this to the helper zone. return nanozone->helper_zone->valloc(nanozone->helper_zone, size); } #endif // OS_VARIANT_NOTRESOLVED #if OS_VARIANT_RESOLVED void * nanov2_realloc(nanozonev2_t *nanozone, void *ptr, size_t new_size) { // If we are given a NULL pointer, just allocate memory of the requested // size. if (ptr == NULL) { return nanov2_malloc(nanozone, new_size); } size_t old_size = nanov2_pointer_size(nanozone, ptr, FALSE); if (!old_size) { // Not a Nano pointer - let the helper deal with it return nanozone->helper_zone->realloc(nanozone->helper_zone, ptr, new_size); } void *new_ptr; if (new_size > NANO_MAX_SIZE) { // Too large for Nano. Try to allocate from the helper zone. new_ptr = nanozone->helper_zone->malloc(nanozone->helper_zone, new_size); if (!new_ptr) { // Failed to allocate - leave the existing allocation alone. return NULL; } } else if (!new_size) { // Resizing to zero. Free the existing memory and explicitly allocate // zero bytes. nanov2_free(nanozone, ptr); return nanov2_malloc(nanozone, 0); } else { size_t new_good_size = _nano_common_good_size(new_size); if (new_good_size > old_size || new_good_size <= old_size/2) { // Growing or shrinking to less than half size - we need to // reallocate. new_ptr = nanov2_malloc(nanozone, new_good_size); if (!new_ptr) { // Failed to allocate - leave the existing allocation alone. return NULL; } } else { // Same size or shrinking by less than half size. Keep the same // allocation and clear the area that's being released. if (new_size != old_size) { MALLOC_ASSERT(new_size < old_size); if (os_unlikely(nanozone->debug_flags & MALLOC_DO_SCRIBBLE)) { memset(ptr + new_size, SCRABBLE_BYTE, old_size - new_size); } } return ptr; } } // If we reach this point, we allocated new memory. Copy the existing // content to the new location and release the old allocation. MALLOC_ASSERT(new_ptr); memcpy(new_ptr, ptr, MIN(old_size, new_size)); nanov2_free(nanozone, ptr); return new_ptr; } #endif // OS_VARIANT_RESOLVED #if OS_VARIANT_NOTRESOLVED static void nanov2_destroy(nanozonev2_t *nanozone) { nanozone->helper_zone->destroy(nanozone->helper_zone); nano_common_deallocate_pages((void *)nanozone, NANOZONEV2_ZONE_PAGED_SIZE, nanozone->debug_flags); } #endif // OS_VARIANT_NOTRESOLVED #if OS_VARIANT_RESOLVED boolean_t nanov2_claimed_address(nanozonev2_t *nanozone, void *ptr) { return nanov2_pointer_size(nanozone, ptr, TRUE) != 0; } unsigned nanov2_batch_malloc(nanozonev2_t *nanozone, size_t size, void **results, unsigned count) { unsigned allocated = 0; size_t rounded_size = _nano_common_good_size(size); if (rounded_size <= NANO_MAX_SIZE) { while (allocated < count) { void *ptr = nanov2_allocate(nanozone, rounded_size, FALSE); if (!ptr) { break; } *results++ = ptr; allocated++; } if (allocated == count) { // Allocated everything. return allocated; } } // We could not allocate everything. Let the helper zone do the rest. return allocated + nanozone->helper_zone->batch_malloc( nanozone->helper_zone, size, results, count - allocated); } void nanov2_batch_free(nanozonev2_t *nanozone, void **to_be_freed, unsigned count) { if (count) { while (count--) { void *ptr = to_be_freed[count]; if (ptr) { nanov2_free(nanozone, ptr); } } } } #endif // OS_VARIANT_RESOLVED #if OS_VARIANT_NOTRESOLVED static void * nanov2_memalign(nanozonev2_t *nanozone, size_t alignment, size_t size) { // Always delegate this to the helper zone. return nanozone->helper_zone->memalign(nanozone->helper_zone, alignment, size); } #endif // OS_VARIANT_NOTRESOLVED #if OS_VARIANT_RESOLVED size_t nanov2_pressure_relief(nanozonev2_t *nanozone, size_t goal) { if (nanov2_madvise_policy != NANO_MADVISE_WARNING_PRESSURE && nanov2_madvise_policy != NANO_MADVISE_CRITICAL_PRESSURE) { // In the current implementation, we only get called on warning, so // act if the policy is either warning or critical. We would need to // add a new zone entry point to respond to critical. return 0; } const char *name = nanozone->basic_zone.zone_name; MAGMALLOC_PRESSURERELIEFBEGIN((void *)nanozone, name, (int)goal); MALLOC_TRACE(TRACE_nano_memory_pressure | DBG_FUNC_START, (uint64_t)nanozone, goal, 0, 0); size_t total = 0; // Loop over all arenas madvising blocks that are marked as madvisable, // until we reach our goal. nanov2_region_t *region = nanozone->first_region_base; nanov2_meta_index_t metablock_meta_index = nanov2_metablock_meta_index(nanozone); while (region) { nanov2_arena_t *arena = nanov2_first_arena_for_region(region); nanov2_arena_t *arena_after_region = nanov2_limit_arena_for_region(nanozone, region); while (arena < arena_after_region) { // Scan all of the blocks in the arena, skipping the metadata block. nanov2_arena_metablock_t *meta_blockp = nanov2_metablock_address_for_ptr(nanozone, arena); nanov2_block_meta_t *block_metap = &meta_blockp->arena_block_meta[0]; // We need to hold the zone madvise lock to madvise. We could take // it for the duration of this function, but that might hold up // ongoing allocation and free operations for too long. So just // lock and unlock for each arena. _malloc_lock_lock(&nanozone->madvise_lock); for (nanov2_meta_index_t i = 0; i < NANOV2_BLOCKS_PER_ARENA; i++, block_metap++) { if (i != metablock_meta_index) { nanov2_block_meta_t meta = os_atomic_load(block_metap, relaxed); if (meta.next_slot == SLOT_CAN_MADVISE) { nanov2_block_t *blockp = nanov2_block_address_from_meta_index( nanozone, arena, i); if (nanov2_madvise_block(nanozone, block_metap, blockp, nanov2_size_class_for_ptr(nanozone, blockp))) { total += NANOV2_BLOCK_SIZE; } } } } _malloc_lock_unlock(&nanozone->madvise_lock); if (goal && total >= goal) { goto done; } arena++; } region = nanov2_next_region_for_region(nanozone, region); } done: MAGMALLOC_PRESSURERELIEFEND((void *)nanozone, name, (int)goal, (int)total); MALLOC_TRACE(TRACE_nano_memory_pressure | DBG_FUNC_END, (uint64_t)nanozone, goal, total, 0); return total; } #endif // OS_VARIANT_RESOLVED #pragma mark - #pragma mark Zone Introspection #if OS_VARIANT_NOTRESOLVED // NOTE: in the code that follows, address that we obtain from the Nano // structures are relative to the target process. They need to be translated // before they can be used to read the mapping in this process. #define NANOV2_ZONE_PTR_TO_MAPPED_PTR(type, zone_ptr, offset) \ (type)((mach_vm_address_t)zone_ptr - (mach_vm_offset_t)offset) #define NANOV2_MAPPED_PTR_TO_ZONE_PTR(type, mapped_ptr, offset) \ (type)((mach_vm_address_t)mapped_ptr + (mach_vm_offset_t)offset) static kern_return_t nanov2_ptr_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder) { // Ensure that we have configured enough of the allocator to be able to // examine its data structures. In tools that do not directly use Nano, we // won't have done this yet. nanov2_configure() runs the initialization // only once. nanov2_configure(); // Only MALLOC_PTR_IN_USE_RANGE_TYPE and MALLOC_PTR_REGION_RANGE_TYPE have // meaning for Nano. Anything else returns immediately. if (!(type_mask & (MALLOC_PTR_IN_USE_RANGE_TYPE|MALLOC_PTR_REGION_RANGE_TYPE))) { return 0; } // Read the zone data. nanozonev2_t *nanozone; nanozonev2_t zone_copy; kern_return_t kr; bitarray_t slots; if (!reader) { reader = nano_common_default_reader; } kr = reader(task, zone_address, sizeof(nanozonev2_t), (void **)&nanozone); if (kr) { return kr; } boolean_t self_zone = (nanozonev2_t *)zone_address == nanozone; memcpy(&zone_copy, nanozone, sizeof(zone_copy)); nanozone = &zone_copy; nanov2_meta_index_t metablock_meta_index = nanov2_metablock_meta_index(nanozone); // Process the zone one region at a time. Report each in-use block as a // pointer range and each in-use slot as a pointer. nanov2_region_t *region = nanozone->first_region_base; while (region) { mach_vm_address_t vm_addr = (mach_vm_address_t)NULL; kern_return_t kr = reader(task, (vm_address_t)region, NANOV2_REGION_SIZE, (void **)&vm_addr); if (kr) { return kr; } // ptr_offset is the difference between an address in the target process // and its mapped address in this process. mach_vm_offset_t ptr_offset = (mach_vm_address_t)region - vm_addr; nanov2_arena_t *arena = nanov2_first_arena_for_region(region); nanov2_arena_t *limit_arena = nanov2_limit_arena_for_region(nanozone, region); vm_range_t ptr_range; while (arena < limit_arena) { // Find the metadata block and process every entry, apart from the // one for the metadata block itself. nanov2_arena_metablock_t *arena_meta_blockp = NANOV2_ZONE_PTR_TO_MAPPED_PTR(nanov2_arena_metablock_t *, nanov2_metablock_address_for_ptr(nanozone, arena), ptr_offset); nanov2_block_meta_t *block_metap = &arena_meta_blockp->arena_block_meta[0]; for (nanov2_meta_index_t i = 0; i < NANOV2_BLOCKS_PER_ARENA; i++, block_metap++) { if (i == metablock_meta_index) { // Skip the metadata block. continue; } nanov2_block_meta_t meta = os_atomic_load(block_metap, relaxed); if (!nanov2_is_block_active(meta)) { continue; } nanov2_block_t *blockp = nanov2_block_address_from_meta_index( nanozone, arena, i); if (type_mask & MALLOC_PTR_REGION_RANGE_TYPE) { // Report this block as an in-use range. ptr_range.address = (vm_address_t)blockp; ptr_range.size = NANOV2_BLOCK_SIZE; recorder(task, context, MALLOC_PTR_REGION_RANGE_TYPE, &ptr_range, 1); } if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) { // Report all of the pointers in the block that are not on // the free list. nanov2_size_class_t size_class = nanov2_size_class_for_ptr( nanozone, blockp); int slot_size = nanov2_size_from_size_class(size_class); int slot_count = slots_by_size_class[size_class]; vm_range_t ranges[NANOV2_MAX_SLOTS_PER_BLOCK]; int range_count = 0; if (meta.next_slot == SLOT_BUMP || meta.next_slot == SLOT_FULL) { // Either the block is full or the freelist is empty. If // it's full, everything is in use. If the free list is // empty, everything up to slot_count - meta.free_count - 1 // is in use. range_count = meta.next_slot == SLOT_BUMP ? slot_count - meta.free_count - 1 : slot_count; for (int i = 0; i < range_count; i++) { ranges[i].address = (vm_address_t)nanov2_slot_in_block_ptr(blockp, size_class, i); ranges[i].size = slot_size; } } else { // We need to scan the freelist to see what's in use. int log_size = 64 - __builtin_clzl(slot_count); if (self_zone) { // Don't allocate from ourselves! slots = nanozone->helper_zone->calloc(nanozone->helper_zone, 1, bitarray_size(log_size)); } else { slots = bitarray_create(log_size); } for (int i = 0; i < slot_count; i++) { bitarray_set(slots, log_size, i); } int next_slot = meta.next_slot; int free_list_count = 0; while (next_slot != SLOT_BUMP) { next_slot--; // meta.next_slot is 1-based. if (next_slot < 0 || next_slot >= slot_count || !bitarray_get(slots, log_size, next_slot)) { // Out of range or already seen?? We may have // snapshotted the block while it was updating. // Don't go any further to avoid an infinite loop. break; } bitarray_zap(slots, log_size, next_slot); void *ptr = nanov2_slot_in_block_ptr(blockp, size_class, next_slot); nanov2_free_slot_t *slotp = NANOV2_ZONE_PTR_TO_MAPPED_PTR(nanov2_free_slot_t *, ptr, ptr_offset); next_slot = slotp->next_slot; free_list_count++; } // Add a range for each slot that is not on the freelist, // unless that slot has never been allocated. int block_free_count = meta.free_count + 1; // actual free count. int in_use_count = slot_count - block_free_count; int slots_used_count = in_use_count + free_list_count; index_t index; while (bitarray_zap_first_set(slots, log_size, &index)) { if (index >= slots_used_count) { // Reached the end of the slots that have been // allocated at some point. break; } ranges[range_count].address = (vm_address_t)nanov2_slot_in_block_ptr(blockp, size_class, index); ranges[range_count].size = slot_size; range_count++; } free(slots); } if (range_count) { // Notify the in-use pointers that we found. recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, ranges, range_count); } } } arena++; } // We have to manually handle the linkage to the next region because // of the address slide between this process and the target. nanov2_region_linkage_t *region_linkagep = nanov2_region_linkage_for_region(nanozone, region); nanov2_region_linkage_t *mapped_region_linkagep = NANOV2_ZONE_PTR_TO_MAPPED_PTR(nanov2_region_linkage_t *, region_linkagep, ptr_offset); int offset = mapped_region_linkagep->next_region_offset; region = offset ? region + offset : NULL; } return 0; } static size_t nanov2_good_size(nanozonev2_t *nanozone, size_t size) { if (size <= NANO_MAX_SIZE) { return _nano_common_good_size(size); } return nanozone->helper_zone->introspect->good_size(nanozone->helper_zone, size); } static boolean_t nanov2_check(nanozonev2_t *nanozone) { // Does nothing, just like Nano V1. return 1; } static void nanov2_print(nanozonev2_t *nanozone, boolean_t verbose) { // Zone-wide statistics malloc_statistics_t stats; nanov2_statistics_t *nano_stats = &nanozone->statistics; nanov2_statistics(nanozone, &stats); malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Nanozonev2 %p: blocks in use: %llu, size in use: %llu allocated size: %llu, " "allocated regions: %d, region holes: %d\n", nanozone, (uint64_t)stats.blocks_in_use, (uint64_t)stats.size_in_use, (uint64_t)stats.size_allocated, nano_stats->allocated_regions, nano_stats->region_address_clashes); #if DEBUG_MALLOC // Per-size class statistics malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "\nPer size-class statistics:\n"); for (int i = 0; i < NANO_SIZE_CLASSES; i++) { nanov2_size_class_statistics *cs = &nano_stats->size_class_statistics[i]; malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, " Class %d: ", i); malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "total alloc: %llu, total frees: %llu, madvised blocks: %llu, madvise races: %llu", cs->total_allocations, cs->total_frees, cs->madvised_blocks, cs->madvise_races); malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "\n"); } #endif // DEBUG_MALLOC // Per-context block pointers. malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Current Allocation Blocks By Size Class/Context [CPU]\n"); for (int i = 0; i < NANO_SIZE_CLASSES; i++) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, " Class %d: ", i); for (int j = 0; j < MAX_CURRENT_BLOCKS; j++) { if (nanozone->current_block[i][j]) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%d: %p; ", j, nanozone->current_block[i][j]); } } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "\n"); } nanov2_meta_index_t metablock_meta_index = nanov2_metablock_meta_index(nanozone); nanov2_region_t *region = nanozone->first_region_base; int region_index = 0; while (region) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "\nRegion %d: base address %p\n", region_index, region); nanov2_arena_t *arena = nanov2_first_arena_for_region(region); nanov2_arena_t *limit_arena = nanov2_limit_arena_for_region(nanozone, region); int arena_index = 0; while (arena < limit_arena) { // Find the metadata block and process every entry, apart from the // one for the metadata block itself. nanov2_arena_metablock_t *arena_meta_blockp = nanov2_metablock_address_for_ptr(nanozone, arena); nanov2_block_meta_t *block_metap = &arena_meta_blockp->arena_block_meta[0]; int active_blocks = 0; int madvisable_blocks = 0; int unused_blocks = 0; int madvised_blocks = 0; int madvising_blocks = 0; for (nanov2_meta_index_t i = 0; i < NANOV2_BLOCKS_PER_ARENA; i++) { if (i == metablock_meta_index) { // Skip the metadata block. continue; } nanov2_block_meta_t meta = block_metap[i]; switch (meta.next_slot) { case SLOT_NULL: unused_blocks++; break; case SLOT_MADVISED: madvised_blocks++; break; case SLOT_MADVISING: madvising_blocks++; break; case SLOT_CAN_MADVISE: madvisable_blocks++; break; default: active_blocks++; break; } } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Arena #%d: base address %p. Blocks - active: %d, " "madvisable: %d, madvising: %d, madvised: %d, unused: %d\n", arena_index, arena, active_blocks, madvisable_blocks, madvising_blocks, madvised_blocks, unused_blocks); // Print which size classes have blocks allocated in this arena. int non_empty_size_classes[NANO_SIZE_CLASSES]; for (int i = 0; i < NANO_SIZE_CLASSES; i++) { non_empty_size_classes[i] = 0; } for (nanov2_meta_index_t i = 0; i < NANOV2_BLOCKS_PER_ARENA; i++) { if (i == metablock_meta_index) { // Skip the metadata block. continue; } nanov2_block_meta_t meta = block_metap[i]; nanov2_size_class_t size_class = nanov2_size_class_for_meta_index(nanozone, i); switch (meta.next_slot) { case SLOT_FULL: case SLOT_BUMP: default: non_empty_size_classes[size_class]++; break; case SLOT_NULL: case SLOT_CAN_MADVISE: case SLOT_MADVISING: case SLOT_MADVISED: // Do not count these. break; } } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Size classes with allocated blocks: "); for (int i = 0; i < NANO_SIZE_CLASSES; i++) { if (non_empty_size_classes[i]) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%d ", i); } } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "\n"); for (nanov2_meta_index_t i = 0; i < NANOV2_BLOCKS_PER_ARENA; i++) { if (i == metablock_meta_index) { // Skip the metadata block. continue; } nanov2_block_meta_t meta = block_metap[i]; if (!nanov2_is_block_active(meta) && !verbose) { continue; } nanov2_size_class_t size_class = nanov2_size_class_for_meta_index(nanozone, i); char *slot_text; switch (meta.next_slot) { case SLOT_NULL: slot_text = "NOT USED"; break; case SLOT_FULL: slot_text = "FULL"; break; case SLOT_CAN_MADVISE: slot_text = "CAN MADVISE"; break; case SLOT_MADVISING: slot_text = "MADVISING"; break; case SLOT_MADVISED: slot_text = "MADVISED"; break; default: slot_text = NULL; break; } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, " Block %d: base %p; metadata: %p, size %d (class %d) in-use: %d ", i, nanov2_block_address_from_meta_index(nanozone, arena, i), &block_metap[i], nanov2_size_from_size_class(size_class), size_class, meta.in_use); if (slot_text) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "%s\n", slot_text); } else { int allocated = slots_by_size_class[size_class] - meta.free_count - 1; if (meta.next_slot == SLOT_BUMP) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "BUMP (free list empty)"); } else { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "next_slot (1-based) = %d", meta.next_slot); } malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, ", allocated slots: %d, free slots = %d, occupancy: %d%%\n", allocated, meta.free_count + 1, (100 * allocated)/slots_by_size_class[size_class]); } } arena++; arena_index++; } region = nanov2_next_region_for_region(nanozone, region); region_index++; } } static void nanov2_log(malloc_zone_t *zone, void *log_address) { // Does nothing, just like Nano V1. } static void nanov2_force_lock(nanozonev2_t *nanozone) { // Nothing to do - Nano V2 does not have a zone lock. } static void nanov2_force_unlock(nanozonev2_t *nanozone) { // Nothing to do - Nano V2 does not have a zone lock. } static void nanov2_reinit_lock(nanozonev2_t *nanozone) { // Nothing to do - Nano V2 does not have a zone lock. } static boolean_t nanov2_locked(nanozonev2_t *nanozone) { // Nothing to do - Nano V2 does not have a zone lock. return FALSE; } static void nanov2_statistics(nanozonev2_t *nanozone, malloc_statistics_t *stats) { memset(stats, '\0', sizeof(*stats)); nanov2_region_t *region; nanov2_arena_t * arena; nanov2_meta_index_t metadata_block_index = nanov2_metablock_meta_index(nanozone); // Iterate over each arena in each region. Within each region, add // statistics for each slot in each block, excluding the meta data block. for (region = nanozone->first_region_base; region; region = nanov2_next_region_for_region(nanozone, region)) { for (arena = nanov2_first_arena_for_region(region); arena < nanov2_limit_arena_for_region(nanozone, region); arena++) { nanov2_arena_metablock_t *meta_block = nanov2_metablock_address_for_ptr(nanozone, arena); for (nanov2_meta_index_t i = 0; i < NANOV2_BLOCKS_PER_ARENA; i++) { if (i == metadata_block_index) { // Skip the metadata block. continue; } nanov2_block_meta_t *block_metap = &meta_block->arena_block_meta[i]; nanov2_size_class_t size_class = nanov2_size_class_for_meta_index(nanozone, i); int slot_size = nanov2_size_from_size_class(size_class); nanov2_block_meta_t meta = os_atomic_load(block_metap, relaxed); int slots_in_use = 0; switch (meta.next_slot) { case SLOT_NULL: // FALLTHRU case SLOT_CAN_MADVISE: // FALLTHRU case SLOT_MADVISING: // FALLTHRU case SLOT_MADVISED: // These blocks have no active content. break; case SLOT_FULL: slots_in_use = slots_by_size_class[size_class]; break; case SLOT_BUMP: // FALLTHRU default: slots_in_use = slots_by_size_class[size_class] - meta.free_count - 1; break; } // We can't report max_size_in_use because we don't have the // metadata to do so. if (slots_in_use) { stats->blocks_in_use += slots_in_use; stats->size_in_use += slots_in_use * slot_size; stats->size_allocated += NANOV2_BLOCK_SIZE; } } } } } static const struct malloc_introspection_t nanov2_introspect = { .enumerator = (void *)nanov2_ptr_in_use_enumerator, .good_size = (void *)nanov2_good_size, .check = (void *)nanov2_check, .print = (void *)nanov2_print, .log = (void *)nanov2_log, .force_lock = (void *)nanov2_force_lock, .force_unlock = (void *)nanov2_force_unlock, .statistics = (void *)nanov2_statistics, .zone_locked = (void *)nanov2_locked, .enable_discharge_checking = NULL, .disable_discharge_checking = NULL, #ifdef __BLOCKS__ .enumerate_discharged_pointers = NULL, #else // __BLOCKS__ .enumerate_unavailable_without_blocks = NULL, #endif // __BLOCKS__ .reinit_lock = (void *)nanov2_reinit_lock, }; #endif // OS_VARIANT_NOTRESOLVED #pragma mark - #pragma mark Utility Functions #if OS_VARIANT_RESOLVED // Given a pointer that may be to Nano V2-allocated memory, returns the size of // the allocation, or 0 if the pointer does not correspond to an active // allocation. If allow_inner is true, the pointer need not point to the start // of the allocation. size_t nanov2_pointer_size(nanozonev2_t *nanozone, void *ptr, boolean_t allow_inner) { // First check the address signature. if (!nanov2_has_valid_signature((void *)ptr)) { return 0; } // Check for proper alignment, unless we could have an inner pointer. if (!allow_inner && ((uintptr_t)ptr) & NANO_QUANTA_MASK) { return 0; } // Bounds check against the active address space. if (ptr < (void *)nanozone->first_region_base || ptr > (void *)nanozone->current_region_next_arena) { return 0; } #if NANOV2_MULTIPLE_REGIONS // Need to check that the region part is valid because there could be holes. // Do this only if we know there is a hole. // NOTE: in M2 convergence, use a hashed structure to make this more // efficient. if (nanozone->statistics.region_address_clashes) { nanov2_region_t *ptr_region = nanov2_region_address_for_ptr(ptr); nanov2_region_t *region = nanozone->first_region_base; while (region) { if (ptr_region == region) { break; } region = nanov2_next_region_for_region(nanozone, region); } if (!region) { // Reached the end of the region list without matching - not a // valid Nano V2 pointer. return 0; } } #endif // NANOV2_MULTIPLE_REGIONS // Get the size class for the pointer and the address of its meta block // header. nanov2_size_class_t size_class = nanov2_size_class_for_ptr(nanozone, ptr); nanov2_block_meta_t *block_metap = nanov2_meta_ptr_for_ptr(nanozone, ptr); // Reject if the block is not active, or it doesn't have any allocations. nanov2_block_meta_t meta = os_atomic_load(block_metap, relaxed); if (!nanov2_is_block_active(meta) || (meta.next_slot != SLOT_FULL && meta.free_count == slots_by_size_class[size_class] - 1)) { return 0; } size_t size = nanov2_size_from_size_class(size_class); nanov2_addr_t addr = { .addr = ptr }; if (!allow_inner && (addr.fields.nano_offset % size)) { return 0; } // The only reasonable way to check whether the pointer is free is to // inspect the canary value at the start of the slot, since we cannot take // a huge hit for walking the free list. nanov2_free_slot_t *slotp = (nanov2_free_slot_t *)ptr; uintptr_t guard = os_atomic_load(&slotp->double_free_guard, relaxed); if ((guard ^ nanozone->slot_freelist_cookie) == (uintptr_t)ptr) { return 0; } return size; } #pragma mark - #pragma mark Madvise Management // Given a pointer to a block and its metadata, calls madvise() on that block // if it is in state SLOT_CAN_MADVISE. Returns true on success, false if the // block is not in the correct state or if the state changed during the // operation. // // This function must be called with the zone's madvise_lock held boolean_t nanov2_madvise_block(nanozonev2_t *nanozone, nanov2_block_meta_t *block_metap, nanov2_block_t *blockp, nanov2_size_class_t size_class) { _malloc_lock_assert_owner(&nanozone->madvise_lock); boolean_t madvised = FALSE; nanov2_block_meta_t old_meta = os_atomic_load(block_metap, relaxed); if (old_meta.next_slot == SLOT_CAN_MADVISE) { // Nobody raced with us. We can safely madvise this block. First change // the state to SLOT_MADVISING so that other threads don't try to // grab the block for new allocations. nanov2_block_meta_t new_meta = { .next_slot = SLOT_MADVISING, .gen_count = old_meta.gen_count + 1, }; if (!os_atomic_cmpxchgv(block_metap, old_meta, new_meta, &old_meta, relaxed)) { // Somebody else tampered with this block. This can happen if // another thread raced with us to allocate in this block. Count // the contended access. nanozone->statistics.size_class_statistics[size_class].madvise_races++; return false; } if (mvm_madvise_free(nanozone, nanov2_region_address_for_ptr(blockp), (uintptr_t)blockp, (uintptr_t)(blockp + 1), NULL, FALSE)) { malloc_zone_error(0, false, "Failed to madvise block at blockp: %p, error: %d\n", blockp, errno); } else { nanozone->statistics.size_class_statistics[size_class].madvised_blocks++; madvised = TRUE; } nanov2_block_meta_t final_meta = { .next_slot = SLOT_MADVISED, .gen_count = new_meta.gen_count + 1, }; if (!os_atomic_cmpxchgv(block_metap, new_meta, final_meta, &old_meta, relaxed)) { // This should not happen since we should have exclusive interest // in this block. malloc_zone_error(nanozone->debug_flags, false, "Failed when changing state from MADVISING to MADVISED, " "block_metap = %p, blockp = %p\n", block_metap, blockp); } } return madvised; } #endif // OS_VARIANT_RESOLVED #pragma mark - #pragma mark Region Management #if OS_VARIANT_NOTRESOLVED #if NANOV2_MULTIPLE_REGIONS static nanov2_addr_t nanov2_max_region_base = { .fields.nano_signature = NANOZONE_SIGNATURE, .fields.nano_region = NANOV2_MAX_REGION_NUMBER }; #endif // NANOV2_MULTIPLE_REGIONS // Attempts to allocate VM space for a region at a given address and returns // whether the allocation succeeded. static boolean_t nanov2_allocate_region(nanov2_region_t *region) { MALLOC_TRACE(TRACE_nanov2_region_allocation | DBG_FUNC_START, (uint64_t)region, 0, 0, 0); boolean_t result = nano_common_allocate_vm_space((mach_vm_address_t)region, NANOV2_REGION_SIZE); MALLOC_TRACE(TRACE_nanov2_region_allocation | DBG_FUNC_END, (uint64_t)region, result, 0, 0); return result; } // Allocates a new region adjacent to the current one. If the allocation fails, // keep sliding up by the size of a region until we either succeed or run out of // address space. The caller must own the Nanozone regions lock. boolean_t nanov2_allocate_new_region(nanozonev2_t *nanozone) { #if NANOV2_MULTIPLE_REGIONS boolean_t result = FALSE; _malloc_lock_assert_owner(&nanozone->regions_lock); nanov2_region_t *current_region = nanozone->current_region_base; nanov2_region_t *next_region = (nanov2_region_t *)nanozone->current_region_limit; while ((void *)next_region <= nanov2_max_region_base.addr) { if (nanov2_allocate_region(next_region)) { nanozone->current_region_base = next_region; nanozone->current_region_next_arena = (nanov2_arena_t *)next_region; nanozone->current_region_limit = next_region + 1; nanozone->statistics.allocated_regions++; result = TRUE; break; } next_region++; nanozone->statistics.region_address_clashes++; } if (result) { // Link this region to the previous one. nanov2_region_linkage_t *current_region_linkage = nanov2_region_linkage_for_region(nanozone, current_region); nanov2_region_linkage_t *next_region_linkage = nanov2_region_linkage_for_region(nanozone, next_region); uint16_t offset = next_region - current_region; current_region_linkage->next_region_offset = offset; next_region_linkage->next_region_offset = 0; } return result; #else // NANOV2_MULTIPLE_REGIONS // On iOS, only one region is supported, so we fail since the first // region is allocated separately. return FALSE; #endif // CONFIG_NANOV2_MULTIPLE_REGIONS } #endif // OS_VARIANT_NOTRESOLVED #pragma mark - #pragma mark Allocation #if OS_VARIANT_RESOLVED // Allocates memory from the block that corresponds to a given block meta data // pointer. The memory is taken from the free list if possible, or from the // unused region of the block if not. If the block is no longer in use or is // full, NULL is returned and the caller is expected to find another block to // allocate from. void * nanov2_allocate_from_block(nanozonev2_t *nanozone, nanov2_block_meta_t *block_metap, nanov2_size_class_t size_class) { nanov2_block_meta_view_t old_meta_view; old_meta_view.meta = os_atomic_load(block_metap, relaxed); // Calculating blockp and ptr is relatively expensive. Do both lazily to // minimize the time in the block starting with "again:" and ending with the // atomic update so that we lose at little time as possible if we have to // repeat that loop due to contention. This should also reduce the risk of // contention. nanov2_block_t *blockp = NULL; again: if (!nanov2_can_allocate_from_block(old_meta_view.meta)) { // Move along, nothing to allocate here... return NULL; } int slot; void *ptr = NULL; boolean_t from_free_list = FALSE; nanov2_block_meta_t new_meta = { .in_use = 1, .free_count = old_meta_view.meta.free_count - 1, .gen_count = old_meta_view.meta.gen_count + 1 }; // Grab a slot from the free list or get the next unused slot. We know there // should be one because the block is not full. boolean_t slot_full = old_meta_view.meta.free_count == 0; if (old_meta_view.meta.next_slot == SLOT_BUMP || old_meta_view.meta.next_slot == SLOT_CAN_MADVISE) { // Free list empty, grab the next unused slot. new_meta.next_slot = slot_full ? SLOT_FULL : SLOT_BUMP; slot = slots_by_size_class[size_class] - old_meta_view.meta.free_count - 1; } else { // Grab the first item from the free list. from_free_list = TRUE; if (!blockp) { blockp = nanov2_block_address_from_meta_ptr(nanozone, block_metap); } slot = old_meta_view.meta.next_slot - 1; // meta.next_slot is 1-based. ptr = nanov2_slot_in_block_ptr(blockp, size_class, slot); nanov2_free_slot_t *slotp = (nanov2_free_slot_t *)ptr; new_meta.next_slot = slot_full ? SLOT_FULL : slotp->next_slot; } // Write the updated meta data; try again if we raced with another thread. if (!os_atomic_cmpxchgv(block_metap, old_meta_view.meta, new_meta, &old_meta_view.meta, dependency)) { if (old_meta_view.meta.next_slot == SLOT_CAN_MADVISE || old_meta_view.meta.next_slot == SLOT_MADVISING || old_meta_view.meta.next_slot == SLOT_MADVISED) { _malloc_lock_lock(&nanozone->madvise_lock); if (old_meta_view.meta.next_slot == SLOT_MADVISED) { // We raced against another thread madvising this block. We need // to redo the madvise because we may have touched it when // reading the next pointer in the freelist. if (!blockp) { blockp = nanov2_block_address_from_meta_ptr(nanozone, block_metap); } if (mvm_madvise_free(nanozone, nanov2_region_address_for_ptr(blockp), (uintptr_t)blockp, (uintptr_t)(blockp + 1), NULL, FALSE)) { malloc_zone_error(0, false, "Failed to remadvise block at blockp: %p, error: %d\n", blockp, errno); } } _malloc_lock_unlock(&nanozone->madvise_lock); } goto again; } if (!ptr) { if (!blockp) { blockp = nanov2_block_address_from_meta_ptr(nanozone, block_metap); } ptr = nanov2_slot_in_block_ptr(blockp, size_class, slot); } nanov2_free_slot_t *slotp = (nanov2_free_slot_t *)os_atomic_force_dependency_on(ptr, (unsigned long)old_meta_view.bits); if (from_free_list) { // We grabbed the item from the free list. Check the free list canary // and crash if it's not valid. We can't do this check before the // cmpxchgv because another thread may race with us, claim the slot and // write to it. uintptr_t guard = os_atomic_load(&slotp->double_free_guard, relaxed); if ((guard ^ nanozone->slot_freelist_cookie) != (uintptr_t)ptr) { malloc_zone_error(MALLOC_ABORT_ON_CORRUPTION, false, "Heap corruption detected, free list is damaged at %p\n" "*** Incorrect guard value: %lu\n", ptr, guard); __builtin_unreachable(); } } // Reset the canary value so that the slot no longer looks free. os_atomic_store(&slotp->double_free_guard, 0, relaxed); #if DEBUG_MALLOC nanozone->statistics.size_class_statistics[size_class].total_allocations++; #endif // DEBUG_MALLOC return ptr; } // Finds a block for allocation in an arena and returns a pointer to its // metadata header. The search begins from the block with metadata pointer // start_block (which must not be NULL). If no acceptable block was found, // NULL is returned and it is expected that the caller will take appropriate // action (typically allocate a new arena). // // The search starts with start_block. If this is in-use and not full, that // block is returned. Otherwise, a scan for a usable block is initiated. The // search starts from start_block and initially works backward towards the // start of the arena. If this does not succeed, a forward search from // start_block is made. // // A block is considered a candidate if it is not in use. As the scan proceeds, // we remember blocks which have been madvisable, blocks which have been // madvised or never used and those blocks which still have allocated slots // but which fall within the reuse criteria (i.e. their occupancy is within the // max/min occupancy range). // // If the scan policy is NANO_SCAN_FIRST_FIT, we just return the first block // from the above list that we find. This is the fastest option, but likely // maximises fragmentation. // // Otherwise, the scan policy is NANO_SCAN_CAPACITY_BASED. If we find a block // that fits the reuse criteria, we return it immediately. Otherwise, we // continue to scan until we find such a block, or we find a less ideal block // and we reach the scan limit or exhaust the arena. At that point, we return // one of the candidate blocks that we found, choosing based on the state of // that block: // - blocks that have allocations that are greater than the minimum capacity // are preferred. // - failing that, return an unused or madvise'd block. // - failing that, return a block that is waiting to be madvised. // // In order to avoid races, this function must be called with the // current_block_lock for the calling context [CPU] and size class locked. // On return, the selected block has been marked as in-use, so the caller must // either assign it as the active allocation block for the calling context or // clear the in-use bit. // MALLOC_ALWAYS_INLINE MALLOC_INLINE nanov2_block_meta_t * nanov2_find_block_in_arena(nanozonev2_t *nanozone, nanov2_arena_t *arena, nanov2_size_class_t size_class, nanov2_block_meta_t *start_block) { // If we don't have a starting point, start with the first block in the // arena for the given size class. This is the case where we are looking for // the first allocation block for a new context (i.e probably a new CPU, so // take the first fit to avoid having to scan the whole size class for this // very common start up case.) boolean_t use_first_fit = !start_block || nanov2_policy_config.block_scan_policy == NANO_SCAN_FIRST_FIT; nanov2_block_meta_t *first_block = nanov2_first_block_for_size_class_in_arena( nanozone, size_class, arena); boolean_t scanning_backwards; if (!start_block) { start_block = first_block; } int slots_in_block = slots_by_size_class[size_class]; nanov2_block_meta_t old_meta; nanov2_block_meta_t *this_block; nanov2_block_meta_t *found_block; nanov2_block_meta_t *madvisable_block; nanov2_block_meta_t *free_block; nanov2_block_meta_t *fallback_block; boolean_t fallback_below_max; int scan_limit; // Check all of the blocks in the size class until we find one that we can // use, based on nanov2_block_scan_policy. retry: this_block = start_block; found_block = NULL; madvisable_block = NULL; free_block = NULL; fallback_block = NULL; fallback_below_max = FALSE; scan_limit = nanov2_policy_config.block_scan_limit; scanning_backwards = TRUE; do { old_meta = os_atomic_load(this_block, relaxed); if (!old_meta.in_use && old_meta.next_slot != SLOT_FULL && old_meta.next_slot != SLOT_MADVISING) { if (old_meta.next_slot == SLOT_CAN_MADVISE) { if (!madvisable_block) { // We can use this block as a last-ditch fallback. madvisable_block = this_block; } } else if (old_meta.next_slot == SLOT_NULL || old_meta.next_slot == SLOT_MADVISED) { if (!free_block) { free_block = this_block; } } else if (use_first_fit) { found_block = this_block; } else { MALLOC_ASSERT(nanov2_policy_config.block_scan_policy == NANO_SCAN_CAPACITY_BASED); int percent_used = (100 * old_meta.free_count)/slots_in_block; if (percent_used >= nanov2_policy_config.block_scan_min_capacity && percent_used <= nanov2_policy_config.block_scan_max_capacity) { // Within specified limits -- take this one. found_block = this_block; } else if (percent_used >= nanov2_policy_config.block_scan_min_capacity) { if (!fallback_block || fallback_below_max) { // More full than we want, but still acceptable as a // fallback. fallback_block = this_block; } } else if (!fallback_block && percent_used < nanov2_policy_config.block_scan_min_capacity) { // Less full than we want. Keep it as a backup, but set // fallback_below_max to allow a block that's above max to // be preferred. The rationale behind this is to allow // blocks that have low occupancy to drain so that they can // be madvised. fallback_block = this_block; fallback_below_max = TRUE; } else if (!free_block) { // Not ideal, but we could use it. free_block = this_block; } } if (use_first_fit && (found_block || fallback_block || free_block)) { // Take whatever we got. break; } } if (scan_limit > 0) { // Only enforce the scan limit once we have a candidate. if ((fallback_block || free_block) && --scan_limit == 0) { break; } } if (scanning_backwards) { boolean_t wrapped; nanov2_block_meta_t *prev_block = nanov2_previous_block_for_size_class( nanozone, size_class, this_block, &wrapped); if (wrapped) { // We wrapped. Scan forward from the start block instead. scan_limit = nanov2_policy_config.block_scan_limit; scanning_backwards = FALSE; this_block = start_block; } else { this_block = prev_block; } } else { // Move to the next block, wrapping when we reach the last one for // this size class. Stop once we get to the block where we started. this_block = nanov2_next_block_for_size_class(nanozone, size_class, this_block, NULL); if (this_block == start_block) { break; } } } while (!found_block); if (!found_block) { if (fallback_block) { found_block = fallback_block; } else if (free_block) { found_block = free_block; } else if (madvisable_block) { found_block = madvisable_block; } } if (found_block) { // Now we need to activate the block. If this fails, we look for // another block. // If we are bringing a block that is draining back into use, we // just need to set in_use to 1. Otherwise, we fully initialize it. old_meta = os_atomic_load(found_block, relaxed); if (old_meta.next_slot == SLOT_MADVISING) { goto retry; } boolean_t reset_slot = old_meta.next_slot == SLOT_NULL || old_meta.next_slot == SLOT_CAN_MADVISE || old_meta.next_slot == SLOT_MADVISED; nanov2_block_meta_t new_meta = { .in_use = 1, .free_count = reset_slot ? slots_in_block - 1 : old_meta.free_count, .next_slot = reset_slot ? SLOT_BUMP : old_meta.next_slot, .gen_count = reset_slot ? 0 : old_meta.gen_count + 1, }; if (!os_atomic_cmpxchgv(found_block, old_meta, new_meta, &old_meta, relaxed)) { goto retry; } } return found_block; } // Finds a block to allocate from and allocates memory from it. The search // for a block starts from *block_metapp if not NULL, otherwise from the first // arena in the first block (which is the case when the first block is allocated // for a size class for a CPU). // If none of the blocks for a size class in the current arena can be used, a // new arena is allocated and, if necessary, a new region is added. // // The address of the allocated memory is returned and its metadata pointer is // stored in *block_metapp. If a new region is required and it can't be // allocated, NULL is returned and *block_metapp is unmodified. // // On success, the returned block is marked as in-use and the block originally // pointed to by *block_metapp has its in-use bit cleared. // // In order to avoid races, this function must be called with the // current_block_lock for the calling context [CPU] and size class locked. MALLOC_NOINLINE void * nanov2_find_block_and_allocate(nanozonev2_t *nanozone, nanov2_size_class_t size_class, nanov2_block_meta_t **block_metapp) { nanov2_arena_t *arena; nanov2_block_meta_t *start_block = os_atomic_load(block_metapp, relaxed); nanov2_block_meta_t *orig_block = start_block; if (start_block) { // Use the arena for the starting block. arena = nanov2_arena_address_for_ptr(start_block); } else { // Start from the first arena. arena = nanov2_arena_address_for_ptr(nanozone->first_region_base); } nanov2_region_t *start_region; retry: start_region = nanov2_region_address_for_ptr(arena); nanov2_arena_t *start_arena = arena; nanov2_region_t *region = start_region; nanov2_arena_t *limit_arena = nanov2_limit_arena_for_region(nanozone, start_region); nanov2_arena_t *initial_region_next_arena = nanozone->current_region_next_arena; do { nanov2_block_meta_t *block_metap = nanov2_find_block_in_arena(nanozone, arena, size_class, start_block); if (block_metap) { // Try to allocate from this block and return if it succeeds. Note // that the block is now marked as in-use, so effectively belongs // to the calling context. void *ptr = nanov2_allocate_from_block(nanozone, block_metap, size_class); if (ptr) { // Make the new block the current one for the calling context. os_atomic_store(block_metapp, block_metap, relaxed); // Turn off in-use in old block_metap, if there is one. if (orig_block) { // Turn off in-use in the original current block. nanov2_turn_off_in_use(orig_block); } return ptr; } // We found a block but failed to allocate from it, probably because // it became full. Look for a new block, using the one that we just // failed with as the starting point. First, we need to turn off the // in-use bit for the block that we just failed to allocate from. nanov2_turn_off_in_use(block_metap); start_block = block_metap; goto retry; } // Try the next arena. If this is the last arena in the region, try the // next region. start_block = NULL; arena++; if (arena >= limit_arena) { region = nanov2_next_region_for_region(nanozone, region); if (!region) { // Reached the last region -- loop back to the first. region = nanozone->first_region_base; } arena = nanov2_first_arena_for_region(region); limit_arena = nanov2_limit_arena_for_region(nanozone, region); } } while (arena != start_arena); // If we get to this point, we need to allocate a new arena and possibly // a new region. If we are not permitted to do so by policy, return NULL. if (nanov2_policy_config.single_arena_size_classes & (1 << size_class)) { return NULL; } // Allocate a new arena and maybe a new region. To do either of those // things, we need to take the regions_lock. After doing so, check that // the state is unchanged. If it has, just assume that we might have some // new space to allocate into and try again. boolean_t failed = FALSE; arena = initial_region_next_arena; _malloc_lock_lock(&nanozone->regions_lock); if (nanozone->current_region_next_arena == arena) { if ((void *)arena >= nanozone->current_region_limit) { // Reached the end of the region. Allocate a new one, if we can. if (nanov2_allocate_new_region(nanozone)) { arena = nanozone->current_region_next_arena++; } else { failed = TRUE; } } else { // Assign the new arena, in the same region. nanozone->current_region_next_arena = arena + 1; } } _malloc_lock_unlock(&nanozone->regions_lock); if (!failed) { // Now allocate from the new arena. Since we updated the nanozone, it's // possible that some other thread has already raced with us to allocate // some space from it, so just use the normal allocation path to avoid // assumptions. It's a little more expensive, but this path is rare. start_block = NULL; goto retry; } // We need more space and we can't get it. We'll delegate to the helper. return NULL; } // Allocates memory of a given size (which must be a multiple of the Nano // quantum size) and optionally clears it (for calloc). // // Allocation is attempted first from the block last used for the caller's // context (which is initially the physical CPU by default). If there is no // last block, or the block is full or now out of use, find another one, if // possible. See the comments for nanov2_get_allocation_block() for the details. // // If the allocation fails, NULL is returned. void * nanov2_allocate(nanozonev2_t *nanozone, size_t rounded_size, boolean_t clear) { void *ptr = NULL; nanov2_size_class_t size_class = nanov2_size_class_from_size(rounded_size); MALLOC_ASSERT(size_class < NANO_SIZE_CLASSES); MALLOC_ASSERT(rounded_size != 0); nanov2_block_meta_t *block_metap; nanov2_block_meta_t **block_metapp; // Get the index of the pointer to the block from which we are should be // allocating. This currently depends on the physical CPU number. int allocation_index = nanov2_get_allocation_block_index() & MAX_CURRENT_BLOCKS_MASK; // Get the current allocation block meta data pointer. If this is NULL, // we need to find a new allocation block. block_metapp = &nanozone->current_block[size_class][allocation_index]; block_metap = os_atomic_load(block_metapp, relaxed); if (block_metap) { // Fast path: we have a block -- try to allocate from it. ptr = nanov2_allocate_from_block(nanozone, block_metap, size_class); if (ptr) { goto done; } } // No current allocation block, or we were unable to allocate. We need to // get a new block. Before doing so, delegate to the helper allocator if // the size class was full and has not released enough memory yet. if (nanozone->delegate_allocations & (1 << size_class)) { ptr = nanozone->helper_zone->malloc(nanozone->helper_zone, rounded_size); goto done; } // Before we try to get another block, lock and try another allocation, // which may succeed because another thread may have beaten us to it, or // some space may have freed up in the current block. _malloc_lock_s *lock = &nanozone->current_block_lock[size_class][allocation_index]; _malloc_lock_lock(lock); block_metap = os_atomic_load(block_metapp, relaxed); if (block_metap) { ptr = nanov2_allocate_from_block(nanozone, block_metap, size_class); if (ptr) { // Good to go - keep the current block. goto unlock; } } // At this point, we do not have a current allocation block and the old one, // if there was one, has been marked as not in use. We need to find and // assign a new block. Since we have the lock, nobody else can change the // current_block pointer. ptr = nanov2_find_block_and_allocate(nanozone, size_class, block_metapp); unlock: _malloc_lock_unlock(lock); if (!ptr) { // We could not find a block to allocate from -- make future // allocations for this size class go to the helper zone until // we have enough free space. _malloc_lock_lock(&nanozone->delegate_allocations_lock); nanozone->delegate_allocations |= 1 << size_class; _malloc_lock_unlock(&nanozone->delegate_allocations_lock); } done: if (ptr) { if (clear) { memset(ptr, '\0', rounded_size); } else { // Always clear the double-free guard so that we can recognize that // this block is not on the free list. nanov2_free_slot_t *slotp = (nanov2_free_slot_t *)ptr; os_atomic_store(&slotp->double_free_guard, 0, relaxed); } } return ptr; } #pragma mark - #pragma mark Freeing // Frees an allocation to its owning block and updates the block's state. // If the block becomes empty, it is marked as SLOT_CAN_MADVISE and is // madvised immediately if the policy is NANO_MADVISE_IMMEDIATE. void nanov2_free_to_block(nanozonev2_t *nanozone, void *ptr, nanov2_size_class_t size_class) { nanov2_block_t *blockp = nanov2_block_address_for_ptr(ptr); nanov2_block_meta_t *block_metap = nanov2_meta_ptr_for_ptr(nanozone, ptr); // Release the slot memory onto the block's freelist. nanov2_block_meta_t old_meta = os_atomic_load(block_metap, relaxed); int slot_count = slots_by_size_class[size_class]; nanov2_block_meta_t new_meta; boolean_t was_full; again: was_full = old_meta.next_slot == SLOT_FULL; new_meta.free_count = old_meta.free_count + 1; new_meta.in_use = old_meta.in_use; new_meta.gen_count = old_meta.gen_count + 1; boolean_t freeing_last_active_slot = !was_full && new_meta.free_count == slots_by_size_class[size_class] - 1; if (freeing_last_active_slot) { // Releasing the last active slot onto the free list. Mark the block as // ready to be madvised if it's not in use, otherwise reset next_slot // to SLOT_BUMP. new_meta.next_slot = new_meta.in_use ? SLOT_BUMP : SLOT_CAN_MADVISE; // Write the updated meta data; try again if we raced with another thread. if (!os_atomic_cmpxchgv(block_metap, old_meta, new_meta, &old_meta, relaxed)) { goto again; } // If the block is now empty and it's not in use, madvise it if the policy // is to do so immediately. if (new_meta.next_slot == SLOT_CAN_MADVISE && nanov2_madvise_policy == NANO_MADVISE_IMMEDIATE) { _malloc_lock_lock(&nanozone->madvise_lock); nanov2_madvise_block(nanozone, block_metap, blockp, size_class); _malloc_lock_unlock(&nanozone->madvise_lock); } } else { int slot_index = nanov2_slot_index_in_block(blockp, size_class, ptr); new_meta.next_slot = slot_index + 1; // meta.next_slot is 1-based nanov2_free_slot_t *slotp = (nanov2_free_slot_t *)ptr; slotp->next_slot = was_full ? SLOT_BUMP : old_meta.next_slot; os_atomic_store(&slotp->double_free_guard, nanozone->slot_freelist_cookie ^ (uintptr_t)ptr, relaxed); // The double_free_guard change must be visible when the os_atomic_cmpxchgv // completes. // Write the updated meta data; try again if we raced with another thread. if (!os_atomic_cmpxchgv(block_metap, old_meta, new_meta, &old_meta, release)) { goto again; } } // If this size class has been marked as full and this block is below an // acceptable level of occupancy, turn off delegation to the helper. Do this // only if the block is not in-use, because an in-use block cannot be a // candidate when searching for a new block. uint16_t class_mask = 1 << size_class; if (!new_meta.in_use && (nanozone->delegate_allocations & class_mask) && (new_meta.free_count >= 0.75 * slot_count)) { _malloc_lock_lock(&nanozone->delegate_allocations_lock); nanozone->delegate_allocations &= ~class_mask; _malloc_lock_unlock(&nanozone->delegate_allocations_lock); } #if DEBUG_MALLOC nanozone->statistics.size_class_statistics[size_class].total_frees++; #endif // DEBUG_MALLOC } #endif // OS_VARIANT_RESOLVED #if OS_VARIANT_NOTRESOLVED #pragma mark - #pragma mark Zone Operations malloc_zone_t * nanov2_create_zone(malloc_zone_t *helper_zone, unsigned debug_flags) { // Note: It is important that nanov2_create_zone resets _malloc_engaged_nano // if it is unable to enable the nanozone (and chooses not to abort). As // several functions rely on _malloc_engaged_nano to determine if they // should manipulate the nanozone, and these should not run if we failed // to create the zone. MALLOC_ASSERT(_malloc_engaged_nano == NANO_V2); // Get memory for the zone and disable Nano if we fail. nanozonev2_t *nanozone = nano_common_allocate_based_pages( NANOZONEV2_ZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC, 0); if (!nanozone) { _malloc_engaged_nano = NANO_NONE; return NULL; } // Set up the basic_zone portion of the nanozonev2 structure nanozone->basic_zone.version = 10; nanozone->basic_zone.size = OS_RESOLVED_VARIANT_ADDR(nanov2_size); nanozone->basic_zone.malloc = OS_RESOLVED_VARIANT_ADDR(nanov2_malloc); nanozone->basic_zone.calloc = OS_RESOLVED_VARIANT_ADDR(nanov2_calloc); nanozone->basic_zone.valloc = (void *)nanov2_valloc; nanozone->basic_zone.free = OS_RESOLVED_VARIANT_ADDR(nanov2_free); nanozone->basic_zone.realloc = OS_RESOLVED_VARIANT_ADDR(nanov2_realloc); nanozone->basic_zone.destroy = (void *)nanov2_destroy; nanozone->basic_zone.batch_malloc = OS_RESOLVED_VARIANT_ADDR(nanov2_batch_malloc); nanozone->basic_zone.batch_free = OS_RESOLVED_VARIANT_ADDR(nanov2_batch_free); nanozone->basic_zone.introspect = (struct malloc_introspection_t *)&nanov2_introspect; nanozone->basic_zone.memalign = (void *)nanov2_memalign; nanozone->basic_zone.free_definite_size = OS_RESOLVED_VARIANT_ADDR(nanov2_free_definite_size); nanozone->basic_zone.pressure_relief = OS_RESOLVED_VARIANT_ADDR(nanov2_pressure_relief); nanozone->basic_zone.claimed_address = OS_RESOLVED_VARIANT_ADDR(nanov2_claimed_address); // Set these both to zero as required by CFAllocator. nanozone->basic_zone.reserved1 = 0; nanozone->basic_zone.reserved2 = 0; // Prevent overwriting the function pointers in basic_zone. mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ); // Nano V2 zone does not support MALLOC_ADD_GUARD_PAGES if (debug_flags & MALLOC_ADD_GUARD_PAGES) { malloc_report(ASL_LEVEL_INFO, "nano does not support guard pages\n"); debug_flags &= ~MALLOC_ADD_GUARD_PAGES; } // Set up the remainder of the nanozonev2 structure nanozone->debug_flags = debug_flags; nanozone->helper_zone = helper_zone; // Initialize the cookies used to detect double freeing and for the ASLR // scramble mapping. #define COOKIE_ENTROPY_MASK 0x0000ffffffff0000ULL #define DEFAULT_ENTROPY_BITS 0x0000DEADDEAD0000ULL uintptr_t cookie = (uintptr_t)malloc_entropy[0] & COOKIE_ENTROPY_MASK; if (!cookie) { cookie = malloc_entropy[1] & COOKIE_ENTROPY_MASK; if (!cookie) { // The cookie can't be zero, because it's used to compute the guard // value in free slots, so make sure we have a non-zero value. Using // a fixed value allows us to recognize that it isn't real entropy. cookie = DEFAULT_ENTROPY_BITS; } } nanozone->slot_freelist_cookie = cookie; // For the ASLR cookie, we take the top 12 bits of malloc_entropy[1] and // align it to the block field of a Nano address. nanozone->aslr_cookie = malloc_entropy[1] >> (64 - NANOV2_BLOCK_BITS); nanozone->aslr_cookie_aligned = nanozone->aslr_cookie << NANOV2_OFFSET_BITS; _malloc_lock_init(&nanozone->blocks_lock); _malloc_lock_init(&nanozone->regions_lock); _malloc_lock_init(&nanozone->madvise_lock); // Allocate the initial region. If this does not succeed, we disable Nano. nanov2_addr_t p = {.fields.nano_signature = NANOZONE_SIGNATURE}; nanov2_region_t *region = (nanov2_region_t *)p.addr; boolean_t result = nanov2_allocate_region(region); if (!result) { nano_common_deallocate_pages(nanozone, NANOZONEV2_ZONE_PAGED_SIZE, 0); _malloc_engaged_nano = NANO_NONE; malloc_report(ASL_LEVEL_NOTICE, "nano zone abandoned due to inability " "to preallocate reserved vm space.\n"); return NULL; } nanov2_region_linkage_t *region_linkage = nanov2_region_linkage_for_region(nanozone, region); region_linkage->next_region_offset = 0; // Install the first region and pre-allocate the first arena. nanozone->first_region_base = region; nanozone->current_region_base = region; nanozone->current_region_next_arena = ((nanov2_arena_t *)region) + 1; nanozone->current_region_limit = region + 1; nanozone->statistics.allocated_regions = 1; return (malloc_zone_t *)nanozone; } #endif // OS_VARIANT_NOTRESOLVED #pragma mark - #pragma mark Zone Fork Handling // Nanomalloc assumes that after a fork, it would be dangerous to rely on // the integrity of the zone data. During a fork, some of the zone handlers are // switched to the versions below, which do the following: // 1. Delegate all new allocation to the helper zone. // 2. Do nothing when asked to free memory that Nano allocated. There will be a // leak, but this is better than possibly crashing. #if OS_VARIANT_RESOLVED void * nanov2_forked_malloc(nanozonev2_t *nanozone, size_t size) { // Just hand to the helper zone. return nanozone->helper_zone->malloc(nanozone->helper_zone, size); } #endif // OS_VARIANT_RESOLVED #if OS_VARIANT_NOTRESOLVED static void * nanov2_forked_calloc(nanozonev2_t *nanozone, size_t num_items, size_t size) { // Just hand to the helper zone. return nanozone->helper_zone->calloc(nanozone->helper_zone, num_items, size); } #endif // OS_VARIANT_NOTRESOLVED #if OS_VARIANT_RESOLVED void nanov2_forked_free(nanozonev2_t *nanozone, void *ptr) { if (!ptr) { return; // Protect against malloc_zone_free() passing NULL. } // exhausting a slot may result in a pointer with // the nanozone prefix being given to nano_free via malloc_zone_free. Calling // vet_and_size here, instead of in _nano_free_check_scribble means we can // early-out into the helper_zone if it turns out nano does not own this ptr. size_t sz = nanov2_pointer_size(nanozone, ptr, FALSE); if (sz || nanov2_has_valid_signature(ptr)) { /* Drop it on the floor as nanozone metadata could be fouled by fork. */ return; } else { nanozone->helper_zone->free(nanozone->helper_zone, ptr); return; } /* NOTREACHED */ } void nanov2_forked_free_definite_size(nanozonev2_t *nanozone, void *ptr, size_t size) { nanov2_forked_free(nanozone, ptr); } void * nanov2_forked_realloc(nanozonev2_t *nanozone, void *ptr, size_t new_size) { // could occur through malloc_zone_realloc() path if (!ptr) { // If ptr is a null pointer, realloc() shall be equivalent to malloc() // for the specified size. return nanov2_forked_malloc(nanozone, new_size); } size_t old_size = nanov2_pointer_size(nanozone, ptr, FALSE); if (!old_size) { // not-nano pointer, hand down to helper zone malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->realloc(zone, ptr, new_size); } else { if (!new_size) { // If size is 0 and ptr is not a null pointer, the object pointed to // is freed. However as nanozone metadata could be fouled by fork, // we'll intentionally leak it. // If size is 0, either a null pointer or a unique pointer that can // be successfully passed to free() shall be returned. return nanov2_forked_malloc(nanozone, 1); } void *new_ptr = nanozone->helper_zone->malloc(nanozone->helper_zone, new_size); if (new_ptr) { size_t valid_size = MIN(old_size, new_size); memcpy(new_ptr, ptr, valid_size); // Original pointer is intentionally leaked as nanozone metadata // could be fouled by fork. return new_ptr; } else { // Original ptr is left intact return NULL; } /* NOTREACHED */ } /* NOTREACHED */ } #endif // OS_VARIANT_RESOLVED #if OS_VARIANT_NOTRESOLVED static unsigned nanov2_forked_batch_malloc(nanozonev2_t *nanozone, size_t size, void **results, unsigned count) { // Just pass this to the helper zone. return nanozone->helper_zone->batch_malloc(nanozone->helper_zone, size, results, count); } #endif // OS_VARIANT_NOTRESOLVED #if OS_VARIANT_RESOLVED void nanov2_forked_batch_free(nanozonev2_t *nanozone, void **to_be_freed, unsigned count) { if (!count) { return; } while (count--) { void *ptr = to_be_freed[count]; if (ptr) { nanov2_forked_free(nanozone, ptr); } } } #endif // OS_VARIANT_RESOLVED #if OS_VARIANT_NOTRESOLVED static boolean_t nanov2_forked_claimed_address(struct _malloc_zone_t *zone, void *ptr) { // This does not operate after fork - default to true to avoid // false negatives. return true; } void nanov2_forked_zone(nanozonev2_t *nanozone) { // Hobble the nano zone in the child of a fork prior to an exec since // the state of the zone can be made inconsistent by a parent thread while // the fork is underway. All new allocations will be referred to the helper // zone (which is more stable.) All free()'s of existing nano objects will // be leaked. mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ | PROT_WRITE); nanozone->basic_zone.size = OS_RESOLVED_VARIANT_ADDR(nanov2_size); // Unchanged nanozone->basic_zone.malloc = OS_RESOLVED_VARIANT_ADDR(nanov2_forked_malloc); nanozone->basic_zone.calloc = (void *)nanov2_forked_calloc; nanozone->basic_zone.valloc = (void *)nanov2_valloc; // Unchanged nanozone->basic_zone.free = OS_RESOLVED_VARIANT_ADDR(nanov2_forked_free); nanozone->basic_zone.realloc = OS_RESOLVED_VARIANT_ADDR(nanov2_forked_realloc); nanozone->basic_zone.destroy = (void *)nanov2_destroy; // Unchanged nanozone->basic_zone.batch_malloc = (void *)nanov2_forked_batch_malloc; nanozone->basic_zone.batch_free = OS_RESOLVED_VARIANT_ADDR(nanov2_forked_batch_free); nanozone->basic_zone.introspect = (struct malloc_introspection_t *)&nanov2_introspect;// Unchanged nanozone->basic_zone.memalign = (void *)nanov2_memalign; // Unchanged nanozone->basic_zone.free_definite_size = OS_RESOLVED_VARIANT_ADDR(nanov2_forked_free_definite_size); nanozone->basic_zone.claimed_address = nanov2_forked_claimed_address; mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ); } #endif // OS_VARIANT_NOTRESOLVED #endif // CONFIG_NANOZONE ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nanov2_malloc.h ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __NANOV2_MALLOC_H #define __NANOV2_MALLOC_H // Forward declaration for the nanozonev2 structure. typedef struct nanozonev2_s nanozonev2_t; MALLOC_NOEXPORT void nanov2_init(const char *envp[], const char *apple[], const char *bootargs); MALLOC_NOEXPORT void nanov2_configure(void); MALLOC_NOEXPORT malloc_zone_t * nanov2_create_zone(malloc_zone_t *helper_zone, unsigned debug_flags); MALLOC_NOEXPORT void nanov2_forked_zone(nanozonev2_t *nanozone); #endif // __NANOV2_MALLOC_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/nanov2_zone.h ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __NANOV2_ZONE_H #define __NANOV2_ZONE_H #if CONFIG_NANOZONE #pragma mark - #pragma mark Address Structure #if TARGET_OS_OSX || TARGET_OS_SIMULATOR #define NANOV2_REGION_BITS 15 #define NANOV2_ARENA_BITS 3 #define NANOV2_BLOCK_BITS 12 #define NANOV2_OFFSET_BITS 14 #else // TARGET_OS_OSX || TARGET_OS_SIMULATOR #define NANOV2_REGION_BITS 0 #define NANOV2_ARENA_BITS 3 #define NANOV2_BLOCK_BITS 12 #define NANOV2_OFFSET_BITS 14 #endif // TARGET_OS_OSX || TARGET_OS_SIMULATOR #if NANOV2_REGION_BITS > 0 #define NANOV2_MULTIPLE_REGIONS 1 #else // NANOV2_REGION_BITS > 0 #define NANOV2_MULTIPLE_REGIONS 0 #endif // NANOV2_REGION_BITS > 0 // Size of a block (currently 16KB) #define NANOV2_BLOCK_SIZE (1 << NANOV2_OFFSET_BITS) // Size of an arena (currently 64MB) #define NANOV2_ARENA_SIZE (64 * 1024 * 1024) // Size of a region (currently 512MB) #define NANOV2_REGION_SIZE (512 * 1024 * 1024) // Number of blocks per arena (currently 4096) #define NANOV2_BLOCKS_PER_ARENA (NANOV2_ARENA_SIZE/NANOV2_BLOCK_SIZE) // Number of arenas per region (currently 8) #define NANOV2_ARENAS_PER_REGION (NANOV2_REGION_SIZE/NANOV2_ARENA_SIZE) // Maximum number of slots per block #define NANOV2_MAX_SLOTS_PER_BLOCK (NANOV2_BLOCK_SIZE/NANO_REGIME_QUANTA_SIZE) // Highest region number. #if NANOV2_MULTIPLE_REGIONS #define NANOV2_MAX_REGION_NUMBER ((1 << NANOV2_REGION_BITS) - 1) #else // NANOV2_MULTIPLE_REGIONS #define NANOV2_MAX_REGION_NUMBER 0 #endif // NANOV2_MULTIPLE_REGIONS // clang-format really dislikes the bitfields here // clang-format off #if defined(__BIG_ENDIAN__) // Nano V2 address structure. struct nanov2_addr_s { uintptr_t nano_signature : NANOZONE_SIGNATURE_BITS; #if NANOV2_MULTIPLE_REGIONS uintptr_t nano_region: NANOV2_REGION_BITS; #endif // NANOV2_MULTIPLE_REGIONS uintptr_t nano_arena : NANOV2_ARENA_BITS; uintptr_t nano_block : NANOV2_BLOCK_BITS; uintptr_t nano_offset : NANOV2_OFFSET_BITS; }; MALLOC_STATIC_ASSERT(sizeof(struct nanov2_addr_s) == sizeof(uintptr_t), "Wrong size for nanov2_addr_s"); #else // defined(__BIG_ENDIAN__) // least significant bits declared first struct nanov2_addr_s { uintptr_t nano_offset : NANOV2_OFFSET_BITS; uintptr_t nano_block : NANOV2_BLOCK_BITS; uintptr_t nano_arena : NANOV2_ARENA_BITS; #if NANOV2_MULTIPLE_REGIONS uintptr_t nano_region: NANOV2_REGION_BITS; #endif // NANOV2_MULTIPLE_REGIONS uintptr_t nano_signature : NANOZONE_SIGNATURE_BITS; }; MALLOC_STATIC_ASSERT(sizeof(struct nanov2_addr_s) == sizeof(uintptr_t), "Wrong size for nanov2_addr_s"); #endif // defined(__BIG_ENDIAN__) // clang-format on // Union that allows easy extraction of the fields in a Nano V2 address. typedef union { void *addr; struct nanov2_addr_s fields; } nanov2_addr_t; // Typedef that tags a size class value. Range is 0 to NANO_SIZE_CLASSES - 1. typedef unsigned nanov2_size_class_t; #pragma mark - #pragma mark Block Definitions // A block is a chunk of NANOV2_BLOCK_SIZE bytes of memory. typedef struct { unsigned char content[NANOV2_BLOCK_SIZE]; } nanov2_block_t; MALLOC_STATIC_ASSERT(sizeof(nanov2_block_t) == NANOV2_BLOCK_SIZE, "nanov2_block_t must be the same size as a block"); #pragma mark - #pragma mark Arena and Block Definitions // An arena is an array of NANOV2_BLOCKS_PER_ARENA blocks. typedef struct { nanov2_block_t blocks[NANOV2_BLOCKS_PER_ARENA]; } nanov2_arena_t; MALLOC_STATIC_ASSERT(sizeof(nanov2_arena_t) == NANOV2_BLOCK_SIZE * NANOV2_BLOCKS_PER_ARENA, "nanov2_arena_t must be the same size as its blocks"); // Per-block header structure, embedded in the arena metadata block. typedef struct { uint32_t next_slot : 11; // Next slot on free list, 1-based. uint32_t free_count : 10; // Free slots in this block - 1 uint32_t gen_count : 10; // A-B-A count uint32_t in_use : 1; // Being used for allocations. } nanov2_block_meta_t; MALLOC_STATIC_ASSERT(sizeof(nanov2_block_meta_t) == sizeof(uint32_t), "Incorrect size for nanov2_block_meta_t"); // Distinguished values of next_slot #define SLOT_NULL 0 // Slot has never been used. #define SLOT_BUMP 0x7fb // Marks the end of the free list #define SLOT_FULL 0x7fc // Slot is full (no free slots) #define SLOT_CAN_MADVISE 0x7fd // Block can be madvised (and in_use == 0) #define SLOT_MADVISING 0x7fe // Block is being madvised. Do not touch #define SLOT_MADVISED 0x7ff // Block has been madvised. // View of the per-block header structure that allows it to be used where a // primitive type is required. typedef union { nanov2_block_meta_t meta; uint32_t bits; } nanov2_block_meta_view_t; // Structure overlaid onto an arena's metadata block. This must be exactly // the same size as a block. typedef struct { nanov2_block_meta_t arena_block_meta[NANOV2_BLOCKS_PER_ARENA]; } nanov2_arena_metablock_t; MALLOC_STATIC_ASSERT(sizeof(nanov2_arena_metablock_t) == NANOV2_BLOCK_SIZE, "nanov2_arena_metablock_t must be the same size as a block"); // Structure overlaid on slots that are on the block freelist. typedef struct { uint64_t double_free_guard; uint16_t next_slot; } nanov2_free_slot_t; MALLOC_STATIC_ASSERT( sizeof(nanov2_free_slot_t) <= NANO_REGIME_QUANTA_SIZE, "nanov2_free_slot_t too large"); // Type for the index of a block in its hosting arena. typedef unsigned nanov2_block_index_t; // Type for the index of a block meta structure in its hosting metadata block. typedef unsigned nanov2_meta_index_t; #pragma mark - #pragma mark Region Definitions // A region is an array of NANOV2_ARENAS_PER_REGION arenas. typedef struct { nanov2_arena_t arenas[NANOV2_ARENAS_PER_REGION]; } nanov2_region_t; // Linkage between regions. Overlays the nanov2_block_meta_t that corresponds // to the arena metadata block, so must be the same size as nanov2_block_meta_t. typedef struct { uint16_t next_region_offset; // Offset to next region in 512MB blocks uint16_t unused; } nanov2_region_linkage_t; MALLOC_STATIC_ASSERT( sizeof (nanov2_block_meta_t) == sizeof(nanov2_region_linkage_t), "nanov2_block_meta_t must be the same size as nanov2_region_linkage_t"); #pragma mark - #pragma mark Statistics typedef struct { uint64_t total_allocations; uint64_t total_frees; uint64_t madvised_blocks; // Does not reduce when reused. uint64_t madvise_races; // Reused while being madvised. } nanov2_size_class_statistics; typedef struct { // Number of allocated regions unsigned allocated_regions; // Number of times a region could not be placed at its preferred location unsigned region_address_clashes; // Statistics collected by size class nanov2_size_class_statistics size_class_statistics[NANO_SIZE_CLASSES]; } nanov2_statistics_t; #pragma mark - #pragma mark Zone Definitions // Maximum number of currently active allocation blocks per size class. // Initially, the default is for each physical CPU to have a dedicated block. #define MAX_CURRENT_BLOCKS 16 #define MAX_CURRENT_BLOCKS_MASK (MAX_CURRENT_BLOCKS - 1) MALLOC_STATIC_ASSERT(MAX_CURRENT_BLOCKS > 1 && !(MAX_CURRENT_BLOCKS & MAX_CURRENT_BLOCKS_MASK), "MAX_CURRENT_BLOCKS must be a power of 2"); typedef struct nanozonev2_s { // first page will be given read-only protection malloc_zone_t basic_zone; uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)]; // Current allocation blocks. Indexed by a factor that may change in the // future (and may be tuneable). Initially indexed by physical CPU number. nanov2_block_meta_t *current_block[NANO_SIZE_CLASSES][MAX_CURRENT_BLOCKS]; // Locks for the current allocation blocks. _malloc_lock_s current_block_lock[NANO_SIZE_CLASSES][MAX_CURRENT_BLOCKS]; // Lock for delegate_allocations. _malloc_lock_s delegate_allocations_lock; // Mask of size classes for which allocation should be delegated when a new // block is needed and the class has become full. uint16_t delegate_allocations; // Zone debug flags. unsigned debug_flags; // Cookie used for ASLR within an arena. uint64_t aslr_cookie; uint64_t aslr_cookie_aligned; // cookie used to protect linkage on the block freelist uintptr_t slot_freelist_cookie; // The zone to which allocations that cannot be satisfied by Nano V2 // will be handed off. malloc_zone_t *helper_zone; // Lock used to serialize access to current_block. _malloc_lock_s blocks_lock; // Lock used to protect current_region_base, current_region_next_arena and // current_region_limit. _malloc_lock_s regions_lock; // Base address of the first region. Fixed once set. nanov2_region_t *first_region_base; // Base address of the current region. Always the most recently allocated // region and therefore the one with the highest base address. nanov2_region_t *current_region_base; // Address to use for the next arena. Always between current_region_base // and current_region_limit. nanov2_arena_t *current_region_next_arena; // Limit address of the current region (first byte after the region). void *current_region_limit; // Lock used when madvising. _malloc_lock_s madvise_lock; // Global and per-size class statistics nanov2_statistics_t statistics; } nanozonev2_t; #define NANOZONEV2_ZONE_PAGED_SIZE mach_vm_round_page(sizeof(nanozonev2_t)) #pragma mark - #pragma mark Address Manipulation #define NANOV2_BLOCK_ADDRESS_MASK ~((1ULL << (NANOV2_OFFSET_BITS)) - 1) #define NANOV2_ARENA_ADDRESS_MASK \ ~((1ULL << (NANOV2_BLOCK_BITS + NANOV2_OFFSET_BITS)) - 1) #define NANOV2_REGION_ADDRESS_MASK \ ~((1ULL << (NANOV2_ARENA_BITS + NANOV2_BLOCK_BITS + NANOV2_OFFSET_BITS)) - 1) #endif // CONFIG_NANOZONE #endif // __NANOV2_ZONE_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/platform.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __PLATFORM_H #define __PLATFORM_H #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR #define MALLOC_TARGET_IOS 1 #else // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR #define MALLOC_TARGET_IOS 0 #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR #ifdef __LP64__ #define MALLOC_TARGET_64BIT 1 #else // __LP64__ #define MALLOC_TARGET_64BIT 0 #endif // #if MALLOC_TARGET_IOS # define CONFIG_MADVISE_PRESSURE_RELIEF 0 #else // MALLOC_TARGET_IOS # define CONFIG_MADVISE_PRESSURE_RELIEF 1 #endif // MALLOC_TARGET_IOS // #if MALLOC_TARGET_IOS # define CONFIG_RECIRC_DEPOT 1 # define CONFIG_AGGRESSIVE_MADVISE 1 #else // MALLOC_TARGET_IOS # define CONFIG_RECIRC_DEPOT 1 # define CONFIG_AGGRESSIVE_MADVISE 0 #endif // MALLOC_TARGET_IOS // #define CONFIG_RELAXED_INVARIANT_CHECKS 1 // #define CONFIG_MADVISE_STYLE MADV_FREE_REUSABLE #if MALLOC_TARGET_64BIT #define CONFIG_NANOZONE 1 #define CONFIG_ASLR_INTERNAL 0 #else // MALLOC_TARGET_64BIT #define CONFIG_NANOZONE 0 #define CONFIG_ASLR_INTERNAL 1 #endif // MALLOC_TARGET_64BIT // enable nano checking for corrupt free list #define NANO_FREE_DEQUEUE_DILIGENCE 1 // This governs a last-free cache of 1 that bypasses the free-list for each region size #define CONFIG_TINY_CACHE 1 #define CONFIG_SMALL_CACHE 1 // The large last-free cache (aka. death row cache) #if MALLOC_TARGET_IOS #define CONFIG_LARGE_CACHE 0 #else #define CONFIG_LARGE_CACHE 1 #endif // compile-time MALLOC_SMALL cut-off size #if MALLOC_TARGET_IOS #if MALLOC_TARGET_64BIT MALLOC_STATIC_ASSERT(PAGE_MAX_SIZE == 16 * 1024, "Expected 16k pages"); // TODO: rdar://problem/35395572 #define CONFIG_SMALL_CUTOFF_DYNAMIC 0 #endif #else #define CONFIG_SMALL_CUTOFF_LARGEMEM 1 #endif // MALLOC_TARGET_IOS // memory resource exception handling #if MALLOC_TARGET_IOS || TARGET_OS_SIMULATOR #define ENABLE_MEMORY_RESOURCE_EXCEPTION_HANDLING 0 #else #define ENABLE_MEMORY_RESOURCE_EXCEPTION_HANDLING 1 #endif // presence of commpage memsize #define CONFIG_HAS_COMMPAGE_MEMSIZE 1 // presence of commpage number of cpu count #define CONFIG_HAS_COMMPAGE_NCPUS 1 // Use of hyper-shift for magazine selection. #define CONFIG_TINY_USES_HYPER_SHIFT 0 #define CONFIG_SMALL_USES_HYPER_SHIFT 0 #endif // __PLATFORM_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/printf.h ================================================ /* * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #define MALLOC_REPORT_NOLOG 0x10 #define MALLOC_REPORT_NOPREFIX 0x20 #define MALLOC_REPORT_CRASH 0x40 #define MALLOC_REPORT_DEBUG 0x80 // Most internal logging should use malloc_report() or malloc_vreport(). The // flags argument should be a combination of the MALLOC_REPORT_xxx values and // an optional log level encoded using the ASL_LEVEL_xxx constants. The log // level is ignored if MALLOC_REPORT_NOLOG is set. // // The flags do the following: // MALLOC_REPORT_NOLOG: // Does not send the text to _simple_asl_log(). // MALLOC_REPORT_NO_PREFIX: // Does not write the program name, pid and thread identifier before // the report text. // MALLOC_REPORTDEBUG: // includes text suggesting that a breakpoint could be set // on malloc_error_break() to debug this kind of error. // MALLOC_REPORT_CRASH: // Same as MALLOC_REPORTDEBUG, but crashes after writing the report // message. // // In addition, if MALLOC_REPORT_CRASH or MALLOC_REPORTDEBUG are specified, this // function will sleep for an hour or send a SIGSTOP signal to the process if // the MallocErrorSleep and MallocErrorStop environment variables were set and // the report text will include a message indicating that this is // happening. In the case of MALLOC_REPORT_CRASH, the crash occurs after all of // the other actions have completed. MALLOC_NOEXPORT MALLOC_NOINLINE void malloc_report(uint32_t flags, const char *fmt, ...) __printflike(2,3); // Like malloc_report(), but precedes the output message with prefix_msg // as a format string using prefix_arg as a single substition parameter, // allows the length of time to sleep while reporting an error to be // specified and passes the arguments to the fmt parameter in a va_list. MALLOC_NOEXPORT MALLOC_NOINLINE void malloc_vreport(uint32_t flags, unsigned sleep_time, const char *prefix_msg, const void *prefix_arg, const char *fmt, va_list ap); // Higher-level functions used by zone implementations to report errors. MALLOC_NOEXPORT MALLOC_NOINLINE void malloc_zone_error(uint32_t flags, bool is_corruption, const char *fmt, ...) __printflike(3,4); MALLOC_NOEXPORT MALLOC_NOINLINE void malloc_zone_check_fail(const char *msg, const char *fmt, ...) __printflike(2,3); // Configures where malloc logging goes based on environment variables. By // default, goes to stderr if it's a tty, and is otherwise dropped. MALLOC_NOEXPORT void malloc_print_configure(bool restricted); ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/purgeable_malloc.c ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" // // purgeable zones have their own "large" allocation pool, but share "tiny" and "large" // heaps with a helper_zone identified in the call to create_purgeable_zone() // static size_t purgeable_size(szone_t *szone, const void *ptr) { // Only claim our large allocations, leave the shared tiny/small for the helper zone to claim. return szone_size_try_large(szone, ptr); } static void * purgeable_malloc(szone_t *szone, size_t size) { if (size <= szone->large_threshold) { return szone_malloc(szone->helper_zone, size); } else { return szone_malloc(szone, size); } } static void * purgeable_calloc(szone_t *szone, size_t num_items, size_t size) { size_t total_bytes; if (calloc_get_size(num_items, size, 0, &total_bytes)) { return NULL; } if (total_bytes <= szone->large_threshold) { return szone_calloc(szone->helper_zone, 1, total_bytes); } else { return szone_calloc(szone, 1, total_bytes); } } static void * purgeable_valloc(szone_t *szone, size_t size) { if (size <= szone->large_threshold) { return szone_valloc(szone->helper_zone, size); } else { return szone_valloc(szone, size); } } static void purgeable_free(szone_t *szone, void *ptr) { large_entry_t *entry; SZONE_LOCK(szone); entry = large_entry_for_pointer_no_lock(szone, ptr); SZONE_UNLOCK(szone); if (entry) { return free_large(szone, ptr); } else { return szone_free(szone->helper_zone, ptr); } } static void purgeable_free_definite_size(szone_t *szone, void *ptr, size_t size) { if (size <= szone->large_threshold) { return szone_free_definite_size(szone->helper_zone, ptr, size); } else { return szone_free_definite_size(szone, ptr, size); } } static void * purgeable_realloc(szone_t *szone, void *ptr, size_t new_size) { size_t old_size; if (NULL == ptr) { // If ptr is a null pointer, realloc() shall be equivalent to malloc() for the specified size. return purgeable_malloc(szone, new_size); } else if (0 == new_size) { // If size is 0 and ptr is not a null pointer, the object pointed to is freed. purgeable_free(szone, ptr); // If size is 0, either a null pointer or a unique pointer that can be successfully passed // to free() shall be returned. return purgeable_malloc(szone, 1); } old_size = purgeable_size(szone, ptr); // Now ptr can be safely size()'d if (!old_size) { old_size = szone_size(szone->helper_zone, ptr); } if (!old_size) { malloc_zone_error(szone->debug_flags, true, "pointer %p being reallocated was not allocated\n", ptr); return NULL; } // Distinguish 4 cases: {oldsize, newsize} x { <= , > large_threshold } // and deal with the allocation crossing from the purgeable zone to the helper zone and vice versa. if (old_size <= szone->large_threshold) { if (new_size <= szone->large_threshold) { return szone_realloc(szone->helper_zone, ptr, new_size); } else { // allocation crosses from helper to purgeable zone void *new_ptr = purgeable_malloc(szone, new_size); if (new_ptr) { memcpy(new_ptr, ptr, old_size); szone_free_definite_size(szone->helper_zone, ptr, old_size); } return new_ptr; // in state VM_PURGABLE_NONVOLATILE } } else { if (new_size <= szone->large_threshold) { // allocation crosses from purgeable to helper zone void *new_ptr = szone_malloc(szone->helper_zone, new_size); if (new_ptr) { memcpy(new_ptr, ptr, new_size); purgeable_free_definite_size(szone, ptr, old_size); } return new_ptr; } else { void *new_ptr = purgeable_malloc(szone, new_size); if (new_ptr) { memcpy(new_ptr, ptr, MIN(old_size, new_size)); purgeable_free_definite_size(szone, ptr, old_size); } return new_ptr; // in state VM_PURGABLE_NONVOLATILE } } /* NOTREACHED */ } static void purgeable_destroy(szone_t *szone) { /* destroy large entries */ size_t index = szone->num_large_entries; large_entry_t *large; vm_range_t range_to_deallocate; while (index--) { large = szone->large_entries + index; if (large->address) { // we deallocate_pages, including guard pages mvm_deallocate_pages((void *)(large->address), large->size, szone->debug_flags); } } large_entries_free_no_lock(szone, szone->large_entries, szone->num_large_entries, &range_to_deallocate); if (range_to_deallocate.size) { mvm_deallocate_pages((void *)range_to_deallocate.address, (size_t)range_to_deallocate.size, 0); } /* Now destroy the separate szone region */ mvm_deallocate_pages((void *)szone, SZONE_PAGED_SIZE, 0); } static unsigned purgeable_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count) { return szone_batch_malloc(szone->helper_zone, size, results, count); } static void purgeable_batch_free(szone_t *szone, void **to_be_freed, unsigned count) { return szone_batch_free(szone->helper_zone, to_be_freed, count); } static void * purgeable_memalign(szone_t *szone, size_t alignment, size_t size) { if (size <= szone->large_threshold) { return szone_memalign(szone->helper_zone, alignment, size); } else { return szone_memalign(szone, alignment, size); } } static kern_return_t purgeable_ptr_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder) { szone_t *szone; kern_return_t err; if (!reader) { reader = _szone_default_reader; } err = reader(task, zone_address, sizeof(szone_t), (void **)&szone); if (err) { return err; } err = large_in_use_enumerator( task, context, type_mask, (vm_address_t)szone->large_entries, szone->num_large_entries, reader, recorder); return err; } static size_t purgeable_good_size(szone_t *szone, size_t size) { if (size <= szone->large_threshold) { return szone_good_size(szone->helper_zone, size); } else { return szone_good_size(szone, size); } } static boolean_t purgeable_check(szone_t *szone) { return 1; } static void purgeable_print(szone_t *szone, boolean_t verbose) { malloc_report(MALLOC_REPORT_NOLOG | MALLOC_REPORT_NOPREFIX, "Scalable zone %p: inUse=%u(%y) flags=%d\n", szone, szone->num_large_objects_in_use, (int)szone->num_bytes_in_large_objects, szone->debug_flags); } static void purgeable_log(malloc_zone_t *zone, void *log_address) { szone_t *szone = (szone_t *)zone; szone->log_address = log_address; } static void purgeable_force_lock(szone_t *szone) { SZONE_LOCK(szone); } static void purgeable_force_unlock(szone_t *szone) { SZONE_UNLOCK(szone); } static void purgeable_reinit_lock(szone_t *szone) { SZONE_REINIT_LOCK(szone); } static void purgeable_statistics(szone_t *szone, malloc_statistics_t *stats) { stats->blocks_in_use = szone->num_large_objects_in_use; stats->size_in_use = stats->max_size_in_use = stats->size_allocated = szone->num_bytes_in_large_objects; } static boolean_t purgeable_locked(szone_t *szone) { int tookLock; tookLock = SZONE_TRY_LOCK(szone); if (tookLock == 0) { return 1; } SZONE_UNLOCK(szone); return 0; } static size_t purgeable_pressure_relief(szone_t *szone, size_t goal) { return szone_pressure_relief(szone, goal) + szone_pressure_relief(szone->helper_zone, goal); } static const struct malloc_introspection_t purgeable_introspect = { (void *)purgeable_ptr_in_use_enumerator, (void *)purgeable_good_size, (void *)purgeable_check, (void *)purgeable_print, purgeable_log, (void *)purgeable_force_lock, (void *)purgeable_force_unlock, (void *)purgeable_statistics, (void *)purgeable_locked, NULL, NULL, NULL, NULL, /* Zone enumeration version 7 and forward. */ (void *)purgeable_reinit_lock, // reinit_lock version 9 and foward }; // marked as const to spare the DATA section static boolean_t purgeable_claimed_address(szone_t *szone, void *ptr) { return szone_claimed_address(szone->helper_zone, ptr); } malloc_zone_t * create_purgeable_zone(size_t initial_size, malloc_zone_t *malloc_default_zone, unsigned debug_flags) { szone_t *szone; uint64_t hw_memsize = 0; /* get memory for the zone. */ szone = mvm_allocate_pages(SZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC); if (!szone) { return NULL; } /* set up the szone structure */ #if 0 #warning LOG enabled szone->log_address = ~0; #endif #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__arm64__) hw_memsize = *(uint64_t *)(uintptr_t)_COMM_PAGE_MEMORY_SIZE; #else size_t uint64_t_size = sizeof(hw_memsize); sysctlbyname("hw.memsize", &hw_memsize, &uint64_t_size, 0, 0); #endif rack_init(&szone->tiny_rack, RACK_TYPE_TINY, 0, debug_flags | MALLOC_PURGEABLE); rack_init(&szone->small_rack, RACK_TYPE_SMALL, 0, debug_flags | MALLOC_PURGEABLE); /* Purgeable zone does not participate in the adaptive "largemem" sizing. */ szone->is_largemem = 0; szone->large_threshold = LARGE_THRESHOLD; szone->vm_copy_threshold = VM_COPY_THRESHOLD; #if CONFIG_LARGE_CACHE // madvise(..., MADV_REUSABLE) death-row arrivals above this threshold [~0.1%] szone->large_entry_cache_reserve_limit = (size_t)(hw_memsize >> 10); /* Reset protection when returning a previous large allocation? */ int32_t libSystemVersion = NSVersionOfLinkTimeLibrary("System"); if ((-1 != libSystemVersion) && ((libSystemVersion >> 16) < 112) /* CFSystemVersionSnowLeopard */) { szone->large_legacy_reset_mprotect = TRUE; } else { szone->large_legacy_reset_mprotect = FALSE; } #endif szone->basic_zone.version = 10; szone->basic_zone.size = (void *)purgeable_size; szone->basic_zone.malloc = (void *)purgeable_malloc; szone->basic_zone.calloc = (void *)purgeable_calloc; szone->basic_zone.valloc = (void *)purgeable_valloc; szone->basic_zone.free = (void *)purgeable_free; szone->basic_zone.realloc = (void *)purgeable_realloc; szone->basic_zone.destroy = (void *)purgeable_destroy; szone->basic_zone.batch_malloc = (void *)purgeable_batch_malloc; szone->basic_zone.batch_free = (void *)purgeable_batch_free; szone->basic_zone.introspect = (struct malloc_introspection_t *)&purgeable_introspect; szone->basic_zone.memalign = (void *)purgeable_memalign; szone->basic_zone.free_definite_size = (void *)purgeable_free_definite_size; szone->basic_zone.pressure_relief = (void *)purgeable_pressure_relief; szone->basic_zone.claimed_address = (void *)purgeable_claimed_address; szone->basic_zone.reserved1 = 0; /* Set to zero once and for all as required by CFAllocator. */ szone->basic_zone.reserved2 = 0; /* Set to zero once and for all as required by CFAllocator. */ mprotect(szone, sizeof(szone->basic_zone), PROT_READ); /* Prevent overwriting the function pointers in basic_zone. */ szone->debug_flags = debug_flags | MALLOC_PURGEABLE; /* Purgeable zone does not support MALLOC_ADD_GUARD_PAGES. */ if (szone->debug_flags & MALLOC_ADD_GUARD_PAGES) { malloc_report(ASL_LEVEL_INFO, "purgeable zone does not support guard pages\n"); szone->debug_flags &= ~MALLOC_ADD_GUARD_PAGES; } _malloc_lock_init(&szone->large_szone_lock); szone->helper_zone = (struct szone_s *)malloc_default_zone; CHECK(szone, __PRETTY_FUNCTION__); return (malloc_zone_t *)szone; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/purgeable_malloc.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __PURGEABLE_MALLOC_H #define __PURGEABLE_MALLOC_H /* Create a new zone that supports malloc_make{un}purgeable() discipline. */ MALLOC_NOEXPORT malloc_zone_t * create_purgeable_zone(size_t initial_size, malloc_zone_t *malloc_default_zone, unsigned debug_flags); #endif // __PURGEABLE_MALLOC_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/radix_tree.c ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #if 0 int radix_tree_indent = 0; bool radix_tree_should_print __attribute__((visibility("default")))= true; #include #define D(s, ...) \ if (radix_tree_should_print) { \ for (int i = 0; i < radix_tree_indent; i++) \ putchar(' '); \ printf(s, __VA_ARGS__); \ } #define DINDENT(x) \ if (radix_tree_should_print) { \ for (int i = 0; i < (x); i++) \ putchar(' '); \ } #define DINC(x) radix_tree_indent += x; #define DDEC(x) radix_tree_indent -= x; #else #define DINDENT(x) #define D(s, ...) #define DINC(x) #define DDEC(x) #endif static void __attribute__((noreturn)) radix_tree_panic(const char *fmt, ...) { va_list args; va_start(args, fmt); char buf[256]; vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); abort_with_reason(OS_REASON_TEST, 0, buf, 0); } struct interval { uint64_t start; uint64_t size; }; struct answer { struct interval interval; uint64_t stackid; uint64_t limit; }; static inline bool in_interval(uint64_t x, struct interval interval) { return x >= interval.start && ((x - interval.start) < interval.size); } static inline bool intervals_intersect(struct interval a, struct interval b) { if (a.size == 0 || b.size == 0) return false; return (in_interval(a.start, b) || in_interval(a.start + a.size - 1, b) || in_interval(b.start, a) || in_interval(b.start + b.size - 1, a)); } __unused static inline bool interval_is_subset(struct interval a, struct interval b) { return in_interval(a.start, b) && in_interval(a.start + a.size - 1, b); } static inline struct interval truncate_interval(struct interval a, uint64_t limit) { if (a.start >= limit) { return (struct interval){.start = a.start, .size = 0}; } else { return (struct interval){.start = a.start, .size = limit - a.start}; } } static inline bool answer_found(struct answer answer) { return answer.stackid != radix_tree_invalid_value; } /* * Modify the node to maintain the invariant that the lesser edge is first. * Return true if node needed to be modified. */ static bool fixnode(struct radix_node *node) { bool swap = false; if (node->edges[0].labelBits && node->edges[1].labelBits) { unsigned label0 = node->edges[0].label << (RADIX_LABEL_BITS - node->edges[0].labelBits); unsigned label1 = node->edges[1].label << (RADIX_LABEL_BITS - node->edges[1].labelBits); if (label1 < label0) swap = true; } else if (node->edges[0].labelBits == 0 && node->edges[1].labelBits != 0) { swap = true; } if (swap) { struct radix_edge edge0 = node->edges[0]; node->edges[0] = node->edges[1]; node->edges[1] = edge0; } return swap; } static struct answer radix_tree_lookup_recursive(struct radix_tree *tree, struct interval keys, // keys we're looking for struct interval nodekeys, // keys it is possible that we will find struct radix_node *node, int keyshift) { DINDENT(keyshift); D("LOOKUPREC %p keys=[%llx-%llx] nodekeys=[%llx-%llx]\n", node, keys.start, keys.start + keys.size, nodekeys.start, nodekeys.start + nodekeys.size); assert(node); assert(intervals_intersect(nodekeys, keys)); assert(keyshift < RADIX_TREE_KEY_BITS); assert(!fixnode(node)); if (keys.start < nodekeys.start) { uint64_t diff = nodekeys.start - keys.start; if (keys.size > diff) { keys.start += diff; keys.size -= diff; assert(keys.start == nodekeys.start); } else { DINDENT(keyshift); D("LOOKUPREC(<) quit keys.size=%llx diff=%llx\n", keys.size, diff); return (struct answer){.limit = nodekeys.start, .stackid = radix_tree_invalid_value}; } DINDENT(keyshift); D("LOOKUPREC(<) %p keys=[%llx-%llx]\n", node, keys.start, keys.start + keys.size); } assert(keys.start >= nodekeys.start); assert(intervals_intersect(nodekeys, keys)); for (int i = 1; i >= 0; i--) { struct radix_edge *edge = &node->edges[i]; if (!edge_valid(edge)) { continue; } uint64_t edgekeys_start = extend_key(nodekeys.start, edge->labelBits, keyshift, edge->label); assert(edgekeys_start >= nodekeys.start); struct interval edgekeys = {.start = edgekeys_start, .size = nodekeys.size - (edgekeys_start - nodekeys.start)}; DINDENT(keyshift); D("LOOKUPREC(edge%d) edgekeys=[%llx-%llx] nodekeys=[%llx-%llx]\n", i, edgekeys.start, edgekeys.start + edgekeys.size, nodekeys.start, nodekeys.start + nodekeys.size); assert(interval_is_subset(edgekeys, nodekeys)); if (intervals_intersect(edgekeys, keys)) { if (edge->isLeaf) { struct radix_node *leaf = getnode(tree, edge->index); assert(leaf); assert(keyshift + edge->labelBits == RADIX_TREE_KEY_BITS); uint64_t size = leaf_size(tree, leaf); assert(size <= edgekeys.size); edgekeys.size = size; // edgekeys is now exact. if (intervals_intersect(edgekeys, keys)) { DINDENT(keyshift); D("LOOKUPREC(found) leaf=(%d)%p %llx\n", edge->index, leaf, leaf->stackid); return (struct answer){.interval = edgekeys, .stackid = leaf->stackid}; } nodekeys = truncate_interval(nodekeys, edgekeys.start); } else { struct answer answer = radix_tree_lookup_recursive(tree, keys, edgekeys, getnode(tree, edge->index), keyshift + edge->labelBits); if (answer_found(answer)) { DINDENT(keyshift); D("LOOKUPREC(found) %llx\n", answer.stackid); return answer; } nodekeys = truncate_interval(nodekeys, answer.limit); } } } struct answer ans = {.limit = nodekeys.start + nodekeys.size, .stackid = radix_tree_invalid_value}; DINDENT(keyshift); D("LOOKUPREC(notfound) limit=%llx\n", ans.limit); return ans; } static struct answer radix_tree_lookup_interval(struct radix_tree *tree, struct interval keys) { struct interval max_interval = {.start = 0, .size = (uint64_t)-1}; struct answer ans = radix_tree_lookup_recursive(tree, keys, max_interval, getnode(tree, 0), 0); D("LOOKUP [%llx-%llx] -> [%llx, %llx] %llx\n", keys.start, keys.start + keys.size, ans.interval.start, ans.interval.start + ans.interval.size, ans.stackid); assert(!answer_found(ans) || intervals_intersect(keys, ans.interval)); return ans; } uint64_t radix_tree_lookup(struct radix_tree *tree, uint64_t key) { return radix_tree_lookup_interval(tree, (struct interval){.start = key, .size = 1}).stackid; } static void radix_tree_grow(struct radix_tree **treep); static unsigned radix_tree_allocate_node(struct radix_tree **treep) { if (!(*treep)->next_free) radix_tree_grow(treep); if (!(*treep)->next_free) return 0; struct radix_tree *tree = *treep; unsigned ret = tree->next_free; struct radix_node *node = getnode(tree, tree->next_free); assert(node); tree->next_free = node->next_free; if (node->next_free && !node->next_free_is_initialized) { struct radix_node *next = getnode(tree, node->next_free); assert(next); next->next_free = (node->next_free + 1 < tree->num_nodes) ? node->next_free + 1 : 0; } node->as_u64 = 0; return ret; } static void radix_tree_free_node(struct radix_tree *tree, unsigned index) { struct radix_node *node = getnode(tree, index); assert(node); node->next_free = tree->next_free; node->next_free_is_initialized = true; tree->next_free = index; } static bool radix_tree_insert_recursive(struct radix_tree **treep, struct interval keys, uint64_t value, unsigned node_index, int keyshift) { struct radix_node *node = getnode(*treep, node_index); DINDENT(keyshift); D("INSERTREC %p keys=[%llx-%llx]\n", node, keys.start, keys.start + keys.size); assert(keyshift < RADIX_TREE_KEY_BITS); assert(node); for (int i = 0; i < 2; i++) { struct radix_edge *edge = &node->edges[i]; int matching_bits = count_matching_bits(edge, keys.start, keyshift); if (matching_bits) { if (matching_bits == edge->labelBits) { if (edge->isLeaf) { assert(false); // it should have been deleted before we got here struct radix_node *leaf = getnode(*treep, edge->index); assert(leaf); assert(keyshift + edge->labelBits == RADIX_TREE_KEY_BITS); leaf->stackid = value; set_leaf_size(*treep, leaf, keys.size); DINDENT(keyshift); D("inserted %p\n", node); return true; } else { return radix_tree_insert_recursive(treep, keys, value, edge->index, keyshift + edge->labelBits); } } else { unsigned index = radix_tree_allocate_node(treep); if (!index) { DINDENT(keyshift); D("FAILED! %p\n", node); return false; } /* pointers may have changed */ node = getnode(*treep, node_index); edge = &node->edges[i]; struct radix_node *newnode = getnode(*treep, index); DINDENT(keyshift); D("splitting edge newnode=%p isleaf=%s matching_bits=%d oldLabelBits=%d\n", newnode, edge->isLeaf ? "true" : "false", matching_bits, edge->labelBits); newnode->edges[0].labelBits = (edge->labelBits - matching_bits); newnode->edges[0].isLeaf = edge->isLeaf; newnode->edges[0].index = edge->index; newnode->edges[0].label = edge->label & ((1 << (edge->labelBits - matching_bits)) - 1); edge->label = edge->label >> (edge->labelBits - matching_bits); edge->labelBits = matching_bits; edge->isLeaf = false; edge->index = index; fixnode(node); return radix_tree_insert_recursive(treep, keys, value, index, keyshift + matching_bits); } } if (edge->labelBits == 0) { if (RADIX_TREE_KEY_BITS - keyshift <= RADIX_LABEL_BITS) { unsigned index = radix_tree_allocate_node(treep); if (!index) { DINDENT(keyshift); D("FAILED! %p\n", node); return false; } /* pointers may have changed */ node = getnode(*treep, node_index); edge = &node->edges[i]; edge->labelBits = RADIX_TREE_KEY_BITS - keyshift; edge->isLeaf = true; edge->index = index; edge->label = keybits(keys.start, RADIX_TREE_KEY_BITS - keyshift, keyshift); struct radix_node *leaf = getnode(*treep, index); DINDENT(keyshift); D("new leaf node %p\n", leaf); leaf->stackid = value; set_leaf_size(*treep, leaf, keys.size); fixnode(node); return true; } else { unsigned index = radix_tree_allocate_node(treep); if (!index) { DINDENT(keyshift); D("FAILED! %p\n", node); return false; } /* pointers may have changed */ node = getnode(*treep, node_index); edge = &node->edges[i]; edge->labelBits = RADIX_LABEL_BITS; edge->isLeaf = false; edge->index = index; edge->label = keybits(keys.start, RADIX_LABEL_BITS, keyshift); struct radix_node *newnode = getnode(*treep, index); newnode->as_u64 = 0; DINDENT(keyshift); D("new internal node %p\n", newnode); fixnode(node); return radix_tree_insert_recursive(treep, keys, value, index, keyshift + RADIX_LABEL_BITS); } } } radix_tree_panic("MallocStackLogging INTERNAL ERROR: at least one edge must prefix-match or be unused"); } bool radix_tree_insert(struct radix_tree **treep, uint64_t key, uint64_t size, uint64_t value) { D("INSERT %llx-%llx\n", key, key + size); DINC(4); if (key + size < key) { radix_tree_panic("MallocStackLogging INTERNAL ERROR: interval wraps around the end of the address space: %llx, size=%llx\n", key, size); } struct radix_node node = {.stackid = value, .size = size >> (*treep)->leaf_size_shift}; if (node.stackid != value || (((uint64_t)node.size) << (*treep)->leaf_size_shift) != size) { radix_tree_panic("MallocStackLogging INTERNAL ERROR: cannot represent value:%llx or size:%llx (key is %llx)\n", value, size, key); return false; } uint64_t mask = ((uint64_t)-1) << (64 - RADIX_TREE_KEY_BITS); if ((key & mask) != key) { radix_tree_panic("MallocStackLogging INTERNAL ERROR: cannot represent key: %llx\n", key); } bool ok; ok = radix_tree_delete(treep, key, size); if (!ok) { goto out; } ok = radix_tree_insert_recursive(treep, (struct interval){.start = key, .size = size}, value, 0, 0); out: DDEC(4); return ok; } static bool radix_tree_delete_recursive(struct radix_tree *tree, uint64_t key, struct radix_node *node, int keyshift) { assert(keyshift < RADIX_TREE_KEY_BITS); assert(node); for (int i = 0; i < 2; i++) { struct radix_edge *edge = &node->edges[i]; if (edge_matches(edge, key, keyshift)) { if (edge->isLeaf) { radix_tree_free_node(tree, edge->index); if (i == 0) { node->edges[0] = node->edges[1]; node->edges[1].labelBits = 0; } else { node->edges[1].labelBits = 0; } return true; } else { bool deleted = radix_tree_delete_recursive(tree, key, getnode(tree, edge->index), keyshift + edge->labelBits); if (deleted) { struct radix_node *child = getnode(tree, edge->index); assert(child); if (child->edges[0].labelBits == 0 && child->edges[1].labelBits == 0) { radix_tree_free_node(tree, edge->index); if (i == 0) { node->edges[0] = node->edges[1]; node->edges[1].labelBits = 0; } else { node->edges[1].labelBits = 0; } } } return deleted; } } } return false; } bool radix_tree_delete(struct radix_tree **treep, uint64_t key, uint64_t size) { D("BALETE %llx-%llx\n", key, key + size); DINC(4); struct interval keys = {.start = key, .size = size}; bool ok = true; while (1) { struct answer answer = radix_tree_lookup_interval(*treep, keys); if (!answer_found(answer)) { break; } ok = radix_tree_delete_recursive(*treep, answer.interval.start, getnode(*treep, 0), 0); assert(ok); D("BALETED %llx-%llx -> %llx\n", answer.interval.start, answer.interval.start + answer.interval.size, answer.stackid); if (answer.interval.start < keys.start) { D("REINSERTING %llx-%llx -> %llx\n", answer.interval.start, answer.interval.start + (keys.start - answer.interval.start), answer.stackid); ok = radix_tree_insert(treep, answer.interval.start, keys.start - answer.interval.start, answer.stackid); if (!ok) { goto out; } } uint64_t answer_end = answer.interval.start + answer.interval.size; uint64_t keys_end = keys.start + keys.size; if (answer_end > keys_end) { D("REINSERTING %llx-%llx -> %llx\n", keys_end, keys_end + (answer_end - keys_end), answer.stackid); ok = radix_tree_insert(treep, keys_end, answer_end - keys_end, answer.stackid); if (!ok) { goto out; } } } out: DDEC(4); return ok; } struct radix_tree * radix_tree_init(void *buf, size_t size) { struct radix_tree *tree = buf; memcpy(tree->header, "radixv2", 8); void *nodestart = &tree->nodes[0]; void *nodesend = buf + size; assert(nodestart < nodesend); tree->num_nodes = (uint32_t)(nodesend - nodestart) / sizeof(struct radix_node); assert(tree->num_nodes >= 3); tree->next_free = 1; tree->nodes[0].as_u64 = tree->nodes[1].as_u64 = 0; tree->nodes[1].next_free = 2; tree->leaf_size_shift = 12; // smallest size of a VM region is 4096 return tree; } struct radix_tree * radix_tree_create() { mach_vm_size_t size = PAGE_SIZE; mach_vm_address_t allocated; kern_return_t kr = mach_vm_allocate(mach_task_self(), &allocated, size, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_ANALYSIS_TOOL)); if (kr != KERN_SUCCESS) { return NULL; } return radix_tree_init((void *)allocated, PAGE_SIZE); return NULL; } static void radix_tree_grow(struct radix_tree **treep) { mach_vm_size_t max_size = (1 << 16) * sizeof(struct radix_node); assert((*treep)->next_free == 0); mach_vm_size_t size = sizeof(struct radix_tree) + sizeof(struct radix_node) * (*treep)->num_nodes; assert(size % PAGE_SIZE == 0); mach_vm_size_t newsize = size * 2; if (newsize > max_size) { newsize = max_size; } if (newsize <= size) { return; } mach_vm_address_t allocated; kern_return_t kr = mach_vm_allocate(mach_task_self(), &allocated, newsize, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_ANALYSIS_TOOL)); if (kr != KERN_SUCCESS) { return; } D("GROW %p -> %p\n", *treep, (void *)allocated); kr = mach_vm_copy(mach_task_self(), (mach_vm_address_t)*treep, size, allocated); if (kr != KERN_SUCCESS) { mach_vm_deallocate(mach_task_self(), allocated, newsize); return; } uint32_t old_num_nodes = (*treep)->num_nodes; mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)*treep, size); *treep = (void *)allocated; void *nodestart = &(*treep)->nodes[0]; void *nodesend = ((void *)(*treep)) + newsize; (*treep)->num_nodes = (uint32_t)(nodesend - nodestart) / sizeof(struct radix_node); (*treep)->next_free = old_num_nodes; (*treep)->nodes[old_num_nodes].next_free_is_initialized = 0; (*treep)->nodes[old_num_nodes].next_free = old_num_nodes + 1; } void radix_tree_destory(struct radix_tree *tree) { mach_vm_size_t size = sizeof(struct radix_tree) + sizeof(struct radix_node) * tree->num_nodes; assert(size % PAGE_SIZE == 0); mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)tree, size); } static uint64_t radix_tree_count_recursive(struct radix_tree *tree, struct radix_node *node) { uint64_t count = 0; for (int i = 0; i < 2; i++) { struct radix_edge *edge = &node->edges[i]; if (edge->labelBits == 0) continue; if (edge->isLeaf) { count += leaf_size(tree, getnode(tree, edge->index)); } else { count += radix_tree_count_recursive(tree, getnode(tree, edge->index)); } } return count; } uint64_t radix_tree_count(struct radix_tree *tree) { return radix_tree_count_recursive(tree, getnode(tree, 0)); } uint64_t radix_tree_size(struct radix_tree *tree) { mach_vm_size_t size = sizeof(struct radix_tree) + sizeof(struct radix_node) * tree->num_nodes; return size; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/radix_tree.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __RADIX_TREE_H #define __RADIX_TREE_H #include #include /* * This is a radix tree implementation mapping 64 bit keys to 64 bit values. * * Its in-memory representation is also valid as a serial representation. */ struct radix_tree; static const uint64_t radix_tree_invalid_value = (uint64_t) -1; /* * Lookup a key in the radix tree and return its value. Returns * radix_tree_invalid_value (ie -1) if not found */ __attribute__((visibility("default"))) uint64_t radix_tree_lookup(struct radix_tree *tree, uint64_t key); /* * Insert an range of keys into a radix tree (possibly reallocing it). Returns true on * success. * * Arguments: * * treep: The tree to modify. Will write to *treep if the tree needs to be realloc'd * key: The first key to set * size: The number of keys to set * value: The value to set them too */ bool radix_tree_insert(struct radix_tree **treep, uint64_t key, uint64_t size, uint64_t value); /* * Delete a range of keys from a radix tree. Returns true on success. * * Arguments * * treep: The tree to modify. Will write to *treep if the tree needs to be realloc'd * key: The first key to delete * size: The number of keys to delete */ bool radix_tree_delete(struct radix_tree **treep, uint64_t key, uint64_t size); /* * Create a radix tree */ struct radix_tree * radix_tree_create(); /* * deallocate a radix tree */ void radix_tree_destory(struct radix_tree *tree); /* * Count the number of keys in a radix tree. */ __attribute__((visibility("default"))) uint64_t radix_tree_count(struct radix_tree *tree); /* * Get the size of the radix tree buffer */ uint64_t radix_tree_size(struct radix_tree *tree); #endif ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/radix_tree_debug.c ================================================ /* * Copyright (c) 2017 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include static bool radix_tree_fsck_recursive(struct radix_tree *tree, struct radix_node *node, uint64_t key, int keyshift, uint64_t min) { bool ok = true; for (int i = 0; i < 2; i++) { struct radix_edge *edge = &node->edges[i]; if (edge->labelBits == 0) continue; uint64_t edgekey = extend_key(key, edge->labelBits, keyshift, edge->label); if (edge->isLeaf) { if (edgekey < min) { fprintf(stderr, "!!!! node=%p min=%llx edge=%d edgekey=%llx\n", node, min, i, edgekey); return false; } struct radix_node *leaf = getnode(tree, edge->index); min = edgekey + leaf_size(tree, leaf); } else { ok = ok && radix_tree_fsck_recursive(tree, getnode(tree, edge->index), edgekey, keyshift+edge->labelBits, min); } } return ok; } bool radix_tree_fsck(struct radix_tree *tree) { return radix_tree_fsck_recursive(tree, getnode(tree, 0), 0, 0, 0); } static void radix_tree_print_recursive(struct radix_tree *tree, struct radix_node *node, int indent, uint64_t key, int keyshift) { if (node->edges[0].labelBits == 0 && node->edges[1].labelBits == 0) { printf("%p:", node); for (int i = 0; i < indent; i++) printf(" "); printf("empty\n"); } for (int i = 0; i < 2; i++) { struct radix_edge *edge = &node->edges[i]; if (edge->labelBits == 0) continue; printf("%p:", node); for (int i = 0; i < indent; i++) printf(" "); printf ("0x%x/%d", edge->label, edge->labelBits); if (edge->isLeaf) { struct radix_node *leaf = getnode(tree, edge->index); printf(" [%llx-%llx] -> stack=%llx\n", extend_key(key, edge->labelBits, keyshift, edge->label), extend_key(key, edge->labelBits, keyshift, edge->label) + leaf_size(tree, leaf), leaf->stackid); } else { printf("\n"); radix_tree_print_recursive(tree, getnode(tree, edge->index), indent + 4, extend_key(key, edge->labelBits, keyshift, edge->label), keyshift + edge->labelBits); } } } void radix_tree_print(struct radix_tree *tree) { radix_tree_print_recursive(tree, getnode(tree, 0), 0, 0, 0); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/radix_tree_internal.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __RADIX_TREE_INTERNAL_H #define __RADIX_TREE_INTERNAL_H #include #include #include #include #define RADIX_LABEL_BITS 11 //max number of label bits per edge #define RADIX_TREE_KEY_BITS (64 - 12) //number of keybits we use (starting from MSB) struct radix_edge { unsigned label : RADIX_LABEL_BITS; unsigned index : 16; unsigned labelBits : 4; unsigned isLeaf : 1; }; _Static_assert(sizeof(struct radix_edge) == 4, "size of radix_edge must be 4"); struct radix_node { union { struct { struct radix_edge edges[2]; }; struct { uint64_t stackid : 32; uint64_t size : 32; }; struct { uint16_t next_free; bool next_free_is_initialized; }; uint64_t as_u64; }; }; _Static_assert(sizeof(struct radix_node) == 8, "size of radix_node must be 8"); struct radix_tree { char header[8]; uint32_t leaf_size_shift; uint32_t num_nodes; uint32_t next_free; struct radix_node nodes[]; }; static inline uint64_t leaf_size(struct radix_tree *tree, struct radix_node *node) { return ((uint64_t)node->size) << tree->leaf_size_shift; } static inline void set_leaf_size(struct radix_tree *tree, struct radix_node *node, uint64_t size) { node->size = size >> tree->leaf_size_shift; assert(leaf_size(tree, node) == size); } /* * Does this edge even exist? */ static inline bool edge_valid(struct radix_edge *edge) { return edge->labelBits != 0; } /* * Read the most significant labelBits out of (key << keyshift) */ static inline unsigned keybits(uint64_t key, int labelBits, int keyshift) { uint64_t mask = (1 << labelBits) - 1; return (unsigned) (key >> (64 - labelBits - keyshift)) & mask; } /* * Add labelBits to key. */ static inline uint64_t extend_key(uint64_t key, int labelBits, int keyshift, uint64_t label) { uint64_t mask __unused = (1 << labelBits) - 1; assert((label & ~mask) == 0); int shift = 64 - keyshift - labelBits; // [ keyshift | labelbits | 64 - keyshift - labelbits ] assert((key & (mask << shift)) == 0); return key | (label << shift); } /* * Return true if exact radix tree traversal should follow this edge. * That is, return true if the edge label matches the key bits exactly. */ static inline bool edge_matches(struct radix_edge *edge, uint64_t key, int keyshift) { if (!edge_valid(edge)) { return false; } return keybits(key, edge->labelBits, keyshift) == edge->label; } /* * Count the number of most-significant bits that (key << keyshift) has in * common with edge. */ static inline int count_matching_bits(struct radix_edge *edge, uint64_t key, int keyshift) { int labelBits = edge->labelBits; uint64_t label = edge->label; while (labelBits) { if (keybits(key, labelBits, keyshift) == label) { return labelBits; } labelBits--; label >>= 1; } return 0; } /* * Lookup a radix tree node by index. Returns NULL for invalid index. */ static inline struct radix_node * getnode(struct radix_tree *tree, unsigned index) { if (index > tree->num_nodes) { return NULL; } else { return &tree->nodes[index]; } } /* * Initialize a radix tree in a new buffer */ struct radix_tree * radix_tree_init(void *buf, size_t size); /* * Print a representation of a radix tree to stdout. */ void radix_tree_print(struct radix_tree *tree); /* * Check a radix tree for consistency. Returns true if everything is ok. */ bool radix_tree_fsck(struct radix_tree *tree); #endif ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/stack_logging_disk.c ================================================ /* * Copyright (c) 2007-2013 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" #include "radix_tree.h" #pragma mark - #pragma mark Defines #if TARGET_OS_IPHONE // malloc_report(ASL_LEVEL_INFO...) on iOS doesn't show up in the Xcode Console log of the device, // but ASL_LEVEL_NOTICE does. So raising the log level is helpful. #undef ASL_LEVEL_INFO #define ASL_LEVEL_INFO ASL_LEVEL_NOTICE #endif // TARGET_OS_IPHONE #ifdef TEST_DISK_STACK_LOGGING #define malloc_report fprintf #undef ASL_LEVEL_INFO #define ASL_LEVEL_INFO stderr #endif #define STACK_LOGGING_BLOCK_WRITING_SIZE 8192 #define STACK_LOGGING_MAX_SIMUL_REMOTE_TASKS_INSPECTED 3 #define BACKTRACE_UNIQUING_DEBUG 0 // The expansion factor controls the shifting up of table size. A factor of 1 will double the size upon expanding, // 2 will quadruple the size, etc. Maintaining a 66% fill in an ideal table requires the collision allowance to // increase by 3 for every quadrupling of the table size (although this the constant applied to insertion // performance O(c*n)) #define EXPAND_FACTOR 2 #define COLLISION_GROWTH_RATE 3 // For a uniquing table, the useful node size is slots := floor(table_byte_size / (2 * sizeof(mach_vm_address_t))) // Some useful numbers for the initial max collision value (desiring 66% fill): // 16K-23K slots -> 16 collisions // 24K-31K slots -> 17 collisions // 32K-47K slots -> 18 collisions // 48K-79K slots -> 19 collisions // 80K-96K slots -> 20 collisions #define INITIAL_MAX_COLLIDE 19 #define DEFAULT_UNIQUING_PAGE_SIZE 256 #pragma mark - #pragma mark Macros #define STACK_LOGGING_FLAGS_SHIFT 56 #define STACK_LOGGING_USER_TAG_SHIFT 24 #define STACK_LOGGING_FLAGS(longlongvar) (uint32_t)((uint64_t)(longlongvar) >> STACK_LOGGING_FLAGS_SHIFT) #define STACK_LOGGING_FLAGS_AND_USER_TAG(longlongvar) \ (uint32_t)(STACK_LOGGING_FLAGS(longlongvar) | (((uint64_t)(longlongvar)&0x00FF000000000000ull) >> STACK_LOGGING_USER_TAG_SHIFT)) #define STACK_LOGGING_OFFSET_MASK 0x0000FFFFFFFFFFFFull #define STACK_LOGGING_OFFSET(longlongvar) ((longlongvar)&STACK_LOGGING_OFFSET_MASK) #define STACK_LOGGING_OFFSET_AND_FLAGS(longlongvar, type_flags) \ (((uint64_t)(longlongvar)&STACK_LOGGING_OFFSET_MASK) | ((uint64_t)(type_flags) << STACK_LOGGING_FLAGS_SHIFT) | \ (((uint64_t)(type_flags)&0xFF000000ull) << STACK_LOGGING_USER_TAG_SHIFT)) #pragma mark - #pragma mark Types typedef struct { uintptr_t argument; uintptr_t address; uint64_t offset_and_flags; // top 8 bits are actually the flags! } stack_logging_index_event; typedef struct { uint32_t argument; uint32_t address; uint64_t offset_and_flags; // top 8 bits are actually the flags! } stack_logging_index_event32; typedef struct { uint64_t argument; uint64_t address; uint64_t offset_and_flags; // top 8 bits are actually the flags! } stack_logging_index_event64; // backtrace uniquing table chunks used in client-side stack log reading code, // in case we can't read the whole table in one mach_vm_read() call. typedef struct table_chunk_header { uint64_t num_nodes_in_chunk; uint64_t table_chunk_size; mach_vm_address_t *table_chunk; struct table_chunk_header *next_table_chunk_header; } table_chunk_header_t; #pragma pack(push, 4) typedef struct backtrace_uniquing_table { uint64_t numPages; // number of pages of the table uint64_t numNodes; uint64_t tableSize; uint64_t untouchableNodes; mach_vm_address_t table_address; int32_t max_collide; // 'table_address' is just an always 64-bit version of the pointer-sized 'table' field to remotely read; // it's important that the offset of 'table_address' in the struct does not change between 32 and 64-bit. #if BACKTRACE_UNIQUING_DEBUG uint64_t nodesFull; uint64_t backtracesContained; #endif union { mach_vm_address_t *table; // in "target" process; allocated using vm_allocate() table_chunk_header_t *first_table_chunk_hdr; // in analysis process } u; uint64_t max_table_size; bool in_client_process : 1; bool nodes_use_refcount : 1; unsigned refcount; } backtrace_uniquing_table; #pragma pack(pop) // for storing/looking up allocations that haven't yet be written to disk; consistent size across 32/64-bit processes. // It's important that these fields don't change alignment due to the architecture because they may be accessed from an // analyzing process with a different arch - hence the pragmas. #pragma pack(push, 4) typedef struct { uint64_t start_index_offset; uint32_t next_free_index_buffer_offset; char index_buffer[STACK_LOGGING_BLOCK_WRITING_SIZE]; backtrace_uniquing_table *uniquing_table; struct radix_tree *vm_stackid_table; uint64_t vm_stackid_table_size; } stack_buffer_shared_memory; #pragma pack(pop) // target process address -> record table (for __mach_stack_logging_get_frames) typedef struct { uint64_t address; uint64_t index_file_offset; } remote_index_node; // for caching index information client-side: typedef struct { size_t cache_size; size_t cache_node_capacity; uint32_t collision_allowance; remote_index_node *table_memory; // this can be malloced; it's on the client side. stack_buffer_shared_memory *shmem; // shared memory stack_buffer_shared_memory snapshot; // memory snapshot of the remote process' shared memory uint32_t last_pre_written_index_size; uint64_t last_index_file_offset; backtrace_uniquing_table uniquing_table_snapshot; // snapshot of the remote process' uniquing table boolean_t lite_mode; struct radix_tree *vm_stackid_table; } remote_index_cache; // for reading stack history information from remote processes: typedef struct { task_t remote_task; pid_t remote_pid; int32_t task_is_64_bit; boolean_t task_uses_lite_or_vmlite_mode; int32_t in_use_count; FILE *index_file_stream; uint64_t remote_stack_buffer_shared_memory_address; remote_index_cache *cache; } remote_task_file_streams; typedef mach_vm_address_t slot_address; typedef uint64_t slot_parent; typedef uint64_t slot_refcount; typedef uint64_t table_slot_index; #pragma pack(push,16) typedef struct { union { struct { uint64_t slot0; uint64_t slot1; } slots; struct { slot_address address:48; slot_parent parent:32; slot_refcount refcount:48; } refcount_slot; struct { slot_address address:64; slot_parent parent:64; } normal_slot; }; } table_slot_t; #pragma pack(pop) _Static_assert(sizeof(table_slot_t) == 16, "table_slot_t must be 128 bits"); #pragma mark - #pragma mark Constants/Globals int stack_logging_enable_logging = 0; int stack_logging_dontcompact = 0; int stack_logging_finished_init = 0; int stack_logging_postponed = 0; int stack_logging_mode = stack_logging_mode_none; #define MAX_PARENT_NORMAL #define MAX_PARENT_REFCOUNT static const slot_parent slot_no_parent_normal = 0xFFFFFFFFFFFFFFFF; // 64 bits static const slot_parent slot_no_parent_refcount = 0xFFFFFFFF; // 32 bits static _malloc_lock_s stack_logging_lock = _MALLOC_LOCK_INIT; static vm_address_t thread_doing_logging = 0; // single-thread access variables static stack_buffer_shared_memory *pre_write_buffers; static vm_address_t *stack_buffer; static uintptr_t last_logged_malloc_address = 0; // Constants to define part of stack logging file path names. // File names are of the form stack-logs..
..XXXXXX.index // where
is the address of the pre_write_buffers VM region in the target // process that will need to be mapped into analysis tool processes. static const char *stack_log_file_base_name = "stack-logs."; static const char *stack_log_file_suffix = ".index"; static FILE *open_log_file_at_path(char *pathname, remote_task_file_streams *streams); char *__stack_log_file_path__ = NULL; static int index_file_descriptor = -1; // for accessing remote log files static remote_task_file_streams remote_fds[STACK_LOGGING_MAX_SIMUL_REMOTE_TASKS_INSPECTED]; static uint32_t next_remote_task_fd = 0; static uint32_t remote_task_fd_count = 0; static _malloc_lock_s remote_fd_list_lock = _MALLOC_LOCK_INIT; uint64_t __mach_stack_logging_shared_memory_address = 0; // activation variables static int logging_use_compaction = 1; // set this to zero to always disable compaction. // We set malloc_logger to NULL to disable logging, if we encounter errors // during file writing typedef void(malloc_logger_t)(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t num_hot_frames_to_skip); extern malloc_logger_t *malloc_logger; extern malloc_logger_t *__syscall_logger; // use this to set up syscall logging (e.g., vm_allocate, vm_deallocate, mmap, munmap) #pragma mark - #pragma mark In-Memory Backtrace Uniquing static __attribute__((always_inline)) inline void * sld_allocate_pages(uint64_t memSize) { mach_vm_address_t allocatedMem = 0ull; if (mach_vm_allocate(mach_task_self(), &allocatedMem, memSize, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_ANALYSIS_TOOL)) != KERN_SUCCESS) { malloc_report(ASL_LEVEL_ERR, "allocate_pages(): virtual memory exhausted!\n"); } return (void *)(uintptr_t)allocatedMem; } static __attribute__((always_inline)) inline int sld_deallocate_pages(void *memPointer, uint64_t memSize) { return mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)(uintptr_t)memPointer, memSize); } static const uint64_t max_table_size_lite = UINT32_MAX; static const uint64_t max_table_size_normal = UINT64_MAX; static backtrace_uniquing_table * __create_uniquing_table(boolean_t lite_or_vmlite_mode) { backtrace_uniquing_table *uniquing_table = (backtrace_uniquing_table *)sld_allocate_pages((uint64_t)round_page(sizeof(backtrace_uniquing_table))); if (!uniquing_table) { return NULL; } bzero(uniquing_table, sizeof(backtrace_uniquing_table)); uniquing_table->numPages = DEFAULT_UNIQUING_PAGE_SIZE; uniquing_table->tableSize = uniquing_table->numPages * vm_page_size; uniquing_table->numNodes = ((uniquing_table->tableSize / (sizeof(mach_vm_address_t) * 2)) >> 1) << 1; // make sure it's even. uniquing_table->u.table = (mach_vm_address_t *)(uintptr_t)sld_allocate_pages(uniquing_table->tableSize); uniquing_table->table_address = (uintptr_t)uniquing_table->u.table; uniquing_table->max_collide = INITIAL_MAX_COLLIDE; uniquing_table->untouchableNodes = 0; uniquing_table->max_table_size = (lite_or_vmlite_mode) ? max_table_size_lite : max_table_size_normal; uniquing_table->nodes_use_refcount = lite_or_vmlite_mode; uniquing_table->in_client_process = 0; #if BACKTRACE_UNIQUING_DEBUG malloc_report(ASL_LEVEL_INFO, "create_uniquing_table(): creating. size: %lldKB == %lldMB, numnodes: %lld (%lld untouchable)\n", uniquing_table->tableSize >> 10, uniquing_table->tableSize >> 20, uniquing_table->numNodes, uniquing_table->untouchableNodes); malloc_report(ASL_LEVEL_INFO, "create_uniquing_table(): table: %p; end: %p\n", uniquing_table->u.table, (void *)((uintptr_t)uniquing_table->u.table + (uintptr_t)uniquing_table->tableSize)); #endif return uniquing_table; } static void __destroy_uniquing_table(backtrace_uniquing_table *table) { assert(!table->in_client_process); sld_deallocate_pages(table->u.table, table->tableSize); sld_deallocate_pages(table, sizeof(backtrace_uniquing_table)); } static boolean_t __expand_uniquing_table(backtrace_uniquing_table *uniquing_table) { assert(!uniquing_table->in_client_process); mach_vm_address_t *oldTable = uniquing_table->u.table; uint64_t oldsize = uniquing_table->tableSize; uint64_t oldnumnodes = uniquing_table->numNodes; uint64_t newsize = (uniquing_table->numPages << EXPAND_FACTOR) * vm_page_size; if (newsize > uniquing_table->max_table_size) { malloc_report(ASL_LEVEL_ERR, "no more space in uniquing table\n"); return false; } uniquing_table->numPages = uniquing_table->numPages << EXPAND_FACTOR; uniquing_table->tableSize = uniquing_table->numPages * vm_page_size; uniquing_table->numNodes = ((uniquing_table->tableSize / (sizeof(mach_vm_address_t) * 2)) >> 1) << 1; // make sure it's even. mach_vm_address_t *newTable = (mach_vm_address_t *)(uintptr_t)sld_allocate_pages(uniquing_table->tableSize); uniquing_table->u.table = newTable; uniquing_table->table_address = (uintptr_t)uniquing_table->u.table; uniquing_table->max_collide = uniquing_table->max_collide + COLLISION_GROWTH_RATE; if (mach_vm_copy(mach_task_self(), (mach_vm_address_t)(uintptr_t)oldTable, oldsize, (mach_vm_address_t)(uintptr_t)newTable) != KERN_SUCCESS) { malloc_report(ASL_LEVEL_ERR, "expandUniquingTable(): VMCopyFailed\n"); } uniquing_table->untouchableNodes = oldnumnodes; #if BACKTRACE_UNIQUING_DEBUG malloc_report(ASL_LEVEL_INFO, "expandUniquingTable(): expanded from nodes full: %lld of: %lld (~%2d%%); to nodes: %lld (inactive = %lld); unique " "bts: %lld\n", uniquing_table->nodesFull, oldnumnodes, (int)(((uniquing_table->nodesFull * 100.0) / (double)oldnumnodes) + 0.5), uniquing_table->numNodes, uniquing_table->untouchableNodes, uniquing_table->backtracesContained); malloc_report(ASL_LEVEL_INFO, "expandUniquingTable(): allocate: %p; end: %p\n", newTable, (void *)((uintptr_t)newTable + (uintptr_t)(uniquing_table->tableSize))); malloc_report(ASL_LEVEL_INFO, "expandUniquingTable(): deallocate: %p; end: %p\n", oldTable, (void *)((uintptr_t)oldTable + (uintptr_t)oldsize)); malloc_report(ASL_LEVEL_INFO, "expandUniquingTable(): new size = %llu\n", newsize); #endif if (sld_deallocate_pages(oldTable, oldsize) != KERN_SUCCESS) { malloc_report(ASL_LEVEL_ERR, "expandUniquingTable(): mach_vm_deallocate failed. [%p]\n", uniquing_table->u.table); } return true; } static void add_new_slot(table_slot_t *table_slot, mach_vm_address_t address, table_slot_index parent, bool use_refcount, size_t ptr_size) { assert(use_refcount == (ptr_size > 0)); table_slot_t new_slot; if (use_refcount) { new_slot.refcount_slot.address = address; new_slot.refcount_slot.parent = parent; new_slot.refcount_slot.refcount = ptr_size; } else { new_slot.normal_slot.address = address; new_slot.normal_slot.parent = parent; } *table_slot = new_slot; } static void increment_slot_refcount(table_slot_t *table_slot, size_t ptr_size) { table_slot->refcount_slot.refcount += ptr_size; } static int enter_frames_in_table(backtrace_uniquing_table *uniquing_table, uint64_t *foundIndex, mach_vm_address_t *frames, int32_t count, size_t ptr_size) { assert(!uniquing_table->in_client_process); boolean_t use_refcount = (ptr_size > 0); // The hash values need to be the same size as the addresses (because we use the value -1), for clarity, define a new type typedef mach_vm_address_t hash_index_t; hash_index_t uParent = use_refcount ? slot_no_parent_refcount : slot_no_parent_normal; hash_index_t modulus = (uniquing_table->numNodes-uniquing_table->untouchableNodes-1); int32_t lcopy = count; int32_t returnVal = 1; hash_index_t hash_multiplier = ((uniquing_table->numNodes - uniquing_table->untouchableNodes)/(uniquing_table->max_collide*2+1)); while (--lcopy >= 0) { mach_vm_address_t thisPC = frames[lcopy]; hash_index_t hash = uniquing_table->untouchableNodes + (((uParent << 4) ^ (thisPC >> 2)) % modulus); int32_t collisions = uniquing_table->max_collide; while (collisions--) { table_slot_t *table_slot = (table_slot_t *) (uniquing_table->u.table + (hash * 2)); if (table_slot->slots.slot0 == 0 && table_slot->slots.slot1 == 0) { add_new_slot(table_slot, thisPC, uParent, uniquing_table->nodes_use_refcount, ptr_size); uParent = hash; break; } slot_address address = use_refcount ? table_slot->refcount_slot.address : table_slot->normal_slot.address; slot_parent parent = use_refcount ? table_slot->refcount_slot.parent : table_slot->normal_slot.parent; if (address == thisPC && parent == uParent) { uParent = hash; if (use_refcount) increment_slot_refcount(table_slot, ptr_size); break; } hash += collisions * hash_multiplier + 1; if (hash >= uniquing_table->numNodes) { hash -= (uniquing_table->numNodes - uniquing_table->untouchableNodes); // wrap around. } } if (collisions < 0) { returnVal = 0; break; } } if (returnVal) { *foundIndex = uParent; } return returnVal; } #pragma mark - #pragma mark Disk Stack Logging // pre-declarations static void delete_log_files(void); static int delete_logging_file(char *log_location); static bool getenv_from_process(pid_t pid, char *env_var_name, char *env_var_value_buf, size_t max_path_len); #define BASE10 10 #define BASE16 16 static void append_int(char *filename, uint64_t inputValue, unsigned base, size_t maxLength) { const char *digits = "0123456789abcdef"; if (base > strlen(digits)) { return; // sanity check } size_t len = strlen(filename); uint32_t count = 0; uint64_t value = inputValue; while (value > 0) { value /= base; count++; } if (len + count >= maxLength) { return; // don't modify the string if it would violate maxLength } filename[len + count] = '\0'; value = inputValue; uint32_t i; for (i = 0; i < count; i++) { filename[len + count - 1 - i] = digits[value % base]; value /= base; } } /* * if we needed to call confstr during init then setting this * flag will postpone stack logging until after Libsystem's initialiser has run. */ static void postpone_stack_logging(void) { malloc_report(ASL_LEVEL_INFO, "stack logging postponed until after initialization.\n"); stack_logging_postponed = 1; } /* * Check various logging directory options, in order of preference: * * value of MallocStackLoggingDirectory env var if user has set it. Typically * used on Mac OS X to write to a non-root file system with more free space. * * _PATH_TMP - /tmp usually writable for desktop apps and internal iOS apps * * value of TMPDIR env var - for sandboxed apps that can't write into /tmp * * confstr(_CS_DARWIN_USER_TEMP_DIR, ...) - should be same as TMPDIR if that is set, * but will create it safely if it doesn't yet exist. (See ) * * Allocating and releasing target buffer is the caller's responsibility. */ static bool get_writeable_logging_directory(char *target) { if (!target) { return false; } char *evn_log_directory = getenv("MallocStackLoggingDirectory"); if (evn_log_directory) { if (-1 != access(evn_log_directory, W_OK)) { strlcpy(target, evn_log_directory, (size_t)PATH_MAX); return true; } else { malloc_report(ASL_LEVEL_INFO, "MallocStackLoggingDirectory env var set to unwritable path '%s'\n", evn_log_directory); } } if (-1 != access(_PATH_TMP, W_OK)) { strlcpy(target, _PATH_TMP, (size_t)PATH_MAX); return true; } evn_log_directory = getenv("TMPDIR"); if (evn_log_directory && (-1 != access(evn_log_directory, W_OK))) { strlcpy(target, evn_log_directory, (size_t)PATH_MAX); return true; } if (stack_logging_finished_init) { size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, target, (size_t)PATH_MAX); if ((n > 0) && (n < PATH_MAX)) { return true; } } else { /* Can't call confstr during init, so postpone * logging till after */ postpone_stack_logging(); } *target = '\0'; return false; } // Stolen from libc. Ugly hack because arc4random uses malloc so we can't call Libc's mkstemps. int getentropy(void *, size_t); static int my_mkstemps(char *path, size_t slen) { char *start, *trv, *suffp, *carryp; char *pad; char carrybuf[MAXPATHLEN]; int fd; for (trv = path; *trv != '\0'; ++trv) { ; } trv -= slen; suffp = trv; --trv; /* Fill space with random characters */ uint8_t randbuf[8]; unsigned int randbuf_offset = 0; getentropy(randbuf, sizeof(randbuf)); static const char padchar[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; while (trv >= path && *trv == 'X') { *trv-- = padchar[randbuf[randbuf_offset++ % sizeof(randbuf)] % sizeof(padchar)]; } start = trv + 1; /* save first combination of random characters */ memcpy(carrybuf, start, suffp - start); for (;;) { if ((fd = open(path, O_CREAT | O_EXCL | O_RDWR, 0600)) >= 0) { return fd; } if (errno != EEXIST) { return -1; } /* If we have a collision, cycle through the space of filenames */ for (trv = start, carryp = carrybuf;;) { /* have we tried all possible permutations? */ if (trv == suffp) { return -1; /* yes - exit with EEXIST */ } pad = strchr(padchar, *trv); if (pad == NULL) { /* this should never happen */ errno = EIO; return -1; } /* increment character */ *trv = (*++pad == '\0') ? padchar[0] : *pad; /* carry to next position? */ if (*trv == *carryp) { /* increment position and loop */ ++trv; ++carryp; } else { /* try with new name */ break; } } } } // If successful, returns path to log file that was created, otherwise NULL. static char * create_log_file() { pid_t pid = getpid(); const char *progname = getprogname(); char *created_log_location = NULL; if (__stack_log_file_path__ == NULL) { // On first use, allocate space directly from the OS without using malloc __stack_log_file_path__ = sld_allocate_pages((uint64_t)round_page(PATH_MAX)); if (__stack_log_file_path__ == NULL) { malloc_report(ASL_LEVEL_INFO, "unable to allocate memory for stack log file path\n"); return NULL; } } if (!get_writeable_logging_directory(__stack_log_file_path__)) { if (!stack_logging_postponed) { malloc_report(ASL_LEVEL_INFO, "No writeable tmp dir\n"); } return NULL; } // Add the '/' only if it's not already there. Having multiple '/' characters works // but is unsightly when we print these stack log file names out. size_t stack_log_len = strlen(__stack_log_file_path__); if (__stack_log_file_path__[stack_log_len - 1] != '/') { // use strlcat to null-terminate for the next strlcat call, and to check buffer size strlcat(__stack_log_file_path__ + stack_log_len, "/", (size_t)PATH_MAX); } // Append the file name to __stack_log_file_path__ but don't use snprintf since // it can cause malloc() calls. // // The file name is of the form "stack-logs..
..XXXXXX.index" // where
is the address of the pre_write_buffers VM region in the target // process that will need to be mapped into analysis tool processes. We used to just // use a shared memory segment for that, but sandbox'ed target apps may not be able // to create shared memory segments so including the address of the VM region in the // file name is a simple way to communicate the address to analysis tools so the // stack log reading code can map in the region with mach_vm_remap(). strlcat(__stack_log_file_path__, stack_log_file_base_name, (size_t)PATH_MAX); append_int(__stack_log_file_path__, pid, BASE10, (size_t)PATH_MAX); strlcat(__stack_log_file_path__, ".", (size_t)PATH_MAX); append_int(__stack_log_file_path__, (uint64_t)pre_write_buffers, BASE16, (size_t)PATH_MAX); if (progname && progname[0] != '\0') { strlcat(__stack_log_file_path__, ".", (size_t)PATH_MAX); strlcat(__stack_log_file_path__, progname, (size_t)PATH_MAX); } strlcat(__stack_log_file_path__, ".XXXXXX", (size_t)PATH_MAX); strlcat(__stack_log_file_path__, stack_log_file_suffix, (size_t)PATH_MAX); // Securely create the log file. if ((index_file_descriptor = my_mkstemps(__stack_log_file_path__, (int)strlen(stack_log_file_suffix))) != -1) { malloc_report(ASL_LEVEL_INFO, "stack logs being written into %s\n", __stack_log_file_path__); created_log_location = __stack_log_file_path__; } else { malloc_report(ASL_LEVEL_INFO, "unable to create stack logs at %s\n", __stack_log_file_path__); __stack_log_file_path__[0] = '\0'; created_log_location = NULL; } return created_log_location; } // This function may be called from either the target process when exiting, or from either the the target process or // a stack log analysis process, when reaping orphaned stack log files. // Returns -1 if the files exist and they couldn't be removed, returns 0 otherwise. static int delete_logging_file(char *log_location) { if (log_location == NULL || log_location[0] == '\0') { return 0; } struct stat statbuf; if (unlink(log_location) != 0 && stat(log_location, &statbuf) == 0) { return -1; } return 0; } // This function will be called from atexit() in the target process. static void delete_log_files(void) { if (__stack_log_file_path__ && __stack_log_file_path__[0]) { if (delete_logging_file(__stack_log_file_path__) == 0) { malloc_report(ASL_LEVEL_INFO, "stack logs deleted from %s\n", __stack_log_file_path__); __stack_log_file_path__[0] = '\0'; } else { malloc_report(ASL_LEVEL_INFO, "unable to delete stack logs from %s\n", __stack_log_file_path__); } } } static bool is_process_running(pid_t pid) { struct kinfo_proc kpt[1]; size_t size = sizeof(struct kinfo_proc); int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; sysctl(mib, 4, kpt, &size, NULL, (size_t)0); // size is either 1 or 0 entries when we ask for a single pid return (size == sizeof(struct kinfo_proc)); } // Stack log files can be quite large and aren't useful after the process that created them no longer exists because // the stack backtrace uniquing tree was only kept in the process memory, not on disk. Normally the log files // should get removed when the process exits, by the delete_log_files() atexit function. However, there are // several situations in which that atexit function doesn't get called so the log files remain: // - if the process crashes or is force-killed // - if the app supported sudden termination, and was terminated through that // - if a process such as a shell execs another binary to transform the pid into a different process; // the new process will get a new log file but the old one would still be there. // // So, reap any stack log files for processes that no longer exist, or for the current process if we find a file // other than __stack_log_file_path__ // // This function looks for log files with prefix name "stack-logs.." underneath . // specifies a simple pattern of where stack logs can be down inside . // The pattern is essentially a relative path, where a level that start with '<' matches any name, otherwise // it has to be an exact name match. See the calling function for examples. static void reap_orphaned_log_files_in_hierarchy(char *directory, char *remaining_path_format, pid_t target_pid, remote_task_file_streams *streams) { DIR *dp; struct dirent *entry; // Ensure that we can access this directory - permissions or sandbox'ing might prevent it. if (access(directory, R_OK | W_OK | X_OK) == -1 || (dp = opendir(directory)) == NULL) { //malloc_report(ASL_LEVEL_INFO, "reaping: no access to %s\n", directory); return; } char pathname[PATH_MAX]; strlcpy(pathname, directory, (size_t)PATH_MAX); size_t pathname_len = strlen(pathname); if (pathname[pathname_len - 1] != '/') { pathname[pathname_len++] = '/'; } char *suffix = pathname + pathname_len; // Recurse down to deeper levels of the temp directory hierarchy if necessary. if (remaining_path_format) { char *separator = remaining_path_format; while (*separator != '/' && *separator != '\0') { separator++; } size_t length_to_match = (*remaining_path_format == '<') ? 0 : separator - remaining_path_format; char *next_remaining_path_format = (*separator == '\0') ? NULL : separator + 1; while ((entry = readdir(dp)) != NULL) { if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { if (length_to_match > 0 && strncmp(entry->d_name, remaining_path_format, length_to_match) != 0) { continue; } strlcpy(suffix, entry->d_name, (size_t)PATH_MAX - pathname_len); reap_orphaned_log_files_in_hierarchy(pathname, next_remaining_path_format, target_pid, streams); } } closedir(dp); return; } // OK, we found a lowest-level directory matching , and we have access to it. // Reap any unnecessary stack log files in here. //malloc_report(ASL_LEVEL_INFO, "reaping: looking in %s\n", directory); // __stack_log_file_path__ may be NULL if this code is running in an analysis tool client process that is not // itself running with MallocStackLogging set. char *curproc_stack_log_file = __stack_log_file_path__ ? strrchr(__stack_log_file_path__, '/') + 1 : NULL; pid_t curpid = getpid(); size_t prefix_length = strlen(stack_log_file_base_name); while ((entry = readdir(dp)) != NULL) { if ((entry->d_type == DT_REG || entry->d_type == DT_LNK) && (strncmp(entry->d_name, stack_log_file_base_name, prefix_length) == 0)) { long pid = strtol(&entry->d_name[prefix_length], (char **)NULL, 10); if (pid == target_pid && streams != NULL) { strlcpy(suffix, entry->d_name, (size_t)PATH_MAX - pathname_len); open_log_file_at_path(pathname, streams); } else if (!is_process_running((pid_t)pid) || (pid == curpid && curproc_stack_log_file && strcmp(entry->d_name, curproc_stack_log_file) != 0)) { strlcpy(suffix, entry->d_name, (size_t)PATH_MAX - pathname_len); if (delete_logging_file(pathname) == 0) { if (pid == curpid) { malloc_report(ASL_LEVEL_INFO, "stack logs deleted from %s\n", pathname); } else { malloc_report(ASL_LEVEL_INFO, "process %ld no longer exists, stack logs deleted from %s\n", pid, pathname); } } } } } closedir(dp); } static void reap_orphaned_log_files(pid_t target_pid, remote_task_file_streams *streams) { reap_orphaned_log_files_in_hierarchy(_PATH_TMP, NULL, target_pid, streams); char *env_var_names[] = {"TMPDIR", "MallocStackLoggingDirectory"}; for (unsigned i = 0; i < sizeof(env_var_names) / sizeof(char *); i++) { char directory[PATH_MAX]; bool success = getenv_from_process(target_pid, env_var_names[i], directory, sizeof(directory)); if (success && strcmp(directory, _PATH_TMP) != 0) { reap_orphaned_log_files_in_hierarchy(directory, NULL, target_pid, streams); } } // Now reap files left over in any other accessible app-specific temp directories. // These could be from sandbox'ed apps. #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR char *root_of_temp_directories = "/private/var/mobile/Containers/Data/Application"; // ugh - hard-coding to user name "mobile". // Works for all iOS's up to now. char *temp_directories_path_format = "/tmp"; #else // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR char *root_of_temp_directories = "/private/var/folders"; char *temp_directories_path_format = "//T"; #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR reap_orphaned_log_files_in_hierarchy(root_of_temp_directories, temp_directories_path_format, target_pid, streams); } /* * Since there a many errors that could cause stack logging to get disabled, this is a convenience method * for disabling any future logging in this process and for informing the user. */ static void disable_stack_logging(void) { malloc_report(ASL_LEVEL_INFO, "stack logging disabled due to previous errors.\n"); stack_logging_enable_logging = 0; malloc_logger = NULL; __syscall_logger = NULL; disable_stack_logging_lite(); } static boolean_t uniquing_table_memory_was_deleted = false; __attribute__((visibility("hidden"))) boolean_t __uniquing_table_memory_was_deleted(void) { return uniquing_table_memory_was_deleted; } __attribute__((visibility("hidden"))) void __delete_uniquing_table_memory_while_locked(void) { // Clean out the memory (if not done already) if (pre_write_buffers && pre_write_buffers->uniquing_table != NULL) { __destroy_uniquing_table(pre_write_buffers->uniquing_table); pre_write_buffers->uniquing_table = NULL; uniquing_table_memory_was_deleted = true; } // Clear the shared memory address so client tools won't look for the uniquing table memory __mach_stack_logging_shared_memory_address = 0; } /* A wrapper around write() that will try to reopen the index/stack file and * write to it if someone closed it underneath us (e.g. the process we just * started decide to close all file descriptors except stin/err/out). Some * programs like to do that and calling abort() on them is rude. */ static ssize_t robust_write(int fd, const void *buf, size_t nbyte) { extern int errno; ssize_t written = write(fd, buf, nbyte); if (written == -1 && errno == EBADF) { char *file_to_reopen = NULL; int *fd_to_reset = NULL; // descriptor was closed on us. We need to reopen it if (fd == index_file_descriptor) { file_to_reopen = __stack_log_file_path__; fd_to_reset = &index_file_descriptor; } else { // We don't know about this file. Return (and abort()). malloc_report(ASL_LEVEL_INFO, "Unknown file descriptor; expecting stack logging index file\n"); return -1; } // The file *should* already exist. If not, fail. fd = open(file_to_reopen, O_WRONLY | O_APPEND); if (fd < 3) { // If we somehow got stdin/out/err, we need to relinquish them and // get another fd. int fds_to_close[3] = {0}; while (fd < 3) { if (fd == -1) { malloc_report(ASL_LEVEL_INFO, "unable to re-open stack logging file %s\n", file_to_reopen); delete_log_files(); return -1; } fds_to_close[fd] = 1; fd = dup(fd); } // We have an fd we like. Close the ones we opened. if (fds_to_close[0]) { close(0); } if (fds_to_close[1]) { close(1); } if (fds_to_close[2]) { close(2); } } *fd_to_reset = fd; written = write(fd, buf, nbyte); } return written; } static void flush_data(void) { ssize_t written; // signed size_t size_t remaining; char *p; if (index_file_descriptor == -1) { if (create_log_file() == NULL) { return; } } // Write the events before the index so that hopefully the events will be on disk if the index refers to them. p = pre_write_buffers->index_buffer; remaining = (size_t)pre_write_buffers->next_free_index_buffer_offset; while (remaining > 0) { written = robust_write(index_file_descriptor, p, remaining); if (written == -1) { malloc_report( ASL_LEVEL_INFO, "Unable to write to stack logging file %s (%s)\n", __stack_log_file_path__, strerror(errno)); disable_stack_logging(); return; } p += written; remaining -= written; } pre_write_buffers->start_index_offset += pre_write_buffers->next_free_index_buffer_offset; pre_write_buffers->next_free_index_buffer_offset = 0; } __attribute__((visibility("hidden"))) boolean_t __prepare_to_log_stacks(boolean_t lite_or_vmlite_mode) { if (!pre_write_buffers) { last_logged_malloc_address = 0ul; logging_use_compaction = (stack_logging_dontcompact ? 0 : logging_use_compaction); // Create a VM region to hold the pre-write index and stack buffers. The address of this VM region will be // encoded into the stack log file name, so that the stack log reading code running in remote analysis // processes can find it and map it into the analysis process. This allows remote analysis processes to access // these buffers to get logs for even the most recent allocations. The remote process will need to pause this // process to assure that the contents of these buffers don't change while being inspected. // // We used to use shm_open() to create a shared memory region for this, but since this code runs in arbitrary // processes that may have sandbox restrictions that don't allow the creation of shared memory regions, // we're using this "create a region and put its address in the stack log file name" approach. size_t full_shared_mem_size = sizeof(stack_buffer_shared_memory); pre_write_buffers = mmap( 0, full_shared_mem_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, VM_MAKE_TAG(VM_MEMORY_ANALYSIS_TOOL), 0); if (MAP_FAILED == pre_write_buffers) { malloc_report(ASL_LEVEL_INFO, "error creating VM region for stack logging output buffers\n"); disable_stack_logging(); return false; } // Store and use the buffer offsets in shared memory so that they can be accessed remotely pre_write_buffers->start_index_offset = 0ull; pre_write_buffers->next_free_index_buffer_offset = 0; // create the backtrace uniquing table pre_write_buffers->uniquing_table = __create_uniquing_table(lite_or_vmlite_mode); if (!pre_write_buffers->uniquing_table) { malloc_report(ASL_LEVEL_INFO, "error while allocating stack uniquing table\n"); disable_stack_logging(); return false; } pre_write_buffers->vm_stackid_table = NULL; uint64_t stack_buffer_sz = (uint64_t)round_page(sizeof(vm_address_t) * STACK_LOGGING_MAX_STACK_SIZE); stack_buffer = (vm_address_t *)sld_allocate_pages(stack_buffer_sz); if (!stack_buffer) { malloc_report(ASL_LEVEL_INFO, "error while allocating stack trace buffer\n"); disable_stack_logging(); return false; } // lite_mode doesn't use a file if (lite_or_vmlite_mode) { __mach_stack_logging_shared_memory_address = (uint64_t) pre_write_buffers; } else { // this call ensures that the log files exist; analyzing processes will rely on this assumption. if (create_log_file() == NULL) { /* postponement support requires cleaning up these structures now */ __destroy_uniquing_table(pre_write_buffers->uniquing_table); sld_deallocate_pages(stack_buffer, stack_buffer_sz); stack_buffer = NULL; munmap(pre_write_buffers, full_shared_mem_size); pre_write_buffers = NULL; if (!stack_logging_postponed) { disable_stack_logging(); } return false; } } } return true; } __attribute__((visibility("hidden"))) void __prepare_to_log_stacks_stage2(void) { static int stage2done = 0; if (!stage2done) { // malloc() can be called by the following, so these need to be done outside the stack_logging_lock but after the buffers // have been set up. atexit(delete_log_files); // atexit() can call malloc() // Reaping orphaned stack log files from dead processes is a nicety, to help // reduce wasted disk space. But we don't *always* have to do it. Specifically, // do not reap orphaned stack log files if the process name is sandboxd or taskgated, // or if the MallocStackLoggingNoReaping env var is set to any value other than "no" // (case-insensitive) or "0". This provides multiple ways to fix // "processes hang if sandboxd is running with // MallocStackLogging enabled", which happened because there were two different // places down inside reap_orphaned_log_files() which called sysctl() for KERN_PROCARGS2 // or KERN_PROC_PID, causing iteration of the process list in the kernel, which takes // a lock, which can't happen when processes are in a transitional state. bool should_reap = true; const char *progname = getprogname(); if (progname && (strcmp(progname, "sandboxd") == 0 || strcmp(progname, "taskgated") == 0)) { should_reap = false; } if (should_reap) { char *noreap = getenv("MallocStackLoggingNoReaping"); if (noreap && strcasecmp(noreap, "no") != 0 && strcmp(noreap, "0") != 0) { should_reap = false; } } if (should_reap) { reap_orphaned_log_files(getpid(), NULL); // this calls opendir() which calls malloc() } stage2done = 1; } } __attribute__((visibility("hidden"))) void __malloc_lock_stack_logging() { _malloc_lock_lock(&stack_logging_lock); thread_doing_logging = (vm_address_t)_os_tsd_get_direct(__TSD_THREAD_SELF); } __attribute__((visibility("hidden"))) void __malloc_unlock_stack_logging() { thread_doing_logging = 0; _malloc_lock_unlock(&stack_logging_lock); } const uint64_t __invalid_stack_id = (uint64_t)(-1ll); // returns the stack id or invalid_stack_id if any kind of error // this needs to be done while stack_logging_lock is locked) __attribute__((visibility("hidden"))) uint64_t __enter_stack_into_table_while_locked(vm_address_t self_thread, uint32_t num_hot_to_skip, boolean_t add_thread_id, size_t ptr_size) { // gather stack uint32_t count; thread_stack_pcs(stack_buffer, STACK_LOGGING_MAX_STACK_SIZE - 1, &count); // only gather up to STACK_LOGGING_MAX_STACK_SIZE-1 since we append thread id if (add_thread_id) { stack_buffer[count++] = self_thread + 1; // stuffing thread # in the coldest slot. Add 1 to match what the old stack logging did. } // skip stack frames after the malloc call num_hot_to_skip += 3; // __disk_stack_logging_log_stack | __enter_stack_into_table_while_locked | thread_stack_pcs if (count <= num_hot_to_skip) { // Oops! Didn't get a valid backtrace from thread_stack_pcs(). return __invalid_stack_id; } // unique stack in memory count -= num_hot_to_skip; #if __LP64__ mach_vm_address_t *frames = (mach_vm_address_t*)stack_buffer + num_hot_to_skip; #else mach_vm_address_t frames[STACK_LOGGING_MAX_STACK_SIZE]; uint32_t i; for (i = 0; i < count; i++) { frames[i] = stack_buffer[i+num_hot_to_skip]; } #endif uint64_t uniqueStackIdentifier = __invalid_stack_id; while (!enter_frames_in_table(pre_write_buffers->uniquing_table, &uniqueStackIdentifier, frames, count, ptr_size)) { if (!__expand_uniquing_table(pre_write_buffers->uniquing_table)) return __invalid_stack_id; } return uniqueStackIdentifier; } static void decrement_ref_count(table_slot_t *table_slot, size_t ptr_size) { if (table_slot->refcount_slot.refcount > 0) { table_slot->refcount_slot.refcount -= ptr_size; if (table_slot->refcount_slot.refcount == 0) { table_slot->slots.slot0 = table_slot->slots.slot1 = 0; } } } __attribute__((visibility("hidden"))) void __decrement_table_slot_refcount(uint64_t stack_id, size_t ptr_size) { __malloc_lock_stack_logging(); // see if msl lite was disabled behind our backs if (!is_stack_logging_lite_enabled()) { __malloc_unlock_stack_logging(); return; } backtrace_uniquing_table *uniquing_table = pre_write_buffers->uniquing_table; assert(uniquing_table->nodes_use_refcount); assert(!uniquing_table->in_client_process); slot_parent parent = stack_id; slot_parent prev_parent = __invalid_stack_id; do { if (parent == prev_parent) { malloc_report(ASL_LEVEL_ERR, "circular parent reference in __decrement_table_slot_refcount\n"); break; } prev_parent = parent; table_slot_t *table_slot = (table_slot_t *) (uniquing_table->u.table + (parent * 2)); parent = table_slot->refcount_slot.parent; decrement_ref_count(table_slot, ptr_size); } while (parent != slot_no_parent_refcount); __malloc_unlock_stack_logging(); } void __disk_stack_logging_log_stack(uint32_t type_flags, uintptr_t zone_ptr, uintptr_t arg2, uintptr_t arg3, uintptr_t return_val, uint32_t num_hot_to_skip) { if (!stack_logging_enable_logging || stack_logging_postponed) { return; } bool stack_logging_mode_lite_or_vmlite = stack_logging_mode == stack_logging_mode_lite || stack_logging_mode == stack_logging_mode_vmlite; if (stack_logging_mode_lite_or_vmlite && !((type_flags & stack_logging_type_vm_allocate) || (type_flags & stack_logging_type_vm_deallocate))) { return; } uintptr_t size; uintptr_t ptr_arg; // check incoming data if (type_flags & stack_logging_type_alloc && type_flags & stack_logging_type_dealloc) { size = arg3; ptr_arg = arg2; // the original pointer if (ptr_arg == 0) { // realloc(NULL, size) same as malloc(size) type_flags ^= stack_logging_type_dealloc; } else { // realloc(arg1, arg2) -> result is same as free(arg1); malloc(arg2) -> result __disk_stack_logging_log_stack( stack_logging_type_dealloc, zone_ptr, ptr_arg, (uintptr_t)0, (uintptr_t)0, num_hot_to_skip + 1); __disk_stack_logging_log_stack(stack_logging_type_alloc, zone_ptr, size, (uintptr_t)0, return_val, num_hot_to_skip + 1); return; } } if (type_flags & stack_logging_type_dealloc || type_flags & stack_logging_type_vm_deallocate) { // For VM deallocations we need to know the size, since they don't always match the // VM allocations. It would be nice if arg2 was the size, for consistency with alloc and // realloc events. However we can't easily make that change because all projects // (malloc.c, GC auto_zone, and gmalloc) have historically put the pointer in arg2 and 0 as // the size in arg3. We'd need to change all those projects in lockstep, which isn't worth // the trouble. ptr_arg = arg2; size = arg3; if (ptr_arg == 0) { return; // free(nil) } } if (type_flags & stack_logging_type_alloc || type_flags & stack_logging_type_vm_allocate) { if (return_val == 0 || return_val == (uintptr_t)MAP_FAILED) { return; // alloc that failed } size = arg2; } if (type_flags & stack_logging_type_vm_allocate || type_flags & stack_logging_type_vm_deallocate) { mach_port_t targetTask = (mach_port_t)zone_ptr; // For now, ignore "injections" of VM into other tasks. if (targetTask != mach_task_self()) { return; } } type_flags &= stack_logging_valid_type_flags; vm_address_t self_thread = (vm_address_t)_os_tsd_get_direct(__TSD_THREAD_SELF); if (thread_doing_logging == self_thread) { // Prevent a thread from deadlocking against itself if vm_allocate() or malloc() // is called below here, from __prepare_to_log_stacks() or _prepare_to_log_stacks_stage2(), // or if we are logging an event and need to call __expand_uniquing_table() which calls // vm_allocate() to grow stack logging data structures. Any such "administrative" // vm_allocate or malloc calls would attempt to recursively log those events. return; } // lock and enter _malloc_lock_lock(&stack_logging_lock); thread_doing_logging = self_thread; // for preventing deadlock'ing on stack logging on a single thread if (stack_logging_mode_lite_or_vmlite && (type_flags & stack_logging_type_vm_deallocate)) { if (pre_write_buffers && pre_write_buffers->vm_stackid_table) { radix_tree_delete(&pre_write_buffers->vm_stackid_table, trunc_page(ptr_arg), round_page(ptr_arg + size) - trunc_page(ptr_arg)); goto out; } } // now actually begin __prepare_to_log_stacks(false); // since there could have been a fatal (to stack logging) error such as the log files not being created, check these variables // before continuing if (!stack_logging_enable_logging || stack_logging_postponed) { goto out; } if (type_flags & stack_logging_type_alloc) { // Only do this second stage of setup when we first record a malloc (as opposed to a VM allocation), // to ensure that the malloc zone has already been created as is necessary for this. __prepare_to_log_stacks_stage2(); } // compaction if (last_logged_malloc_address && (type_flags & stack_logging_type_dealloc) && STACK_LOGGING_DISGUISE(ptr_arg) == last_logged_malloc_address) { // *waves hand* the last allocation never occurred pre_write_buffers->next_free_index_buffer_offset -= (uint32_t)sizeof(stack_logging_index_event); last_logged_malloc_address = 0ul; goto out; } uint64_t uniqueStackIdentifier; if (stack_logging_mode_lite_or_vmlite) { uniqueStackIdentifier = __enter_stack_into_table_while_locked(self_thread, num_hot_to_skip, false, 1); } else { uniqueStackIdentifier = __enter_stack_into_table_while_locked(self_thread, num_hot_to_skip, true, 0); } if (uniqueStackIdentifier == __invalid_stack_id) { goto out; } if (stack_logging_mode_lite_or_vmlite && (type_flags & stack_logging_type_vm_allocate)) { if (pre_write_buffers) { if (!pre_write_buffers->vm_stackid_table) { pre_write_buffers->vm_stackid_table = radix_tree_create(); pre_write_buffers->vm_stackid_table_size = radix_tree_size(pre_write_buffers->vm_stackid_table); } if (pre_write_buffers->vm_stackid_table) { uint64_t address = return_val; radix_tree_insert(&pre_write_buffers->vm_stackid_table, trunc_page(address), round_page(address+size) - trunc_page(address), uniqueStackIdentifier); pre_write_buffers->vm_stackid_table_size = radix_tree_size(pre_write_buffers->vm_stackid_table); } } goto out; } stack_logging_index_event current_index; if (type_flags & stack_logging_type_alloc || type_flags & stack_logging_type_vm_allocate) { current_index.address = STACK_LOGGING_DISGUISE(return_val); current_index.argument = size; if (logging_use_compaction) { last_logged_malloc_address = current_index.address; // disguised } } else { current_index.address = STACK_LOGGING_DISGUISE(ptr_arg); current_index.argument = size; last_logged_malloc_address = 0ul; } current_index.offset_and_flags = STACK_LOGGING_OFFSET_AND_FLAGS(uniqueStackIdentifier, type_flags); // the following line is a good debugging tool for logging each allocation event as it happens. // malloc_report(ASL_LEVEL_INFO, "{0x%lx, %lld}\n", STACK_LOGGING_DISGUISE(current_index.address), uniqueStackIdentifier); // flush the data buffer to disk if necessary if (pre_write_buffers->next_free_index_buffer_offset + sizeof(stack_logging_index_event) >= STACK_LOGGING_BLOCK_WRITING_SIZE) { flush_data(); } // store bytes in buffers memcpy(pre_write_buffers->index_buffer + pre_write_buffers->next_free_index_buffer_offset, ¤t_index, sizeof(stack_logging_index_event)); pre_write_buffers->next_free_index_buffer_offset += (uint32_t)sizeof(stack_logging_index_event); out: thread_doing_logging = 0; _malloc_lock_unlock(&stack_logging_lock); } void __stack_logging_fork_prepare(void) { _malloc_lock_lock(&stack_logging_lock); } void __stack_logging_fork_parent(void) { _malloc_lock_unlock(&stack_logging_lock); } void __stack_logging_fork_child(void) { malloc_logger = NULL; stack_logging_enable_logging = 0; _malloc_lock_init(&stack_logging_lock); } void __stack_logging_early_finished(void) { stack_logging_finished_init = 1; stack_logging_postponed = 0; } // support for gdb and others checking for stack_logging locks __attribute__((visibility("hidden"))) boolean_t __stack_logging_locked(void) { bool acquired_lock = _malloc_lock_trylock(&stack_logging_lock); if (acquired_lock) { _malloc_lock_unlock(&stack_logging_lock); } return (acquired_lock ? false : true); } #pragma mark - #pragma mark Remote Stack Log Access #pragma mark - Design notes: /* * * this first one will look through the index, find the "stack_identifier" (i.e. the offset in the log file), and call the third * function listed here. * extern kern_return_t __mach_stack_logging_get_frames(task_t task, mach_vm_address_t address, mach_vm_address_t * stack_frames_buffer, uint32_t max_stack_frames, uint32_t *num_frames); * // Gets the last allocation record about address * * if !address, will load index and iterate through (expensive) * else will load just index, search for stack, and then use third function here to retrieve. (also expensive) * extern kern_return_t __mach_stack_logging_enumerate_records(task_t task, mach_vm_address_t address, void * enumerator(mach_stack_logging_record_t, void *), void *context); * // Applies enumerator to all records involving address sending context as enumerator's second parameter; if !address, applies * enumerator to all records * * this function will load the stack file, look for the stack, and follow up to STACK_LOGGING_FORCE_FULL_BACKTRACE_EVERY references * to * reconstruct. * extern kern_return_t __mach_stack_logging_frames_for_uniqued_stack(task_t task, uint64_t stack_identifier, mach_vm_address_t * stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count); * // Given a uniqued_stack fills stack_frames_buffer * */ #pragma mark - #pragma mark Backtrace Uniquing Table Reading and Lookup // This is client-side code to get a stack log from a uniquing_table. static void free_uniquing_table_chunks(backtrace_uniquing_table *uniquing_table) { table_chunk_header_t *table_chunk_header = uniquing_table->u.first_table_chunk_hdr; assert(uniquing_table->in_client_process); while (table_chunk_header) { mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)(uintptr_t)(table_chunk_header->table_chunk), table_chunk_header->table_chunk_size); table_chunk_header_t *next = table_chunk_header->next_table_chunk_header; free(table_chunk_header); table_chunk_header = next; } } static kern_return_t read_uniquing_table_from_task(task_t remote_task, backtrace_uniquing_table *uniquing_table) { assert(uniquing_table->in_client_process); mach_vm_address_t next_address_to_read = uniquing_table->table_address; uint64_t remaining_size_to_read = uniquing_table->tableSize; const mach_vm_size_t two_gigabytes = 2ull * 1024 * 1024 * 1024; // attempting to read 4 GB in one call fails, so try a max of 2 GB table_chunk_header_t **table_chunk_hdr_ptr = &(uniquing_table->u.first_table_chunk_hdr); *table_chunk_hdr_ptr = NULL; while (remaining_size_to_read > 0ull) { vm_address_t local_table_chunk_address = 0ul; mach_msg_type_number_t local_table_chunk_size = 0; mach_vm_size_t next_size_to_read = (remaining_size_to_read > two_gigabytes) ? two_gigabytes : remaining_size_to_read; while (1) { kern_return_t err = mach_vm_read( remote_task, next_address_to_read, next_size_to_read, &local_table_chunk_address, &local_table_chunk_size); if (err == KERN_SUCCESS) { *table_chunk_hdr_ptr = malloc(sizeof(table_chunk_header_t)); table_chunk_header_t *table_chunk_hdr = *table_chunk_hdr_ptr; table_chunk_hdr->num_nodes_in_chunk = local_table_chunk_size / (sizeof(mach_vm_address_t) * 2); ; table_chunk_hdr->table_chunk = (mach_vm_address_t *)local_table_chunk_address; table_chunk_hdr->table_chunk_size = local_table_chunk_size; table_chunk_hdr->next_table_chunk_header = NULL; // initialize it, in case it is the last chunk table_chunk_hdr_ptr = &(table_chunk_hdr->next_table_chunk_header); // set up to assign next chunk to this next_address_to_read += local_table_chunk_size; remaining_size_to_read -= local_table_chunk_size; // fprintf(stderr, "requested %#qx, got %#x of %#qx at %p from backtrace uniquing table of target process\n", // next_size_to_read, local_table_chunk_size, uniquing_table->tableSize, table_chunk_hdr); break; } else { // fprintf(stderr, "requested %#qx, failed\n", next_size_to_read); next_size_to_read /= 2; if (next_size_to_read <= 1024 * 1024) { // We couldn't even map one megabyte? Let's call that an error... free_uniquing_table_chunks(uniquing_table); return err; } } } } return KERN_SUCCESS; } static mach_vm_address_t * get_node_from_uniquing_table(backtrace_uniquing_table *uniquing_table, uint64_t index_pos) { assert(uniquing_table->in_client_process); table_chunk_header_t *table_chunk_hdr = uniquing_table->u.first_table_chunk_hdr; uint64_t start_node_of_chunk = 0; while (table_chunk_hdr && index_pos > start_node_of_chunk + table_chunk_hdr->num_nodes_in_chunk) { table_chunk_hdr = table_chunk_hdr->next_table_chunk_header; if (table_chunk_hdr) { start_node_of_chunk += table_chunk_hdr->num_nodes_in_chunk; } } // Handle case where someone passes an invalid stack id // get_node_from_uniquing_table should be more tolerant if (!table_chunk_hdr) { return NULL; } uint64_t index_in_chunk = index_pos - start_node_of_chunk; mach_vm_address_t *node = table_chunk_hdr->table_chunk + (index_in_chunk * 2); return node; } static void unwind_stack_from_table_index(backtrace_uniquing_table *uniquing_table, uint64_t index_pos, mach_vm_address_t *out_frames_buffer, uint32_t *out_frames_count, uint32_t max_frames, boolean_t use_refcount) { mach_vm_address_t *node = get_node_from_uniquing_table(uniquing_table, index_pos); uint32_t foundFrames = 0; slot_parent end_parent = use_refcount ? slot_no_parent_refcount : slot_no_parent_normal; if (node && index_pos < uniquing_table->numNodes) { while (foundFrames < max_frames) { table_slot_t *table_slot = (table_slot_t *) (node); slot_address address = use_refcount ? table_slot->refcount_slot.address : table_slot->normal_slot.address; out_frames_buffer[foundFrames++] = address; if (use_refcount && table_slot->refcount_slot.refcount == 0) { break; } slot_parent parent = use_refcount ? table_slot->refcount_slot.parent : table_slot->normal_slot.parent; if (parent == end_parent) { break; } node = get_node_from_uniquing_table(uniquing_table, parent); } } *out_frames_count = foundFrames; } #pragma mark - caching __attribute__((always_inline)) static inline size_t hash_index(uint64_t address, size_t max_pos) { return (size_t)((address >> 2) % (max_pos - 1)); // simplicity rules. } __attribute__((always_inline)) static inline size_t hash_multiplier(size_t capacity, uint32_t allowed_collisions) { return (capacity / (allowed_collisions * 2 + 1)); } __attribute__((always_inline)) static inline size_t next_hash(size_t hash, size_t multiplier, size_t capacity, uint32_t collisions) { hash += multiplier * collisions; if (hash >= capacity) { hash -= capacity; } return hash; } static void transfer_node(remote_index_cache *cache, remote_index_node *old_node) { uint32_t collisions = 0; size_t pos = hash_index(old_node->address, cache->cache_node_capacity); size_t multiplier = hash_multiplier(cache->cache_node_capacity, cache->collision_allowance); do { if (cache->table_memory[pos].address == old_node->address) { // hit like this shouldn't happen. fprintf(stderr, "impossible collision! two address==address lists! (transfer_node)\n"); break; } else if (cache->table_memory[pos].address == 0) { // empty cache->table_memory[pos] = *old_node; break; } else { collisions++; pos = next_hash(pos, multiplier, cache->cache_node_capacity, collisions); } } while (collisions <= cache->collision_allowance); if (collisions > cache->collision_allowance) { fprintf(stderr, "reporting bad hash function! disk stack logging reader %lu bit. (transfer_node)\n", sizeof(void *) * 8); } } static void expand_cache(remote_index_cache *cache) { // keep old stats size_t old_node_capacity = cache->cache_node_capacity; remote_index_node *old_table = cache->table_memory; // double size cache->cache_size <<= 2; cache->cache_node_capacity <<= 2; cache->collision_allowance += 3; cache->table_memory = (void *)calloc(cache->cache_node_capacity, sizeof(remote_index_node)); // repopulate (expensive!) size_t i; for (i = 0; i < old_node_capacity; i++) { if (old_table[i].address) { transfer_node(cache, &old_table[i]); } } free(old_table); // printf("cache expanded to %0.2f mb (eff: %3.0f%%, capacity: %lu, nodes: %llu, llnodes: %llu)\n", //((float)(cache->cache_size))/(1 << 20), ((float)(cache->cache_node_count)*100.0)/((float)(cache->cache_node_capacity)), // cache->cache_node_capacity, cache->cache_node_count, cache->cache_llnode_count); } static void insert_node(remote_index_cache *cache, uint64_t address, uint64_t index_file_offset) { uint32_t collisions = 0; size_t pos = hash_index(address, cache->cache_node_capacity); size_t multiplier = hash_multiplier(cache->cache_node_capacity, cache->collision_allowance); while (1) { if (cache->table_memory[pos].address == 0ull || cache->table_memory[pos].address == address) { // hit or empty cache->table_memory[pos].address = address; cache->table_memory[pos].index_file_offset = index_file_offset; // Inserted it! Break out of the loop. break; } collisions++; pos = next_hash(pos, multiplier, cache->cache_node_capacity, collisions); if (collisions > cache->collision_allowance) { expand_cache(cache); pos = hash_index(address, cache->cache_node_capacity); multiplier = hash_multiplier(cache->cache_node_capacity, cache->collision_allowance); collisions = 0; } } } // Kudos to Daniel Delwood for this function. This is called in an analysis tool process // to share a VM region from a target process, without the target process needing to explicitly // share the region itself via shm_open(). The VM_FLAGS_RETURN_DATA_ADDR flag is necessary // for iOS in case the target process uses a different VM page size than the analysis tool process. static mach_vm_address_t map_shared_memory_from_task(task_t sourceTask, mach_vm_address_t sourceAddress, mach_vm_size_t sourceSize) { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR int mapRequestFlags = VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR; mach_vm_address_t mapRequestAddress = sourceAddress; mach_vm_size_t mapRequestSize = sourceSize; #else // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR // Sadly, VM_FLAGS_RETURN_DATA_ADDR isn't available to us; align everything manually. int mapRequestFlags = VM_FLAGS_ANYWHERE; mach_vm_address_t mapRequestAddress = trunc_page(sourceAddress); mach_vm_size_t mapRequestSize = round_page(sourceAddress + sourceSize) - mapRequestAddress; #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR mach_vm_address_t mappedAddress = 0; vm_prot_t outCurrentProt = VM_PROT_NONE; vm_prot_t outMaxProt = VM_PROT_NONE; kern_return_t err = mach_vm_remap(mach_task_self(), &mappedAddress, mapRequestSize, 0, mapRequestFlags, sourceTask, mapRequestAddress, false, &outCurrentProt, &outMaxProt, VM_INHERIT_NONE); if (err != KERN_SUCCESS) { return 0; } return mappedAddress + (sourceAddress - mapRequestAddress); } static kern_return_t update_cache_for_file_streams(remote_task_file_streams *descriptors) { remote_index_cache *cache = descriptors->cache; // create from scratch if necessary. if (!cache) { descriptors->cache = cache = (remote_index_cache *)calloc((size_t)1, sizeof(remote_index_cache)); cache->cache_node_capacity = 1 << 14; cache->collision_allowance = 17; cache->last_index_file_offset = 0; cache->cache_size = cache->cache_node_capacity * sizeof(remote_index_node); cache->table_memory = (void *)calloc(cache->cache_node_capacity, sizeof(remote_index_node)); cache->shmem = (stack_buffer_shared_memory *)map_shared_memory_from_task(descriptors->remote_task, descriptors->remote_stack_buffer_shared_memory_address, sizeof(stack_buffer_shared_memory)); if (!cache->shmem) { // failed to connect to the shared memory region; warn and continue. malloc_report(ASL_LEVEL_INFO, "warning: unable to map shared memory from %llx in target process %d; no stack backtraces will be available.\n", descriptors->remote_stack_buffer_shared_memory_address, descriptors->remote_pid); } cache->lite_mode = descriptors->task_uses_lite_or_vmlite_mode; if (cache->shmem && cache->shmem->vm_stackid_table) { cache->vm_stackid_table = (struct radix_tree *)map_shared_memory_from_task( descriptors->remote_task, (mach_vm_address_t) cache->shmem->vm_stackid_table, cache->shmem->vm_stackid_table_size); if (!cache->vm_stackid_table) { malloc_report(ASL_LEVEL_INFO, "warning: unable to map vm_stackid table from %llx in target process %d; no VM stack backtraces will be available.\n", (mach_vm_address_t) cache->shmem->vm_stackid_table, descriptors->remote_pid); } } } // suspend and see how much updating there is to do. there are three scenarios, listed below bool update_snapshot = false; if (descriptors->remote_task != mach_task_self()) { task_suspend(descriptors->remote_task); } struct stat file_statistics; if (descriptors->index_file_stream) { fstat(fileno(descriptors->index_file_stream), &file_statistics); } else { file_statistics.st_size = 0; } size_t read_size = (descriptors->task_is_64_bit ? sizeof(stack_logging_index_event64) : sizeof(stack_logging_index_event32)); uint64_t read_this_update = 0; // the delta indecies is a complex number; there are three cases: // 1. there is no shared memory (or we can't connect); diff the last_index_file_offset from the filesize. // 2. the only updates have been in shared memory; disk file didn't change at all. delta_indecies should be zero, scan snapshot // only. // 3. the updates have flushed to disk, meaning that most likely there is new data on disk that wasn't read from shared memory. // correct delta_indecies for the pre-scanned amount and read the new data from disk and shmem. uint64_t delta_indecies = (file_statistics.st_size - cache->last_index_file_offset) / read_size; uint32_t last_snapshot_scan_index = 0; if (delta_indecies && cache->shmem) { // case 3: add cache scanned to known from disk and recalc cache->last_index_file_offset += cache->snapshot.next_free_index_buffer_offset; delta_indecies = (file_statistics.st_size - cache->last_index_file_offset) / read_size; update_snapshot = true; } else if (cache->shmem) { // case 2: set the last snapshot scan count so we don't rescan something we've seen. last_snapshot_scan_index = cache->snapshot.next_free_index_buffer_offset / (uint32_t)read_size; } // no update necessary for the file; check if need a snapshot. if (delta_indecies == 0) { if (cache->shmem && !update_snapshot) { update_snapshot = (cache->shmem->next_free_index_buffer_offset != cache->snapshot.next_free_index_buffer_offset); } } // need to update the snapshot if in lite mode and haven't yet read the uniquing table if (descriptors->task_uses_lite_or_vmlite_mode && cache->uniquing_table_snapshot.numPages == 0) { update_snapshot = true; } // if a snapshot is necessary, memcpy from remote frozen process' memory // note: there were two ways to do this - spin lock or suspend. suspend allows us to // analyze processes even if they were artificially suspended. with a lock, there'd be // worry that the target was suspended with the lock taken. kern_return_t err = KERN_SUCCESS; if (update_snapshot) { memcpy(&cache->snapshot, cache->shmem, sizeof(stack_buffer_shared_memory)); // also need to update our version of the remote uniquing table vm_address_t local_uniquing_address = 0ul; mach_msg_type_number_t local_uniquing_size = 0; mach_vm_size_t desired_size = round_page(sizeof(backtrace_uniquing_table)); if ((err = mach_vm_read(descriptors->remote_task, (mach_vm_address_t)cache->shmem->uniquing_table, desired_size, &local_uniquing_address, &local_uniquing_size)) != KERN_SUCCESS || local_uniquing_size != desired_size) { fprintf(stderr, "error while attempting to mach_vm_read remote stack uniquing table (%d): %s\n", err, mach_error_string(err)); } else { // the mach_vm_read was successful, so acquire the uniquing table // need to re-read the table, so deallocate the current memory cache->uniquing_table_snapshot.in_client_process = true; free_uniquing_table_chunks(&cache->uniquing_table_snapshot); // The following line copies the uniquing table structure data, but the actual uniquing table memory is invalid // since it's a pointer from the remote process. cache->uniquing_table_snapshot = *((backtrace_uniquing_table *)local_uniquing_address); cache->uniquing_table_snapshot.nodes_use_refcount = cache->lite_mode; cache->uniquing_table_snapshot.u.first_table_chunk_hdr = NULL; cache->uniquing_table_snapshot.in_client_process = true; // Read the uniquing table memory from the target process. err = read_uniquing_table_from_task(descriptors->remote_task, &(cache->uniquing_table_snapshot)); if (err) { fprintf(stderr, "error while attempting to mach_vm_read remote stack uniquing table contents (%d): %s\n", err, mach_error_string(err)); } // Check the error status below, after further deallocating and resuming the target task. mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)local_uniquing_address, (mach_vm_size_t)local_uniquing_size); } } // resume if (descriptors->remote_task != mach_task_self()) { task_resume(descriptors->remote_task); } if (err != KERN_SUCCESS) { // To Do: further clean up allocated resources, and also try to prevent printing numerous identical "out of memory" errors // (maybe we should abort?). return err; } if (!update_snapshot && delta_indecies == 0) { return KERN_SUCCESS; // absolutely no updating needed. } FILE *the_index = (descriptors->index_file_stream); // prepare for the read; target process could be 32 or 64 bit. stack_logging_index_event32 *target_32_index = NULL; stack_logging_index_event64 *target_64_index = NULL; // perform the update from the file uint32_t i; if (delta_indecies) { char bufferSpace[4096]; // 4 kb target_32_index = (stack_logging_index_event32 *)bufferSpace; target_64_index = (stack_logging_index_event64 *)bufferSpace; size_t number_slots = (size_t)(4096 / read_size); size_t read_count = 0; if (fseeko(the_index, (off_t)(cache->last_index_file_offset), SEEK_SET)) { fprintf(stderr, "error while attempting to cache information from remote stack index file. (update_cache_for_file_streams)\n"); } off_t current_index_position = cache->last_index_file_offset; do { number_slots = (size_t)MIN(delta_indecies - read_this_update, number_slots); read_count = fread(bufferSpace, read_size, number_slots, the_index); if (descriptors->task_is_64_bit) { for (i = 0; i < read_count; i++) { insert_node(cache, STACK_LOGGING_DISGUISE(target_64_index[i].address), (uint64_t)current_index_position); read_this_update++; current_index_position += read_size; } } else { for (i = 0; i < read_count; i++) { insert_node(cache, (mach_vm_address_t)STACK_LOGGING_DISGUISE(target_32_index[i].address), (uint64_t)current_index_position); read_this_update++; current_index_position += read_size; } } } while (read_count); if (read_this_update < delta_indecies) { fprintf(stderr, "insufficient data in remote stack index file; expected more records.\n"); } cache->last_index_file_offset += read_this_update * read_size; } if (update_snapshot) { target_32_index = (stack_logging_index_event32 *)(cache->snapshot.index_buffer); target_64_index = (stack_logging_index_event64 *)(cache->snapshot.index_buffer); uint32_t free_snapshot_scan_index = cache->snapshot.next_free_index_buffer_offset / (uint32_t)read_size; off_t current_index_position = cache->snapshot.start_index_offset; if (descriptors->task_is_64_bit) { for (i = last_snapshot_scan_index; i < free_snapshot_scan_index; i++) { insert_node(cache, STACK_LOGGING_DISGUISE(target_64_index[i].address), (uint64_t)(current_index_position + (i * read_size))); } } else { for (i = last_snapshot_scan_index; i < free_snapshot_scan_index; i++) { insert_node(cache, (mach_vm_address_t)STACK_LOGGING_DISGUISE(target_32_index[i].address), (uint64_t)(current_index_position + (i * read_size))); } } } return KERN_SUCCESS; } static void destroy_cache_for_file_streams(remote_task_file_streams *descriptors) { if (!descriptors->cache) { return; } if (descriptors->cache->shmem) { munmap(descriptors->cache->shmem, sizeof(stack_buffer_shared_memory)); } free(descriptors->cache->table_memory); free_uniquing_table_chunks(&descriptors->cache->uniquing_table_snapshot); free(descriptors->cache); descriptors->cache = NULL; } #pragma mark - internal static FILE * open_log_file_at_path(char *pathname, remote_task_file_streams *streams) { FILE *file = fopen(pathname, "r"); if (!file) { return NULL; } char *log_file_name = strrchr(pathname, '/'); char *p = log_file_name; // File names are of the form stack-logs..
..XXXXXX.index if (p) p = strchr(p, '.'); // skip past "stack-logs" if (p) p = strchr(p + 1, '.'); // skip past "." if (p) p++; // skip past '.' if (!p) { return NULL; } char *shared_memory_address_string = p; // The hex address of the remote_index_cache in the target process // is given in the stack log file name, following the pid and a period. streams->remote_stack_buffer_shared_memory_address = strtoll(shared_memory_address_string, NULL, 16); streams->index_file_stream = file; return file; } // In the stack log analysis process, find the stack logging file for target process // by scanning the given directory for entries with names of the form "stack-logs..*.index" // If we find such an entry then open that stack logging file. static FILE * open_log_file_from_directory(pid_t pid, char *directory, remote_task_file_streams *streams) { DIR *dp; struct dirent *entry; char prefix_and_pid[PATH_MAX]; char pathname[PATH_MAX]; FILE *file = NULL; // Check for access permissions in case we're sandbox'ed. if (access(directory, R_OK | X_OK) == 0 && (dp = opendir(directory)) != NULL) { // It's OK to use snprintf in this routine since it should only be called by the clients // of stack logging, and thus calls to malloc are OK. snprintf(prefix_and_pid, (size_t)PATH_MAX, "%s%d.", stack_log_file_base_name, pid); // make sure to use "%s%d." rather than just "%s%d" to match the whole pid size_t prefix_and_pid_length = strlen(prefix_and_pid); while ((entry = readdir(dp)) != NULL) { if (strncmp(entry->d_name, prefix_and_pid, prefix_and_pid_length) == 0) { snprintf(pathname, (size_t)PATH_MAX, "%s/%s", directory, entry->d_name); file = open_log_file_at_path(pathname, streams); break; } } closedir(dp); } return file; } // Read the launch data of the target process from the kernel to find the // value of the environment variable named env_var_name. Since this function // uses alloca() to temporarily allocate space for data copied from the kernel, // and we don't want to malloc space so that this can be called from malloc stack // logging code in the target process as well, we copy the result into the // env_var_value_buf of length max_path_len supplied by the caller. static bool getenv_from_process(pid_t pid, char *env_var_name, char *env_var_value_buf, size_t buf_length) { env_var_value_buf[0] = '\0'; // Just call getenv() if pid is the current process, partly to avoid the sysctl() // call which can cause system deadlock ( "processes hang // if sandboxd is running with MallocStackLogging enabled"). But it probably // doesn't completely fix that since there is another sysctl() call in is_process_running() // when checking to see if the process corresponding to an existing stack log file // is still running. if (pid == getpid()) { char *env_var_value = getenv(env_var_name); if (!env_var_value) { return false; } else { strlcpy(env_var_value_buf, env_var_value, buf_length); return true; } } int mib[3]; size_t argbufSize = 0; // Must initialize this to 0 so this works when compiled for x86_64. // First get the maximum arguments size, to determine the necessary buffer size. mib[0] = CTL_KERN; mib[1] = KERN_ARGMAX; size_t size = sizeof(argbufSize); int ret = sysctl(mib, 2, &argbufSize, &size, NULL, 0); if (ret != 0) { return false; } mib[0] = CTL_KERN; mib[1] = KERN_PROCARGS2; // The older KERN_PROCARGS is deprecated. mib[2] = pid; char *argbuf = (char *)alloca(argbufSize); ret = sysctl(mib, 3, argbuf, &argbufSize, (void *)NULL, 0); if (ret != 0) { return false; } argbuf[argbufSize - 1] = '\0'; // make sure the buffer is null-terminated char *p = argbuf; char *endp = &argbuf[argbufSize]; // Skip over argc, which is always 4 bytes long (int-sized), even in 64-bit architectures. int argumentCount = *((int *)argbuf); p += sizeof(argumentCount); // Skip over arguments, using the argumentCount read from the start of argbuf. argumentCount++; // increment argumentCount to also skip saved exec path, which comes first for (int argumentNum = 0; argumentNum < argumentCount && p < endp; argumentNum++) { while (p < endp && *p != '\0') p++; while (p < endp && *p == '\0') p++; // saved exec path sometimes has multiple nul's } size_t env_var_name_length = strlen(env_var_name); // Examine environment variables. while ((p + env_var_name_length + 1) < endp && *p != '\0') { if (strncmp(p, env_var_name, env_var_name_length) == 0 && p[env_var_name_length] == '=') { p += env_var_name_length + 1; strlcpy(env_var_value_buf, p, buf_length); //malloc_report(ASL_LEVEL_INFO, "found env var %s='%s'\n", env_var_name, env_var_value_buf); return true; } while (p < endp && *p != '\0') p++; p++; } return false; } static FILE * open_log_file(pid_t target_pid, remote_task_file_streams *streams) { static bool already_reaped = false; if (!already_reaped) { // reap any left-over log files (for non-existent processes, but not for this analysis process) reap_orphaned_log_files(target_pid, streams); already_reaped = true; } if (streams->index_file_stream != NULL) { // reap_orphaned_log_files opened the file return streams->index_file_stream; } // Since we're searching for the log file here, not creating it, we can search in any order we want. // So look at MallocStackLoggingDirectory last since that is almost never set. FILE *file = open_log_file_from_directory(target_pid, _PATH_TMP, streams); if (!file) { char *env_var_names[] = {"TMPDIR", "MallocStackLoggingDirectory"}; for (unsigned i = 0; i < sizeof(env_var_names) / sizeof(char *); i++) { char directory[PATH_MAX]; bool success = getenv_from_process(target_pid, env_var_names[i], directory, sizeof(directory)); if (success) { file = open_log_file_from_directory(target_pid, directory, streams); if (file) { break; } } } } return file; } // shared_memory_address is non-zero when in lite mode and this is called for the first time on a task static remote_task_file_streams * retain_file_streams_for_task(task_t task, vm_address_t shared_memory_address) { if (task == MACH_PORT_NULL) { return NULL; } _malloc_lock_lock(&remote_fd_list_lock); // see if they're already in use uint32_t i = 0; for (i = 0; i < remote_task_fd_count; i++) { if (remote_fds[i].remote_task == task) { remote_fds[i].in_use_count++; _malloc_lock_unlock(&remote_fd_list_lock); return &remote_fds[i]; } } // open them uint32_t failures = 0; if (remote_task_fd_count == STACK_LOGGING_MAX_SIMUL_REMOTE_TASKS_INSPECTED) { while (remote_fds[next_remote_task_fd].in_use_count > 0) { next_remote_task_fd++; if (next_remote_task_fd == STACK_LOGGING_MAX_SIMUL_REMOTE_TASKS_INSPECTED) { next_remote_task_fd = 0; } failures++; if (failures >= STACK_LOGGING_MAX_SIMUL_REMOTE_TASKS_INSPECTED) { _malloc_lock_unlock(&remote_fd_list_lock); return NULL; } } fclose(remote_fds[next_remote_task_fd].index_file_stream); destroy_cache_for_file_streams(&remote_fds[next_remote_task_fd]); } pid_t pid; kern_return_t err = pid_for_task(task, &pid); if (err != KERN_SUCCESS) { _malloc_lock_unlock(&remote_fd_list_lock); return NULL; } remote_task_file_streams *this_task_streams = &remote_fds[next_remote_task_fd]; if (shared_memory_address != 0) { this_task_streams->remote_stack_buffer_shared_memory_address = shared_memory_address; this_task_streams->task_uses_lite_or_vmlite_mode = true; } else { open_log_file(pid, this_task_streams); if (this_task_streams->index_file_stream == NULL) { _malloc_lock_unlock(&remote_fd_list_lock); return NULL; } } // check if target pid is running 64-bit int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; struct kinfo_proc processInfo; size_t bufsize = sizeof(processInfo); if (sysctl(mib, (unsigned)(sizeof(mib) / sizeof(int)), &processInfo, &bufsize, NULL, (size_t)0) == 0 && bufsize > 0) { this_task_streams->task_is_64_bit = processInfo.kp_proc.p_flag & P_LP64; } else { this_task_streams->task_is_64_bit = 0; } // otherwise set vars and go this_task_streams->in_use_count = 1; this_task_streams->remote_task = task; this_task_streams->remote_pid = pid; next_remote_task_fd++; if (next_remote_task_fd == STACK_LOGGING_MAX_SIMUL_REMOTE_TASKS_INSPECTED) { next_remote_task_fd = 0; } remote_task_fd_count = MIN(remote_task_fd_count + 1, STACK_LOGGING_MAX_SIMUL_REMOTE_TASKS_INSPECTED); _malloc_lock_unlock(&remote_fd_list_lock); return this_task_streams; } static void release_file_streams_for_task(task_t task) { _malloc_lock_lock(&remote_fd_list_lock); // decrement in-use count uint32_t i = 0; for (i = 0; i < remote_task_fd_count; i++) { if (remote_fds[i].remote_task == task) { remote_fds[i].in_use_count--; break; } } _malloc_lock_unlock(&remote_fd_list_lock); } #pragma mark - extern kern_return_t __mach_stack_logging_start_reading(task_t task, vm_address_t shared_memory_address, boolean_t *uses_lite_mode) { remote_task_file_streams *remote_fd = retain_file_streams_for_task(task, shared_memory_address); if (remote_fd == NULL) { return KERN_FAILURE; } *uses_lite_mode = remote_fd->task_uses_lite_or_vmlite_mode; return KERN_SUCCESS; } kern_return_t __mach_stack_logging_stop_reading(task_t task) { kern_return_t err = KERN_SUCCESS; release_file_streams_for_task(task); _malloc_lock_lock(&remote_fd_list_lock); for (uint32_t i = 0; i < remote_task_fd_count; i++) { if (remote_fds[i].remote_task == task) { if (remote_fds[i].in_use_count > 0) { // Hmm... the client is in the middle of a stack log reading call? err = KERN_FAILURE; } else { // remote_fds[i].in_use_count is 0 so don't decrement it! fclose(remote_fds[i].index_file_stream); remote_fds[i].index_file_stream = NULL; destroy_cache_for_file_streams(&remote_fds[i]); remote_fds[i].remote_task = 0; } break; } } _malloc_lock_unlock(&remote_fd_list_lock); return err; } // This function is no longer used. It was a hack that required an analysis tool process // to read the target tasks's __stack_log_file_path__ variable then pass the value of // that to this function. This is now handled automatically all within this file, by // having the stack log reading code read the environment variables of the target process. // This function should be removed once no clients are calling it. kern_return_t __mach_stack_logging_set_file_path(task_t task, char *file_path) { return KERN_SUCCESS; } kern_return_t __mach_stack_logging_get_frames(task_t task, mach_vm_address_t address, mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count) { remote_task_file_streams *remote_fd = retain_file_streams_for_task(task, 0); if (remote_fd == NULL) { return KERN_FAILURE; } kern_return_t err = update_cache_for_file_streams(remote_fd); if (err != KERN_SUCCESS) { release_file_streams_for_task(task); return err; } uint32_t collisions = 0; size_t hash = hash_index(address, remote_fd->cache->cache_node_capacity); size_t multiplier = hash_multiplier(remote_fd->cache->cache_node_capacity, remote_fd->cache->collision_allowance); uint64_t located_file_position = 0; bool found = false; do { if (remote_fd->cache->table_memory[hash].address == address) { // hit! located_file_position = remote_fd->cache->table_memory[hash].index_file_offset; found = true; break; } else if (remote_fd->cache->table_memory[hash].address == 0ull) { // failure! break; } collisions++; hash = next_hash(hash, multiplier, remote_fd->cache->cache_node_capacity, collisions); } while (collisions <= remote_fd->cache->collision_allowance); if (found) { // prepare for the read; target process could be 32 or 64 bit. stack_logging_index_event32 *target_32_index = NULL; stack_logging_index_event64 *target_64_index = NULL; if (located_file_position >= remote_fd->cache->last_index_file_offset) { // must be in shared memory if (remote_fd->cache->shmem) { if (remote_fd->task_is_64_bit) { target_64_index = (stack_logging_index_event64 *)(remote_fd->cache->snapshot.index_buffer + (located_file_position - remote_fd->cache->snapshot.start_index_offset)); located_file_position = STACK_LOGGING_OFFSET(target_64_index->offset_and_flags); } else { target_32_index = (stack_logging_index_event32 *)(remote_fd->cache->snapshot.index_buffer + (located_file_position - remote_fd->cache->snapshot.start_index_offset)); located_file_position = STACK_LOGGING_OFFSET(target_32_index->offset_and_flags); } } else { found = false; } } else { // it's written to disk char bufferSpace[128]; size_t read_size = (remote_fd->task_is_64_bit ? sizeof(stack_logging_index_event64) : sizeof(stack_logging_index_event32)); fseeko(remote_fd->index_file_stream, (off_t)located_file_position, SEEK_SET); size_t read_count = fread(bufferSpace, read_size, (size_t)1, remote_fd->index_file_stream); if (read_count) { if (remote_fd->task_is_64_bit) { target_64_index = (stack_logging_index_event64 *)bufferSpace; located_file_position = STACK_LOGGING_OFFSET(target_64_index->offset_and_flags); } else { target_32_index = (stack_logging_index_event32 *)bufferSpace; located_file_position = STACK_LOGGING_OFFSET(target_32_index->offset_and_flags); } } else { found = false; } } } release_file_streams_for_task(task); if (!found) { return KERN_FAILURE; } return __mach_stack_logging_get_frames_for_stackid(task, located_file_position, stack_frames_buffer, max_stack_frames, count, NULL); } kern_return_t __mach_stack_logging_enumerate_records(task_t task, mach_vm_address_t address, void enumerator(mach_stack_logging_record_t, void *), void *context) { remote_task_file_streams *remote_fd = retain_file_streams_for_task(task, 0); if (remote_fd == NULL) { return KERN_FAILURE; } bool reading_all_addresses = (address == 0 ? true : false); mach_stack_logging_record_t pass_record; kern_return_t err = KERN_SUCCESS; // update (read index file once and only once) err = update_cache_for_file_streams(remote_fd); if (err != KERN_SUCCESS) { release_file_streams_for_task(task); return err; } FILE *the_index = (remote_fd->index_file_stream); // prepare for the read; target process could be 32 or 64 bit. char bufferSpace[2048]; // 2 kb stack_logging_index_event32 *target_32_index = (stack_logging_index_event32 *)bufferSpace; stack_logging_index_event64 *target_64_index = (stack_logging_index_event64 *)bufferSpace; uint32_t target_addr_32 = (uint32_t)STACK_LOGGING_DISGUISE((uint32_t)address); uint64_t target_addr_64 = STACK_LOGGING_DISGUISE((uint64_t)address); size_t read_size = (remote_fd->task_is_64_bit ? sizeof(stack_logging_index_event64) : sizeof(stack_logging_index_event32)); size_t number_slots = (size_t)(2048 / read_size); uint64_t total_slots = remote_fd->cache->last_index_file_offset / read_size; // perform the search size_t read_count = 0; int64_t current_file_offset = 0; uint32_t i; do { // at this point, we need to read index events; read them from the file until it's necessary to grab them from the shared // memory snapshot // and crop file reading to the point where we last scanned number_slots = (size_t)MIN(number_slots, total_slots); // if out of file to read (as of the time we entered this function), try to use shared memory snapshot if (number_slots == 0) { if (remote_fd->cache->shmem && remote_fd->cache->snapshot.start_index_offset + remote_fd->cache->snapshot.next_free_index_buffer_offset > (uint64_t)current_file_offset) { // use shared memory target_32_index = (stack_logging_index_event32 *)remote_fd->cache->snapshot.index_buffer; target_64_index = (stack_logging_index_event64 *)remote_fd->cache->snapshot.index_buffer; read_count = (uint32_t)(remote_fd->cache->snapshot.start_index_offset + remote_fd->cache->snapshot.next_free_index_buffer_offset - current_file_offset) / read_size; current_file_offset += read_count * read_size; } else { break; } } else { // get and save index (enumerator could modify) fseeko(the_index, current_file_offset, SEEK_SET); read_count = fread(bufferSpace, read_size, number_slots, the_index); current_file_offset = ftello(the_index); total_slots -= read_count; } if (remote_fd->task_is_64_bit) { for (i = 0; i < read_count; i++) { if (reading_all_addresses || target_64_index[i].address == target_addr_64) { pass_record.address = STACK_LOGGING_DISGUISE(target_64_index[i].address); pass_record.argument = target_64_index[i].argument; pass_record.stack_identifier = STACK_LOGGING_OFFSET(target_64_index[i].offset_and_flags); pass_record.type_flags = STACK_LOGGING_FLAGS_AND_USER_TAG(target_64_index[i].offset_and_flags); enumerator(pass_record, context); } } } else { for (i = 0; i < read_count; i++) { if (reading_all_addresses || target_32_index[i].address == target_addr_32) { pass_record.address = STACK_LOGGING_DISGUISE(target_32_index[i].address); pass_record.argument = target_32_index[i].argument; pass_record.stack_identifier = STACK_LOGGING_OFFSET(target_32_index[i].offset_and_flags); pass_record.type_flags = STACK_LOGGING_FLAGS_AND_USER_TAG(target_32_index[i].offset_and_flags); enumerator(pass_record, context); } } } } while (read_count); release_file_streams_for_task(task); return err; } uint64_t __mach_stack_logging_stackid_for_vm_region(task_t task, mach_vm_address_t address) { remote_task_file_streams *remote_fd = retain_file_streams_for_task(task, 0); if (remote_fd == NULL) { return __invalid_stack_id; } kern_return_t err = update_cache_for_file_streams(remote_fd); if (err != KERN_SUCCESS) { release_file_streams_for_task(task); return __invalid_stack_id; } uint64_t stackid = __invalid_stack_id; if (remote_fd->cache && remote_fd->cache->vm_stackid_table) { stackid = radix_tree_lookup(remote_fd->cache->vm_stackid_table, address); } release_file_streams_for_task(task); return stackid; } kern_return_t __mach_stack_logging_frames_for_uniqued_stack(task_t task, uint64_t stack_identifier, mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count) { return __mach_stack_logging_get_frames_for_stackid(task, stack_identifier, stack_frames_buffer, max_stack_frames, count, NULL); } kern_return_t __mach_stack_logging_get_frames_for_stackid(task_t task, uint64_t stack_identifier, mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count, bool *last_frame_is_threadid) { remote_task_file_streams *remote_fd = retain_file_streams_for_task(task, 0); if (remote_fd == NULL) { return KERN_FAILURE; } // ensure that the uniquing table snapshot is valid kern_return_t err = update_cache_for_file_streams(remote_fd); if (err != KERN_SUCCESS) { release_file_streams_for_task(task); return err; } bool lite_mode = remote_fd->cache->lite_mode; unwind_stack_from_table_index(&remote_fd->cache->uniquing_table_snapshot, stack_identifier, stack_frames_buffer, count, max_stack_frames, lite_mode); release_file_streams_for_task(task); if (last_frame_is_threadid) { *last_frame_is_threadid = !lite_mode; } if (*count) { return KERN_SUCCESS; } else { return KERN_FAILURE; } } kern_return_t __attribute__((visibility("default"))) __mach_stack_logging_uniquing_table_read_stack(struct backtrace_uniquing_table *uniquing_table, uint64_t stackid, mach_vm_address_t *out_frames_buffer, uint32_t *out_frames_count, uint32_t max_frames) { unwind_stack_from_table_index(uniquing_table, stackid, out_frames_buffer, out_frames_count, max_frames, uniquing_table-> nodes_use_refcount); return *out_frames_count ? KERN_SUCCESS : KERN_FAILURE; } struct backtrace_uniquing_table * __mach_stack_logging_copy_uniquing_table(task_t task) { remote_task_file_streams *remote_fd = retain_file_streams_for_task(task, 0); if (remote_fd == NULL) { return NULL; } // ensure that the uniquing table snapshot is valid kern_return_t err = update_cache_for_file_streams(remote_fd); if (err != KERN_SUCCESS || remote_fds->cache->uniquing_table_snapshot.numPages == 0) { release_file_streams_for_task(task); return NULL; } /* Steal the uniqing table snapshot. A new snapshot will be taken next time someone calls * update_cache_for_file_streams */ backtrace_uniquing_table *table = malloc(sizeof(backtrace_uniquing_table)); memcpy(table, &remote_fds->cache->uniquing_table_snapshot, sizeof(backtrace_uniquing_table)); bzero(&remote_fds->cache->uniquing_table_snapshot, sizeof(backtrace_uniquing_table)); remote_fds->cache->uniquing_table_snapshot.in_client_process = true; table->refcount = 1; release_file_streams_for_task(task); return table; } void __mach_stack_logging_uniquing_table_release(struct backtrace_uniquing_table *table) { if (!table) { return; } assert(table->refcount > 0); table->refcount--; if (table->refcount == 0) { free_uniquing_table_chunks(table); free(table); } } void __mach_stack_logging_uniquing_table_retain(struct backtrace_uniquing_table *table) { assert(table->refcount > 0); table->refcount++; } static const size_t uniquingTableDataAlign = 16 * 1024; static const size_t uniquingTableHeaderLength = 16; static inline size_t roundUp(size_t x, size_t alignment) { return x + (-x % alignment); } size_t __mach_stack_logging_uniquing_table_sizeof(struct backtrace_uniquing_table *table) { size_t size = 0; size += uniquingTableHeaderLength; //header size += sizeof(backtrace_uniquing_table); size = roundUp(size, uniquingTableDataAlign); assert(table->in_client_process); table_chunk_header_t *table_chunk_header = table->u.first_table_chunk_hdr; while (table_chunk_header) { size += 2 * sizeof(mach_vm_address_t) * table_chunk_header->num_nodes_in_chunk; table_chunk_header = table_chunk_header->next_table_chunk_header; } return size; } void * __mach_stack_logging_uniquing_table_serialize(struct backtrace_uniquing_table *table, mach_vm_size_t *size) { *size = __mach_stack_logging_uniquing_table_sizeof(table); mach_vm_address_t buffer_address = 0; kern_return_t kr = mach_vm_allocate(mach_task_self(), &buffer_address, *size, VM_FLAGS_ANYWHERE); if (kr != KERN_SUCCESS) { *size = 0; return NULL; } void *buffer = (void*)buffer_address; uint8_t *p = buffer; memcpy(p, "MslUniquingTable", uniquingTableHeaderLength); p += uniquingTableHeaderLength; memcpy(p, table, sizeof(backtrace_uniquing_table)); p += sizeof(backtrace_uniquing_table); p = ((uint8_t*)buffer) + roundUp(p - (uint8_t*)buffer, uniquingTableDataAlign); table_chunk_header_t *table_chunk_header = table->u.first_table_chunk_hdr; uint64_t num_nodes = 0; while (table_chunk_header) { num_nodes += table_chunk_header->num_nodes_in_chunk; size_t chunk_size = 2 * sizeof(mach_vm_address_t) * (size_t)table_chunk_header->num_nodes_in_chunk; kr = mach_vm_copy(mach_task_self(), (mach_vm_address_t)table_chunk_header->table_chunk, chunk_size, (vm_address_t)p); if (kr != KERN_SUCCESS) { memcpy(p, table_chunk_header->table_chunk, chunk_size); } p += chunk_size; table_chunk_header = table_chunk_header->next_table_chunk_header; } assert(num_nodes == table->numNodes); return buffer; } struct backtrace_uniquing_table * __mach_stack_logging_uniquing_table_copy_from_serialized(void *buffer, size_t size) { if (size < uniquingTableHeaderLength + sizeof(backtrace_uniquing_table)) { return NULL; } uint8_t *p = buffer; if (strncmp(buffer, "MslUniquingTable", uniquingTableHeaderLength) != 0) { return NULL; } p += uniquingTableHeaderLength; backtrace_uniquing_table *table = malloc(sizeof(backtrace_uniquing_table)); memcpy(table, p, sizeof(backtrace_uniquing_table)); p += sizeof(backtrace_uniquing_table); p = ((uint8_t*)buffer) + roundUp(p - (uint8_t*)buffer, uniquingTableDataAlign); table->u.first_table_chunk_hdr = malloc(sizeof(table_chunk_header_t)); table->refcount = 1; mach_vm_size_t chunkSize = 2 * table->numNodes * sizeof(mach_vm_address_t); mach_vm_address_t chunkAddr = 0; if (roundUp(uniquingTableHeaderLength + sizeof(backtrace_uniquing_table), uniquingTableDataAlign) + chunkSize < size ) { goto fail; } kern_return_t kr = mach_vm_allocate(mach_task_self(), &chunkAddr, chunkSize, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_ANALYSIS_TOOL)); if (kr != KERN_SUCCESS) { goto fail; } table->u.first_table_chunk_hdr->num_nodes_in_chunk = table->numNodes; table->u.first_table_chunk_hdr->table_chunk_size = chunkSize; table->u.first_table_chunk_hdr->table_chunk = (mach_vm_address_t*) chunkAddr; table->u.first_table_chunk_hdr->next_table_chunk_header = NULL; kr = mach_vm_copy(mach_task_self(), (mach_vm_address_t)p, chunkSize, (mach_vm_address_t) table->u.first_table_chunk_hdr->table_chunk); if (kr != KERN_SUCCESS) { goto fail; } return table; fail: if (table) { if (table->u.first_table_chunk_hdr) { free(table->u.first_table_chunk_hdr); } free(table); } if (chunkAddr) { mach_vm_deallocate(mach_task_self(), chunkAddr, chunkSize); } return NULL; } #ifdef TEST_DISK_STACK_LOGGING // cc -o stack_logging_disk stack_logging_disk.c -DTEST_DISK_STACK_LOGGING #include int main() { int status; int i; size_t total_globals = 0ul; fprintf(stderr, "master test process is %d\n", getpid()); fprintf(stderr, "sizeof pre_write_buffers: %lu\n", sizeof(pre_write_buffers)); total_globals += sizeof(pre_write_buffers); fprintf(stderr, "sizeof stack_buffer: %lu\n", sizeof(stack_buffer)); total_globals += sizeof(stack_buffer); fprintf(stderr, "sizeof last_logged_malloc_address: %lu\n", sizeof(last_logged_malloc_address)); total_globals += sizeof(last_logged_malloc_address); fprintf(stderr, "sizeof stack_log_file_base_name: %lu\n", sizeof(stack_log_file_base_name)); total_globals += sizeof(stack_log_file_base_name); fprintf(stderr, "sizeof stack_log_file_suffix: %lu\n", sizeof(stack_log_file_suffix)); total_globals += sizeof(stack_log_file_suffix); fprintf(stderr, "sizeof __stack_log_file_path__ (index_file_path): %lu\n", (size_t)PATH_MAX); total_globals += (size_t)PATH_MAX; fprintf(stderr, "sizeof index_file_descriptor: %lu\n", sizeof(index_file_descriptor)); total_globals += sizeof(index_file_descriptor); fprintf(stderr, "sizeof remote_fds: %lu\n", sizeof(remote_fds)); total_globals += sizeof(remote_fds); fprintf(stderr, "sizeof next_remote_task_fd: %lu\n", sizeof(next_remote_task_fd)); total_globals += sizeof(next_remote_task_fd); fprintf(stderr, "sizeof remote_task_fd_count: %lu\n", sizeof(remote_task_fd_count)); total_globals += sizeof(remote_task_fd_count); fprintf(stderr, "sizeof remote_fd_list_lock: %lu\n", sizeof(remote_fd_list_lock)); total_globals += sizeof(remote_fd_list_lock); fprintf(stderr, "sizeof logging_use_compaction: %lu\n", sizeof(logging_use_compaction)); total_globals += sizeof(logging_use_compaction); fprintf(stderr, "size of all global data: %lu\n", total_globals); create_log_file(); // create a few child processes and exit them cleanly so their logs should get cleaned up fprintf(stderr, "\ncreating child processes and exiting cleanly\n"); for (i = 0; i < 3; i++) { if (fork() == 0) { fprintf(stderr, "\nin child processes %d\n", getpid()); create_log_file(); fprintf(stderr, "exiting child processes %d\n", getpid()); exit(1); } wait(&status); } // create a few child processes and abruptly _exit them, leaving their logs around fprintf(stderr, "\ncreating child processes and exiting abruptly, leaving logs around\n"); for (i = 0; i < 3; i++) { if (fork() == 0) { fprintf(stderr, "\nin child processes %d\n", getpid()); create_log_file(); fprintf(stderr, "exiting child processes %d\n", getpid()); _exit(1); } wait(&status); } // this should reap any remaining logs fprintf(stderr, "\nexiting master test process %d\n", getpid()); delete_log_files(); return 0; } #endif /* vim: set noet:ts=4:sw=4:cindent: */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/stack_logging_internal.h ================================================ /* * Copyright (c) 2016, Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef stack_logging_disk_h #define stack_logging_disk_h extern int stack_logging_enable_logging; /* when clear, no logging takes place */ extern int stack_logging_dontcompact; /* default is to compact; when set does not compact alloc/free logs; useful for tracing history */ extern int stack_logging_finished_init; /* set after we've returned from the Libsystem initialiser */ extern int stack_logging_postponed; /* set if we needed to postpone logging till after initialisation */ extern int stack_logging_mode; extern const uint64_t __invalid_stack_id; // returns the stack id uint64_t __enter_stack_into_table_while_locked(vm_address_t self_thread, uint32_t num_hot_to_skip, boolean_t add_thread_id, size_t ptr_size); void __malloc_lock_stack_logging(); void __malloc_unlock_stack_logging(); // support for gdb and others checking for stack_logging locks extern boolean_t __stack_logging_locked(); extern boolean_t __prepare_to_log_stacks(boolean_t lite_or_vmlite_mode); extern void __prepare_to_log_stacks_stage2(); // support for multi-threaded forks extern void __stack_logging_fork_prepare(); extern void __stack_logging_fork_parent(); extern void __stack_logging_fork_child(); extern void __stack_logging_early_finished(); void __decrement_table_slot_refcount(uint64_t stackID, size_t size); void __delete_uniquing_table_memory_while_locked(); boolean_t __uniquing_table_memory_was_deleted(void); // Returns true if the stack logging lite malloc zone is enabled. Does not indicate if VM-only lite mode is enabled. boolean_t is_stack_logging_lite_enabled(void); #endif /* stack_logging_disk_h */ ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/thresholds.h ================================================ /* * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __THRESHOLDS_H #define __THRESHOLDS_H /* * Tiny region size definitions; these are split into quanta of 16 bytes, * 64520 blocks is the magical value of how many quanta we can fit in a 1mb * region including the region trailer and metadata. */ #define SHIFT_TINY_QUANTUM 4 #define SHIFT_TINY_CEIL_BLOCKS 16 // ceil(log2(NUM_TINY_BLOCKS)) #define TINY_QUANTUM (1 << SHIFT_TINY_QUANTUM) #define NUM_TINY_BLOCKS 64520 #define NUM_TINY_CEIL_BLOCKS (1 << SHIFT_TINY_CEIL_BLOCKS) /* * Small region size definitions. * * We can only represent up to 1<<15 for msize; but we choose to stay * even below that to avoid the convention msize=0 => msize = (1<<15) */ #define SHIFT_SMALL_QUANTUM (SHIFT_TINY_QUANTUM + 5) // 9 #define SMALL_QUANTUM (1 << SHIFT_SMALL_QUANTUM) // 512 bytes #define SHIFT_SMALL_CEIL_BLOCKS 14 // ceil(log2(NUM_SMALL_BLOCKs)) #define NUM_SMALL_BLOCKS 16319 #define NUM_SMALL_CEIL_BLOCKS (1 << SHIFT_SMALL_CEIL_BLOCKS) #define SMALL_BLOCKS_ALIGN (SHIFT_SMALL_CEIL_BLOCKS + SHIFT_SMALL_QUANTUM) // 23 #if MALLOC_TARGET_64BIT #define NUM_TINY_SLOTS 64 // number of slots for free-lists #else // MALLOC_TARGET_64BIT #define NUM_TINY_SLOTS 32 // number of slots for free-lists #endif // MALLOC_TARGET_64BIT /* * The threshold above which we start allocating from the small * magazines. Computed from the largest allocation we can make * in the tiny region (currently 1008 bytes on 64-bit, and * 496 bytes on 32-bit). */ #define SMALL_THRESHOLD ((NUM_TINY_SLOTS - 1) * TINY_QUANTUM) /* * The threshold above which we start allocating from the large * "region" (ie. direct vm_allocates). The LARGEMEM size is used * on macOS and 64bit iOS with 16k pages, > 2gb and > 2 cores once * CONFIG_SMALL_CUTOFF_DYNAMIC is enabled (TODO: rdar://problem/35395572) * Must be a multiple of SMALL_QUANTUM (512 bytes) */ #if MALLOC_TARGET_IOS #if MALLOC_TARGET_64BIT #define LARGE_THRESHOLD (15 * 1024) // <1 * 16k pages #define LARGE_THRESHOLD_LARGEMEM (64 * 1024) // 4 * 16k pages #else #define LARGE_THRESHOLD (15 * 1024) // <4 * 4k pages #define LARGE_THRESHOLD_LARGEMEM (64 * 1024) // 16 * 4k pages #endif #else #define LARGE_THRESHOLD (15 * 1024) // <4 * 4k pages #define LARGE_THRESHOLD_LARGEMEM (127 * 1024) // <32 * 4k pages #endif /* * The number of slots in the free-list for small blocks. To avoid going to * vm system as often on large memory machines, increase the number of free list * spots above some amount of RAM installed in the system. */ #define NUM_SMALL_SLOTS (LARGE_THRESHOLD >> SHIFT_SMALL_QUANTUM) #define NUM_SMALL_SLOTS_LARGEMEM (LARGE_THRESHOLD_LARGEMEM >> SHIFT_SMALL_QUANTUM) /* * When all memory is touched after a copy, vm_copy() is always a lose * But if the memory is only read, vm_copy() wins over memmove() at 3 or 4 pages * (on a G3/300MHz) * * This must be >= LARGE_THRESHOLD */ #if MALLOC_TARGET_IOS && MALLOC_TARGET_64BIT #define VM_COPY_THRESHOLD (48 * 1024) // 3 * 16k pages #define VM_COPY_THRESHOLD_LARGEMEM (96 * 1024) // 6 * 16k pages #else #define VM_COPY_THRESHOLD (40 * 1024) // 10 * 4k pages #define VM_COPY_THRESHOLD_LARGEMEM (128 * 1024) // 32 * 4k pages #endif /* * Large entry cache (death row) sizes. The large cache is bounded with * an overall top limit size, each entry is allowed a given slice of * that limit. */ #if MALLOC_TARGET_64BIT #define LARGE_ENTRY_CACHE_SIZE 16 #define LARGE_CACHE_SIZE_LIMIT ((vm_size_t)0x80000000) /* 2Gb */ #define LARGE_CACHE_SIZE_ENTRY_LIMIT (LARGE_CACHE_SIZE_LIMIT / LARGE_ENTRY_CACHE_SIZE) #else // MALLOC_TARGET_64BIT #define LARGE_ENTRY_CACHE_SIZE 8 #define LARGE_CACHE_SIZE_LIMIT ((vm_size_t)0x02000000) /* 32Mb */ #define LARGE_CACHE_SIZE_ENTRY_LIMIT (LARGE_CACHE_SIZE_LIMIT / LARGE_ENTRY_CACHE_SIZE) #endif // MALLOC_TARGET_64BIT /* * Large entry cache (death row) "flotsam" limits. Until the large cache * contains at least "high" bytes, the cache is not cleaned under memory * pressure. After that, memory pressure notifications cause cache cleaning * until the large cache drops below the "low" limit. */ #define SZONE_FLOTSAM_THRESHOLD_LOW (1024 * 512) #define SZONE_FLOTSAM_THRESHOLD_HIGH (1024 * 1024) /* * The magazine freelist array must be large enough to accomdate the allocation * granularity of both the tiny and small allocators. In addition, the last * slot in the list is special and reserved for coalesced regions bigger than * the overall max allocation size of the allocator. */ #define MAGAZINE_FREELIST_SLOTS (NUM_SMALL_SLOTS_LARGEMEM + 1) #define MAGAZINE_FREELIST_BITMAP_WORDS ((MAGAZINE_FREELIST_SLOTS + 31) >> 5) /* * Density threshold used in determining the level of emptiness before * moving regions to the recirc depot. */ #define DENSITY_THRESHOLD(a) \ ((a) - ((a) >> 2)) // "Emptiness" f = 0.25, so "Density" is (1 - f)*a. Generally: ((a) - ((a) >> -log2(f))) /* * Minimum number of regions to retain in a recirc depot. */ #define DEFAULT_RECIRC_RETAINED_REGIONS 2 /* Sanity checks. */ MALLOC_STATIC_ASSERT(NUM_SMALL_SLOTS_LARGEMEM == LARGE_THRESHOLD_LARGEMEM >> SHIFT_SMALL_QUANTUM, "NUM_SMALL_SLOTS_LARGEMEM must match LARGE_THRESHOLD_LARGEMEM >> SHIFT_SMALL_QUANTUM"); MALLOC_STATIC_ASSERT(NUM_TINY_SLOTS <= NUM_SMALL_SLOTS_LARGEMEM, "NUM_TINY_SLOTS must be less than or equal to NUM_SMALL_SLOTS_LARGEMEM"); MALLOC_STATIC_ASSERT((LARGE_THRESHOLD % SMALL_QUANTUM) == 0, "LARGE_THRESHOLD must be a multiple of SMALL_QUANTUM"); MALLOC_STATIC_ASSERT((LARGE_THRESHOLD_LARGEMEM % SMALL_QUANTUM) == 0, "LARGE_THRESHOLD_LARGEMEM must be a multiple of SMALL_QUANTUM"); MALLOC_STATIC_ASSERT((LARGE_THRESHOLD / SMALL_QUANTUM) <= NUM_SMALL_SLOTS, "LARGE_THRESHOLD must be less than NUM_SMALL_SLOTS * SMALL_QUANTUM"); MALLOC_STATIC_ASSERT((LARGE_THRESHOLD_LARGEMEM / SMALL_QUANTUM) <= NUM_SMALL_SLOTS_LARGEMEM, "LARGE_THRESHOLD_LARGEMEM must be less than NUM_SMALL_SLOTS * SMALL_QUANTUM"); MALLOC_STATIC_ASSERT(VM_COPY_THRESHOLD >= LARGE_THRESHOLD, "VM_COPY_THRESHOLD must be larger than LARGE_THRESHOLD"); MALLOC_STATIC_ASSERT(VM_COPY_THRESHOLD_LARGEMEM >= LARGE_THRESHOLD_LARGEMEM, "VM_COPY_THRESHOLD_LARGEMEM must be larger than LARGE_THRESHOLD_LARGEMEM"); #endif // __THRESHOLDS_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/trace.h ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __TRACE_H #define __TRACE_H // defines these two subclasses for us: // DBG_UMALLOC_EXTERNAL - for external entry points into malloc // DBG_UMALLOC_INTERNAL - for tracing internal malloc state #ifndef _MALLOC_BUILDING_CODES_ #include #define MALLOC_TRACE(code,arg1,arg2,arg3,arg4) \ { if (malloc_tracing_enabled) { kdebug_trace(code, arg1, arg2, arg3, arg4); } } #define TRACE_CODE(name, subclass, code) \ static const int TRACE_##name = KDBG_EVENTID(DBG_UMALLOC, subclass, code) #else # define DBG_UMALLOC 51 # define DBG_UMALLOC_EXTERNAL 0x1 # define DBG_UMALLOC_INTERNAL 0x2 # define STR(x) #x # define TRACE_CODE(name, subclass, code) \ printf("0x%x\t%s\n", ((DBG_UMALLOC << 24) | ((subclass & 0xff) << 16) | ((code & 0x3fff) << 2)), STR(name)) #endif // "external" trace points TRACE_CODE(malloc, DBG_UMALLOC_EXTERNAL, 0x01); TRACE_CODE(free, DBG_UMALLOC_EXTERNAL, 0x02); TRACE_CODE(realloc, DBG_UMALLOC_EXTERNAL, 0x03); TRACE_CODE(memalign, DBG_UMALLOC_EXTERNAL, 0x04); TRACE_CODE(calloc, DBG_UMALLOC_EXTERNAL, 0x05); TRACE_CODE(valloc, DBG_UMALLOC_EXTERNAL, 0x06); // "internal" trace points TRACE_CODE(nano_malloc, DBG_UMALLOC_INTERNAL, 0x1); TRACE_CODE(tiny_malloc, DBG_UMALLOC_INTERNAL, 0x2); TRACE_CODE(small_malloc, DBG_UMALLOC_INTERNAL, 0x3); TRACE_CODE(large_malloc, DBG_UMALLOC_INTERNAL, 0x4); TRACE_CODE(nano_free, DBG_UMALLOC_INTERNAL, 0x5); TRACE_CODE(tiny_free, DBG_UMALLOC_INTERNAL, 0x6); TRACE_CODE(small_free, DBG_UMALLOC_INTERNAL, 0x7); TRACE_CODE(large_free, DBG_UMALLOC_INTERNAL, 0x8); TRACE_CODE(malloc_memory_pressure, DBG_UMALLOC_INTERNAL, 0x9); TRACE_CODE(nano_memory_pressure, DBG_UMALLOC_INTERNAL, 0xa); TRACE_CODE(madvise, DBG_UMALLOC_INTERNAL, 0xb); TRACE_CODE(nanov2_region_allocation, DBG_UMALLOC_INTERNAL, 0x10); #endif // __TRACE_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/vm.c ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "internal.h" static volatile uintptr_t entropic_address = 0; static volatile uintptr_t entropic_limit = 0; MALLOC_NOEXPORT uint64_t malloc_entropy[2] = {0, 0}; #define ENTROPIC_KABILLION 0x10000000 /* 256Mb */ // align 64bit ARM shift to 32MB PTE entries #if MALLOC_TARGET_IOS && MALLOC_TARGET_64BIT #define ENTROPIC_SHIFT 25 #else // MALLOC_TARGET_IOS && MALLOC_TARGET_64BIT #define ENTROPIC_SHIFT SMALL_BLOCKS_ALIGN #endif void mvm_aslr_init(void) { // Prepare ASLR #if __i386__ || __x86_64__ || __arm64__ || (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) #if __i386__ uintptr_t stackbase = 0x8fe00000; int entropic_bits = 3; #elif __x86_64__ uintptr_t stackbase = USRSTACK64; int entropic_bits = 16; #elif __arm64__ #if __LP64__ uintptr_t stackbase = USRSTACK64; int entropic_bits = 7; #else // __LP64__ uintptr_t stackbase = USRSTACK; int entropic_bits = 3; #endif #else uintptr_t stackbase = USRSTACK; int entropic_bits = 3; #endif // assert(((1 << entropic_bits) - 1) << SMALL_BLOCKS_ALIGN < (stackbase - MAXSSIZ - ENTROPIC_KABILLION)); if (mvm_aslr_enabled()) { if (0 == entropic_address) { uintptr_t t = stackbase - MAXSSIZ - ((uintptr_t)(malloc_entropy[1] & ((1 << entropic_bits) - 1)) << ENTROPIC_SHIFT); OSAtomicCompareAndSwapLong(0, t, (volatile long *)&entropic_limit); OSAtomicCompareAndSwapLong(0, t - ENTROPIC_KABILLION, (volatile long *)&entropic_address); } } else { // zero slide when ASLR has been disabled by boot-arg. Eliminate cloaking. malloc_entropy[0] = 0; malloc_entropy[1] = 0; } #else // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR #error ASLR unhandled on this platform #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR } void * mvm_allocate_pages(size_t size, unsigned char align, unsigned debug_flags, int vm_page_label) { boolean_t add_guard_pages = debug_flags & MALLOC_ADD_GUARD_PAGES; boolean_t purgeable = debug_flags & MALLOC_PURGEABLE; mach_vm_address_t vm_addr; uintptr_t addr; mach_vm_size_t allocation_size = round_page_quanta(size); mach_vm_offset_t allocation_mask = ((mach_vm_offset_t)1 << align) - 1; int alloc_flags = VM_FLAGS_ANYWHERE | VM_MAKE_TAG(vm_page_label); kern_return_t kr; if (!allocation_size) { allocation_size = vm_page_quanta_size; } if (add_guard_pages) { if (align > vm_page_quanta_shift) { /* alignment greater than pagesize needs more work */ allocation_size += (1 << align) + vm_page_quanta_size; } else { allocation_size += 2 * vm_page_quanta_size; } } if (purgeable) { alloc_flags |= VM_FLAGS_PURGABLE; } if (allocation_size < size) { // size_t arithmetic wrapped! return NULL; } vm_addr = vm_page_quanta_size; kr = mach_vm_map(mach_task_self(), &vm_addr, allocation_size, allocation_mask, alloc_flags, MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT); if (kr) { malloc_zone_error(debug_flags, false, "can't allocate region\n*** mach_vm_map(size=%lu) failed (error code=%d)\n", size, kr); return NULL; } addr = (uintptr_t)vm_addr; if (add_guard_pages) { if (align > vm_page_quanta_shift) { /* calculate the first address inside the alignment padding * where we can place the guard page and still be aligned. * * |-----------------------------------------------------------| * |leading|gp| alloc |gp| t | * |-----------------------------------------------------------| */ uintptr_t alignaddr = ((addr + vm_page_quanta_size) + (1 << align) - 1) & ~((1 << align) - 1); size_t leading = alignaddr - addr - vm_page_quanta_size; size_t trailing = (1 << align) - vm_page_quanta_size - leading; /* Unmap the excess area. */ kr = mach_vm_deallocate(mach_task_self(), addr, leading); if (kr) { malloc_zone_error(debug_flags, false, "can't unmap excess guard region\n" "*** mach_vm_deallocate(addr=%p, size=%lu) failed (code=%d)\n", (void *)addr, leading, kr); return NULL; } kr = mach_vm_deallocate(mach_task_self(), addr + allocation_size - trailing, trailing); if (kr) { malloc_zone_error(debug_flags, false, "can't unmap excess trailing guard region\n" "*** mach_vm_deallocate(addr=%p, size=%lu) failed (code=%d)\n", (void *)(addr + allocation_size - trailing), trailing, kr); return NULL; } addr = alignaddr; } else { addr += vm_page_quanta_size; } mvm_protect((void *)addr, size, PROT_NONE, debug_flags); } return (void *)addr; } void * mvm_allocate_pages_securely(size_t size, unsigned char align, int vm_page_label, uint32_t debug_flags) { mach_vm_address_t vm_addr; uintptr_t addr; mach_vm_size_t allocation_size = round_page_quanta(size); mach_vm_offset_t allocation_mask = ((mach_vm_offset_t)1 << align) - 1; int alloc_flags = VM_FLAGS_ANYWHERE | VM_MAKE_TAG(vm_page_label); kern_return_t kr; if (debug_flags & DISABLE_ASLR) { return mvm_allocate_pages(size, align, 0, vm_page_label); } if (!allocation_size) { allocation_size = vm_page_quanta_size; } if (allocation_size < size) { // size_t arithmetic wrapped! return NULL; } retry: vm_addr = entropic_address; kr = mach_vm_map(mach_task_self(), &vm_addr, allocation_size, allocation_mask, alloc_flags, MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT); if (kr == KERN_NO_SPACE) { vm_addr = vm_page_quanta_size; kr = mach_vm_map(mach_task_self(), &vm_addr, allocation_size, allocation_mask, alloc_flags, MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT); } if (kr) { malloc_zone_error(debug_flags, false, "can't allocate region securely\n", "*** mach_vm_map(size=%lu) failed (error code=%d)\n", size, kr); return NULL; } addr = (uintptr_t)vm_addr; // Don't allow allocation to rise above entropic_limit (for tidiness). if (addr + allocation_size > entropic_limit) { // Exhausted current range? uintptr_t t = entropic_address; uintptr_t u = t - ENTROPIC_KABILLION; if (u < t) { // provided we don't wrap, deallocate and retry, in the expanded entropic range mach_vm_deallocate(mach_task_self(), vm_addr, allocation_size); OSAtomicCompareAndSwapLong(t, u, (volatile long *)&entropic_address); // Just one reduction please goto retry; } // fall through to use what we got } if (addr < entropic_address) { // we wrapped to find this allocation, expand the entropic range uintptr_t t = entropic_address; uintptr_t u = t - ENTROPIC_KABILLION; if (u < t) { OSAtomicCompareAndSwapLong(t, u, (volatile long *)&entropic_address); // Just one reduction please } // fall through to use what we got } return (void *)addr; } void mvm_deallocate_pages(void *addr, size_t size, unsigned debug_flags) { boolean_t add_guard_pages = debug_flags & MALLOC_ADD_GUARD_PAGES; mach_vm_address_t vm_addr = (mach_vm_address_t)addr; mach_vm_size_t allocation_size = size; kern_return_t kr; if (add_guard_pages) { vm_addr -= vm_page_quanta_size; allocation_size += 2 * vm_page_quanta_size; } kr = mach_vm_deallocate(mach_task_self(), vm_addr, allocation_size); if (kr) { malloc_zone_error(debug_flags, false, "Can't deallocate_pages region at %p\n", addr); } } void mvm_protect(void *address, size_t size, unsigned protection, unsigned debug_flags) { kern_return_t err; if (!(debug_flags & MALLOC_DONT_PROTECT_PRELUDE)) { err = mprotect((void *)((uintptr_t)address - vm_page_quanta_size), vm_page_quanta_size, protection); if (err) { malloc_report(ASL_LEVEL_ERR, "*** can't mvm_protect(%u) region for prelude guard page at %p\n", protection, (void *)((uintptr_t)address - vm_page_quanta_size)); } } if (!(debug_flags & MALLOC_DONT_PROTECT_POSTLUDE)) { err = mprotect((void *)(round_page_quanta(((uintptr_t)address + size))), vm_page_quanta_size, protection); if (err) { malloc_report(ASL_LEVEL_ERR, "*** can't mvm_protect(%u) region for postlude guard page at %p\n", protection, (void *)((uintptr_t)address + size)); } } } int mvm_madvise_free(void *rack, void *r, uintptr_t pgLo, uintptr_t pgHi, uintptr_t *last, boolean_t scribble) { if (pgHi > pgLo) { size_t len = pgHi - pgLo; if (scribble) { memset((void *)pgLo, SCRUBBLE_BYTE, len); // Scribble on MADV_FREEd memory } #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR if (last) { if (*last == pgLo) { return 0; } *last = pgLo; } #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR MAGMALLOC_MADVFREEREGION(rack, r, (void *)pgLo, (int)len); // DTrace USDT Probe if (-1 == madvise((void *)pgLo, len, CONFIG_MADVISE_STYLE)) { /* -1 return: VM map entry change makes this unfit for reuse. Something evil lurks. */ #if DEBUG_MADVISE malloc_zone_error(NULL, false, "madvise_free_range madvise(..., MADV_FREE_REUSABLE) failed for %p, length=%d\n", (void *)pgLo, len); #endif return 1; } else { MALLOC_TRACE(TRACE_madvise, (uintptr_t)r, (uintptr_t)pgLo, len, CONFIG_MADVISE_STYLE); } } return 0; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/src/vm.h ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __VM_H #define __VM_H static inline bool mvm_aslr_enabled(void) { return _dyld_get_image_slide((const struct mach_header *)_NSGetMachExecuteHeader()) != 0; } MALLOC_NOEXPORT void mvm_aslr_init(void); MALLOC_NOEXPORT void * mvm_allocate_pages(size_t size, unsigned char align, unsigned debug_flags, int vm_page_label); MALLOC_NOEXPORT void * mvm_allocate_pages_securely(size_t size, unsigned char align, int vm_page_label, uint32_t debug_flags); MALLOC_NOEXPORT void mvm_deallocate_pages(void *addr, size_t size, unsigned debug_flags); MALLOC_NOEXPORT int mvm_madvise_free(void *szone, void *r, uintptr_t pgLo, uintptr_t pgHi, uintptr_t *last, boolean_t scribble); MALLOC_NOEXPORT void mvm_protect(void *address, size_t size, unsigned protection, unsigned debug_flags); #endif // __VM_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/Makefile ================================================ PROJECT := libmalloc TEST_DIR := tests/ DEVELOPER_DIR ?= /Applications/Xcode.app/Contents/Developer/ include $(DEVELOPER_DIR)/AppleInternal/Makefiles/darwintest/Makefile.common TRACE_FILES := \ $(notdir $(wildcard $(SRCROOT)/../traces/*.mtrace)) # add trace files without the traces/ prefix, fex: # exclude_this_file.mtrace EXCLUDED_TRACE_FILES := OTHER_TEST_TARGETS = \ $(addprefix nano-trace-replay_, $(basename $(filter-out $(EXCLUDED_TRACE_FILES), $(TRACE_FILES)))) BATS_PLISTS = \ $(patsubst %,$(SYMROOT)/%.plist,$(OTHER_TEST_TARGETS)) CUSTOM_TARGETS = \ single-churn \ single-list_allocate \ single-tree_allocate \ single-tree_churn \ single-fragment \ single-fragment_iterate \ single-message_one \ single-message_many \ parallel-churn \ parallel-list_allocate \ parallel-tree_allocate \ parallel-tree_churn \ parallel-fragment \ parallel-fragment_iterate # single-medium \ # single-big \ # parallel-medium \ # parallel-big MALLOCBENCH_SOURCE := $(wildcard MallocBench/*.cpp) EXCLUDED_SOURCES := \ MallocBench.cpp \ nano_trace_replay.c CXX := $(shell $(XCRUN) -sdk "$(TARGETSDK)" -find clang++) WARNING_CFLAGS := -Wno-format-invalid-specifier -Wno-format-extra-args OTHER_CFLAGS += \ -DDARWINTEST \ -DOS_UNFAIR_LOCK_INLINE=1 \ -lCrashReporterClient \ -I$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders \ -I$(SRCROOT)/../include/malloc \ $(WARNING_CFLAGS) $(OBJROOT)/MallocBench/%.o: MallocBench/%.cpp @mkdir -p $(OBJROOT)/MallocBench $(CXX) $(CFLAGS) -I$(SRCROOT)/MallocBench -std=gnu++11 -stdlib=libc++ -c -o $@ $< single-%: $(addprefix $(OBJROOT)/, $(MALLOCBENCH_SOURCE:.cpp=.o)) $(CXX) -c -o $(OBJROOT)/MallocBench-$@.o \ -I$(SRCROOT)/MallocBench \ $(CFLAGS) \ -DBENCHMARK_NAME=\"$*\" \ -DPARALLEL=false \ $(SRCROOT)/MallocBench.cpp $(CXX) -o $(SYMROOT)/$@ \ $(CFLAGS) \ $^ \ $(OBJROOT)/MallocBench-$@.o install-single-%: $(CUSTOM_TARGETS) @mkdir -p $(INSTALLDIR) cp $(SYMROOT)/$(patsubst install-%,%,$@) $(INSTALLDIR)/$(patsubst install-%,%,$@) parallel-%: $(addprefix $(OBJROOT)/, $(MALLOCBENCH_SOURCE:.cpp=.o)) $(CXX) -c -o $(OBJROOT)/MallocBench-$@.o \ -I$(SRCROOT)/MallocBench \ $(CFLAGS) \ -DBENCHMARK_NAME=\"$*\" \ -DPARALLEL=true \ $(SRCROOT)/MallocBench.cpp $(CXX) -o $(SYMROOT)/$@ \ $(CFLAGS) \ $^ \ $(OBJROOT)/MallocBench-$@.o install-parallel-%: $(CUSTOM_TARGETS) @mkdir -p $(INSTALLDIR) cp $(SYMROOT)/$(patsubst install-%,%,$@) $(INSTALLDIR)/$(patsubst install-%,%,$@) nano-trace-replay_%: $(SRCROOT)/nano_trace_replay.c $(CC) \ $(CFLAGS) $(DT_CFLAGS) $(OTHER_CFLAGS) \ $(LDFLAGS) $(DT_LDFLAGS) $(OTHER_LDFLAGS) \ -DTRACE_NAME="$(patsubst nano-trace-replay_%,%,$(notdir $@))" \ -o $@ \ $(SRCROOT)/nano_trace_replay.c $(BATS_PLISTS): %.plist : % $(EXTRACTMETA) extract -i /$(INSTALLPATH)/$(notdir $<) -b $(SYMROOT)/$(notdir $<) -o $@ @plutil -convert binary1 $@ SANITIZER_DYLIB_PATH := /usr/local/lib/sanitizers/ asan: OTHER_CFLAGS += -fsanitize=address asan: OTHER_LDFLAGS += -Wl,-rpath -Wl,$(SANITIZER_DYLIB_PATH) ifeq ($(Embedded),NO) tsan: CFLAGS := $(filter-out $(ARCH_FLAGS),$(CFLAGS)) -arch x86_64 -fsanitize=thread tsan: OTHER_LDFLAGS += -Wl,-rpath -Wl,$(SANITIZER_DYLIB_PATH) else EXCLUDED_SOURCES += tsan.c endif madvise: OTHER_CFLAGS += -I../src stack_logging_test: OTHER_CFLAGS += -I../private radix_tree_test: OTHER_CFLAGS += -I../src -framework Foundation .DEFAULT_GOAL := all include $(DEVELOPER_DIR)/AppleInternal/Makefiles/darwintest/Makefile.targets ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/Benchmark.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "Benchmark.h" #include "CPUCount.h" #include "balloon.h" #include "big.h" #include "churn.h" #include "fragment.h" #include "list.h" #include "medium.h" #include "memalign.h" #include "message.h" #include "realloc.h" #include "stress.h" #include "stress_aligned.h" #include "tree.h" #include #include #include #include #include #include #include #include #include #include #include "mbmalloc.h" using namespace std; struct BenchmarkPair { const char* const name; const BenchmarkFunction function; }; static const BenchmarkPair benchmarkPairs[] = { { "balloon", benchmark_balloon }, { "big", benchmark_big }, { "churn", benchmark_churn }, { "fragment", benchmark_fragment }, { "fragment_iterate", benchmark_fragment_iterate }, { "list_allocate", benchmark_list_allocate }, { "list_traverse", benchmark_list_traverse }, { "medium", benchmark_medium }, { "memalign", benchmark_memalign }, { "message_many", benchmark_message_many }, { "message_one", benchmark_message_one }, { "realloc", benchmark_realloc }, { "stress", benchmark_stress }, { "stress_aligned", benchmark_stress_aligned }, { "tree_allocate", benchmark_tree_allocate }, { "tree_churn", benchmark_tree_churn }, { "tree_traverse", benchmark_tree_traverse }, }; static const size_t benchmarksPairsCount = sizeof(benchmarkPairs) / sizeof(BenchmarkPair); static inline bool operator==(const BenchmarkPair& benchmarkPair, const string& string) { return string == benchmarkPair.name; } static void*** allocateHeap(size_t heapSize, size_t chunkSize, size_t objectSize) { if (!heapSize) return 0; size_t chunkCount = heapSize / chunkSize; size_t objectCount = chunkSize / objectSize; void*** chunks = (void***)mbmalloc(chunkCount * sizeof(void**)); for (size_t i = 0; i < chunkCount; ++i) { chunks[i] = (void**)mbmalloc(objectCount * sizeof(void*)); for (size_t j = 0; j < objectCount; ++j) { chunks[i][j] = (void*)mbmalloc(objectSize); bzero(chunks[i][j], objectSize); } } return chunks; } static void deallocateHeap(void*** chunks, size_t heapSize, size_t chunkSize, size_t objectSize) { if (!heapSize) return; size_t chunkCount = heapSize / chunkSize; size_t objectCount = chunkSize / objectSize; for (size_t i = 0; i < chunkCount; ++i) { for (size_t j = 0; j < objectCount; ++j) mbfree(chunks[i][j], objectSize); mbfree(chunks[i], objectCount * sizeof(void*)); } mbfree(chunks, chunkCount * sizeof(void**)); } Benchmark::Benchmark(const string& benchmarkName, bool isParallel, size_t runs, size_t heapSize) : m_benchmarkPair() , m_elapsedTime() , m_isParallel(isParallel) , m_heapSize(heapSize) , m_runs(runs) { const BenchmarkPair* benchmarkPair = std::find( benchmarkPairs, benchmarkPairs + benchmarksPairsCount, benchmarkName); if (benchmarkPair == benchmarkPairs + benchmarksPairsCount) return; m_benchmarkPair = benchmarkPair; } void Benchmark::printBenchmarks() { cout << "Benchmarks: " << endl; for (size_t i = 0; i < benchmarksPairsCount; ++i) cout << "\t" << benchmarkPairs[i].name << endl; } void Benchmark::runOnce() { if (!m_isParallel) { m_benchmarkPair->function(m_isParallel); return; } dispatch_group_t group = dispatch_group_create(); for (size_t i = 0; i < cpuCount(); ++i) { dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ m_benchmarkPair->function(m_isParallel); }); } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); } void Benchmark::run() { static const size_t objectSize = 32; static const size_t chunkSize = 1024 * 1024; void*** heap = allocateHeap(m_heapSize, chunkSize, objectSize); runOnce(); // Warmup run. for (size_t i = 0; i < m_runs; ++i) { double start = currentTimeMS(); runOnce(); double end = currentTimeMS(); double elapsed = end - start; m_elapsedTime += elapsed; } m_elapsedTime /= m_runs; deallocateHeap(heap, m_heapSize, chunkSize, objectSize); mbscavenge(); m_memory = currentMemoryBytes(); } void Benchmark::printReport() { size_t kB = 1024; const char *env = getenv("BATS_TMP_DIR") ?: "/tmp"; std::string name(m_isParallel ? "parallel-" : "single-"); name += m_benchmarkPair->name; /* * Must prepend files with dtres_ to get them picked up by BATS until * */ std::string perfDataPath = std::string(env) + "/dtres_" + name + ".perfdata"; std::ofstream ofs(perfDataPath, std::ofstream::out); cout << "Writing perf data to: " << perfDataPath << endl; ofs << "{" << endl; ofs << "\t\"version\": \"1.0\"," << endl; ofs << "\t\"bats_test_name\": \"" << name << "\"," << endl; ofs << "\t\"measurements\": {" << endl; ofs << "\t\t\"" << name << "\": {" << endl; ofs << "\t\t\t\"names\": [\"time\", \"memory\", \"peakmem\", \"phys_footprint\"]," << endl; ofs << "\t\t\t\"units\": [\"us\", \"B\", \"B\", \"B\"]," << endl; ofs << "\t\t\t\"data\": [["; ofs << m_elapsedTime << "], ["; ofs << m_memory.resident << "], ["; ofs << m_memory.residentMax << "], ["; ofs << m_memory.physicalFootprint; ofs << "]]" << endl; ofs << "\t\t}" << endl; ofs << "\t}" << endl; ofs << "}" << endl; } double Benchmark::currentTimeMS() { struct timeval now; gettimeofday(&now, 0); return (now.tv_sec * 1000.0) + now.tv_usec / 1000.0; } Benchmark::Memory Benchmark::currentMemoryBytes() { Memory memory; task_vm_info_data_t vm_info; mach_msg_type_number_t vm_size = TASK_VM_INFO_COUNT; if (KERN_SUCCESS != task_info(mach_task_self(), TASK_VM_INFO_PURGEABLE, (task_info_t)(&vm_info), &vm_size)) { cout << "Failed to get mach task info" << endl; exit(1); } memory.resident = vm_info.internal - vm_info.purgeable_volatile_pmap; memory.residentMax = vm_info.resident_size_peak; memory.physicalFootprint = vm_info.phys_footprint; return memory; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/Benchmark.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef Benchmark_h #define Benchmark_h #include #include typedef void (*BenchmarkFunction)(bool isParallel); struct BenchmarkPair; class Benchmark { public: struct Memory { Memory() : resident() , residentMax() , physicalFootprint() { } Memory(size_t resident, size_t residentMax, size_t physicalFootprint) : resident(resident) , residentMax(residentMax) , physicalFootprint(physicalFootprint) { } Memory operator-(const Memory& other) { return Memory(resident - other.resident, residentMax - other.residentMax, physicalFootprint - other.physicalFootprint); } size_t resident; size_t residentMax; size_t physicalFootprint; }; static double currentTimeMS(); static Memory currentMemoryBytes(); Benchmark(const std::string&, bool isParallel, size_t runs, size_t heapSize); bool isValid() { return m_benchmarkPair; } void printBenchmarks(); void run(); void printReport(); private: typedef std::map MapType; void runOnce(); MapType m_map; const BenchmarkPair* m_benchmarkPair; bool m_isParallel; size_t m_runs; size_t m_heapSize; Memory m_memory; double m_elapsedTime; }; #endif // Benchmark_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/CPUCount.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CPUCount.h" #include #include #include #include static size_t count; size_t cpuCount() { if (count) return count; size_t length = sizeof(count); int name[] = { CTL_HW, HW_NCPU }; int sysctlResult = sysctl(name, sizeof(name) / sizeof(int), &count, &length, 0, 0); if (sysctlResult < 0) abort(); return count; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/CPUCount.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef CPUCount_h #define CPUCount_h #include size_t cpuCount(); #endif // CPUCount_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/CommandLine.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CommandLine.h" #include #include struct option CommandLine::longOptions[] = { {"benchmark", required_argument, 0, 'b'}, {"parallel", no_argument, 0, 'p'}, {"heap", required_argument, 0, 'h'}, {"runs", required_argument, 0, 'r'}, {0, 0, 0, 0} }; CommandLine::CommandLine(std::string name, bool parallel) : m_benchmarkName(name) , m_isParallel(parallel) , m_heapSize(0) , m_runs(4) { } void CommandLine::printUsage() { std::string fullPath(m_argv[0]); size_t pos = fullPath.find_last_of("/") + 1; std::string program = fullPath.substr(pos); std::cout << "Usage: " << program << " --benchmark benchmark_name [ --parallel ] [ --heap MB ]" << std::endl; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/CommandLine.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include class CommandLine { public: CommandLine(std::string name, bool parallel); bool isValid() { return m_benchmarkName.size(); } const std::string& benchmarkName() { return m_benchmarkName; } bool isParallel() { return m_isParallel; } size_t heapSize() { return m_heapSize; } size_t runs() { return m_runs; } void printUsage(); private: static struct option longOptions[]; int m_argc; char** m_argv; std::string m_benchmarkName; bool m_isParallel; size_t m_heapSize; size_t m_runs; }; ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/Interpreter.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CPUCount.h" #include "Interpreter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "mbmalloc.h" Interpreter::Interpreter(const char* fileName, bool shouldFreeAllObjects) : m_shouldFreeAllObjects(shouldFreeAllObjects) { m_fd = open(fileName, O_RDWR, S_IRUSR | S_IWUSR); if (m_fd == -1) fprintf(stderr, "failed to open\n"); struct stat buf; fstat(m_fd, &buf); m_opCount = buf.st_size / sizeof(Op); assert(m_opCount * sizeof(Op) == buf.st_size); size_t maxSlot = 0; std::vector ops(1024); size_t remaining = m_opCount * sizeof(Op); while (remaining) { size_t bytes = std::min(remaining, ops.size() * sizeof(Op)); remaining -= bytes; read(m_fd, ops.data(), bytes); size_t opCount = bytes / sizeof(Op); for (size_t i = 0; i < opCount; ++i) { Op op = ops[i]; if (op.slot > maxSlot) maxSlot = op.slot; } } m_objects.resize(maxSlot + 1); } Interpreter::~Interpreter() { int result = close(m_fd); if (result == -1) fprintf(stderr, "failed to close\n"); } void Interpreter::run() { std::vector ops(1024); lseek(m_fd, 0, SEEK_SET); size_t remaining = m_opCount * sizeof(Op); while (remaining) { size_t bytes = std::min(remaining, ops.size() * sizeof(Op)); remaining -= bytes; read(m_fd, ops.data(), bytes); size_t opCount = bytes / sizeof(Op); for (size_t i = 0; i < opCount; ++i) { Op op = ops[i]; switch (op.opcode) { case op_malloc: { m_objects[op.slot] = { mbmalloc(op.size), op.size }; assert(m_objects[op.slot].object); bzero(m_objects[op.slot].object, op.size); break; } case op_free: { if (!m_objects[op.slot].object) continue; mbfree(m_objects[op.slot].object, m_objects[op.slot].size); m_objects[op.slot] = { 0, 0 }; break; } case op_realloc: { if (!m_objects[op.slot].object) continue; m_objects[op.slot] = { mbrealloc(m_objects[op.slot].object, m_objects[op.slot].size, op.size), op.size }; break; } default: { fprintf(stderr, "bad opcode: %d\n", op.opcode); abort(); break; } } } } // A recording might not free all of its allocations. if (!m_shouldFreeAllObjects) return; for (size_t i = 0; i < m_objects.size(); ++i) { if (!m_objects[i].object) continue; mbfree(m_objects[i].object, m_objects[i].size); m_objects[i] = { 0, 0 }; } } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/Interpreter.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef Interpreter_h #define Interpreter_h #include class Interpreter { public: Interpreter(const char* fileName, bool shouldFreeAllObjects = true); ~Interpreter(); void run(); private: enum Opcode { op_malloc, op_free, op_realloc }; struct Op { Opcode opcode; size_t slot; size_t size; }; struct Record { void* object; size_t size; }; bool m_shouldFreeAllObjects; int m_fd; size_t m_opCount; std::vector m_objects; }; #endif // Interpreter_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/balloon.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "Benchmark.h" #include "CPUCount.h" #include "balloon.h" #include #include #include #include #include "mbmalloc.h" void benchmark_balloon(bool isParallel) { const size_t chunkSize = 1 * 1024; const size_t balloonSize = 100 * 1024 * 1024; const size_t steadySize = 10 * 1024 * 1024; std::array balloon; std::array steady; auto start = std::chrono::steady_clock::now(); for (size_t i = 0; i < balloon.size(); ++i) { balloon[i] = mbmalloc(chunkSize); bzero(balloon[i], chunkSize); } for (size_t i = 0; i < balloon.size(); ++i) mbfree(balloon[i], chunkSize); auto stop = std::chrono::steady_clock::now(); auto benchmarkTime = stop - start; start = std::chrono::steady_clock::now(); // Converts bytes to time -- for reporting's sake -- by waiting a while until // the heap shrinks back down. This isn't great for pooling with other // benchmarks in a geometric mean of throughput, but it's OK for basic testing. while (Benchmark::currentMemoryBytes().resident > 2 * steadySize && std::chrono::steady_clock::now() - start < 8 * benchmarkTime) { for (size_t i = 0; i < steady.size(); ++i) { steady[i] = mbmalloc(chunkSize); bzero(steady[i], chunkSize); } for (size_t i = 0; i < steady.size(); ++i) mbfree(steady[i], chunkSize); } } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/balloon.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef balloon_h #define balloon_h void benchmark_balloon(bool isParallel); #endif // balloon_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/big.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CPUCount.h" #include "big.h" #include #include #include #include #include "mbmalloc.h" using namespace std; struct Object { double* p; size_t size; }; void benchmark_big(bool isParallel) { size_t times = 1; size_t vmSize = 1ul * 1024 * 1024 * 1024; size_t objectSizeMin = 4 * 1024; size_t objectSizeMax = 64 * 1024; if (isParallel) vmSize /= cpuCount(); size_t objectCount = vmSize / objectSizeMin; srandom(0); // For consistency between runs. for (size_t i = 0; i < times; ++i) { Object* objects = (Object*)mbmalloc(objectCount * sizeof(Object)); bzero(objects, objectCount * sizeof(Object)); for (size_t i = 0, remaining = vmSize; remaining > objectSizeMin; ++i) { size_t size = min(remaining, max(objectSizeMin, random() % objectSizeMax)); objects[i] = { (double*)mbmalloc(size), size }; bzero(objects[i].p, size); remaining -= size; } for (size_t i = 0; i < objectCount && objects[i].p; ++i) mbfree(objects[i].p, objects[i].size); mbfree(objects, objectCount * sizeof(Object)); } } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/big.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef big_h #define big_h void benchmark_big(bool isParallel); #endif // big_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/churn.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CPUCount.h" #include "churn.h" #include #include #include "mbmalloc.h" struct HeapDouble { void* operator new(size_t size) { return mbmalloc(size); } void operator delete(void* p, size_t size) { mbfree(p, size); } HeapDouble(double d) : value(d) { } const HeapDouble& operator+=(const HeapDouble& other) { value += other.value; return *this; } double value; }; void benchmark_churn(bool isParallel) { size_t times = 7000000; if (isParallel) times /= cpuCount(); auto total = std::unique_ptr(new HeapDouble(0.0)); for (size_t i = 0; i < times; ++i) { auto heapDouble = std::unique_ptr(new HeapDouble(i)); *total += *heapDouble; } } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/churn.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef churn_h #define churn_h void benchmark_churn(bool isParallel); #endif // churn_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/fragment.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CPUCount.h" #include "fragment.h" #include #include #include "mbmalloc.h" namespace { class Node { public: void* operator new(size_t size) { return mbmalloc(size); } void operator delete(void* p, size_t size) { mbfree(p, size); } Node() : m_next(0) , m_payload() { } Node(Node* next) : m_next(next) , m_payload() { } Node* next() { return m_next; } void validate() { for (size_t i = 0; i < sizeof(m_payload); ++i) { if (m_payload[i]) abort(); } } private: Node* m_next; char m_payload[32 - sizeof(Node*)]; }; } // namespace void validate(Node* head) { for (Node* node = head; node; node = node->next()) node->validate(); } void benchmark_fragment(bool isParallel) { size_t nodeCount = 128 * 1024; if (isParallel) nodeCount /= cpuCount(); size_t replaceCount = nodeCount / 4; size_t times = 25; srandom(0); // For consistency between runs. for (size_t i = 0; i < times; ++i) { Node** nodes = static_cast(mbmalloc(nodeCount * sizeof(Node*))); for (size_t i = 0; i < nodeCount; ++i) nodes[i] = new Node; for (size_t i = 0; i < replaceCount; ++i) { size_t node = random() % nodeCount; delete nodes[node]; nodes[node] = new Node; } for (size_t node = 0; node < nodeCount; ++node) delete nodes[node]; mbfree(nodes, nodeCount * sizeof(Node*)); } } void benchmark_fragment_iterate(bool isParallel) { size_t nodeCount = 512 * 1024; size_t times = 20; if (isParallel) nodeCount /= cpuCount(); size_t replaceCount = nodeCount / 4; srandom(0); // For consistency between runs. Node** nodes = static_cast(mbmalloc(nodeCount * sizeof(Node*))); for (size_t i = 0; i < nodeCount; ++i) nodes[i] = new Node; Node* head = 0; for (size_t i = 0; i < replaceCount; ++i) { size_t node = random() % nodeCount; delete nodes[node]; nodes[node] = 0; head = new Node(head); } for (size_t i = 0; i < times; ++i) validate(head); for (Node* next ; head; head = next) { next = head->next(); delete head; } for (size_t node = 0; node < nodeCount; ++node) { if (!nodes[node]) continue; delete nodes[node]; } mbfree(nodes, nodeCount * sizeof(Node*)); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/fragment.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef fragment_h #define fragment_h void benchmark_fragment(bool isParallel); void benchmark_fragment_iterate(bool isParallel); #endif // fragment_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/list.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CPUCount.h" #include "list.h" #include #include #include "mbmalloc.h" namespace { struct Node { void* operator new(size_t size) { return mbmalloc(size); } void operator delete(void* p, size_t size) { mbfree(p, size); } Node(Node* next, size_t payloadSize) : m_refCount(1) , m_next(next) , m_payload(static_cast(mbmalloc(payloadSize))) , m_payloadSize(payloadSize) { if (m_next) m_next->ref(); bzero(m_payload, payloadSize); } ~Node() { if (m_next) m_next->deref(); mbfree(m_payload, m_payloadSize); } void ref() { ++m_refCount; } void deref() { if (m_refCount == 1) delete this; else --m_refCount; } Node* takeNext() { Node* tmp = m_next; m_next = 0; return tmp; } bool validate() { if (m_payload[0]) return false; return true; } unsigned m_refCount; Node* m_next; char* m_payload; size_t m_payloadSize; }; } // namespace void benchmark_list_allocate(bool isParallel) { Node* head = 0; size_t times = 70; size_t nodes = 32 * 1024; if (isParallel) { nodes /= cpuCount(); times *= 2; } for (size_t time = 0; time < times; ++time) { // Construct a list of nodes. for (size_t node = 0; node < nodes; ++node) { Node* oldHead = head; head = new Node(oldHead, (nodes & (64 - 1)) | 1); if (oldHead) oldHead->deref(); } // Tear down the list. while (head) { Node* tmp = head->takeNext(); head->deref(); head = tmp; } } } void benchmark_list_traverse(bool isParallel) { Node* head = 0; size_t times = 1 * 1024; size_t nodes = 32 * 1024; if (isParallel) { nodes /= cpuCount(); times *= 4; } // Construct a list of nodes. for (size_t node = 0; node < nodes; ++node) { Node* oldHead = head; head = new Node(oldHead, (nodes & (64 - 1)) | 1); if (oldHead) oldHead->deref(); } // Validate the list. for (size_t time = 0; time < times; ++time) { for (Node* node = head; node; node = node->m_next) { if (!node->validate()) abort(); } } // Tear down the list. while (head) { Node* tmp = head->takeNext(); head->deref(); head = tmp; } } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/list.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef list_h #define list_h void benchmark_list_allocate(bool isParallel); void benchmark_list_traverse(bool isParallel); #endif // list_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/mbmalloc.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #import extern "C" { void* mbmalloc(size_t size) { return malloc(size); } void* mbmemalign(size_t alignment, size_t size) { void* result; posix_memalign(&result, alignment, size); return result; } void mbfree(void* p, size_t) { return free(p); } void* mbrealloc(void* p, size_t, size_t newSize) { return realloc(p, newSize); } void mbscavenge() { malloc_zone_pressure_relief(nullptr, 0); } } // extern "C" ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/mbmalloc.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef mbmalloc_h #define mbmalloc_h #include // This file defines a default implementation of the mbmalloc API, using system // malloc. To test with another malloc, supply an override .dylib that exports // these symbols. extern "C" { void* mbmalloc(size_t); void* mbmemalign(size_t, size_t); void mbfree(void*, size_t); void* mbrealloc(void*, size_t, size_t); void mbscavenge(); } // Catch accidental benchmark allocation through malloc and free. All benchmark // code should use mbmalloc / mbfree, to call the instrumented malloc we're // benchmarking against. #define malloc error #define free error #define realloc error #endif // mbmalloc_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/medium.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CPUCount.h" #include "medium.h" #include #include #include #include #include "mbmalloc.h" using namespace std; struct Object { double* p; size_t size; }; void benchmark_medium(bool isParallel) { size_t times = 1; size_t vmSize = 1ul * 1024 * 1024 * 1024; size_t objectSizeMin = 2 * 1024; size_t objectSizeMax = 8 * 1024; if (isParallel) vmSize /= cpuCount(); size_t objectCount = vmSize / objectSizeMin; srandom(0); // For consistency between runs. for (size_t i = 0; i < times; ++i) { Object* objects = (Object*)mbmalloc(objectCount * sizeof(Object)); bzero(objects, objectCount * sizeof(Object)); for (size_t i = 0, remaining = vmSize; remaining > objectSizeMin; ++i) { size_t size = min(remaining, max(objectSizeMin, random() % objectSizeMax)); objects[i] = { (double*)mbmalloc(size), size }; bzero(objects[i].p, size); remaining -= size; } for (size_t i = 0; i < objectCount && objects[i].p; ++i) mbfree(objects[i].p, objects[i].size); mbfree(objects, objectCount * sizeof(Object)); } } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/medium.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef medium_h #define medium_h void benchmark_medium(bool isParallel); #endif // medium_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/memalign.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "memalign.h" #include #include #include "mbmalloc.h" void test(size_t alignment, size_t size) { void* result = mbmemalign(alignment, size); assert(result); assert(!((uintptr_t)result & (alignment - 1))); mbfree(result, size); } void benchmark_memalign(bool isParallel) { for (size_t alignment = 2; alignment < 4096; alignment *= 2) { for (size_t size = 0; size < 4096; ++size) test(alignment, size); } test(1 * 1024 * 1024, 8); test(8 * 1024 * 1024, 8); test(32 * 1024 * 1024, 8); test(64 * 1024 * 1024, 8); test(1 * 1024 * 1024, 8 * 1024 * 1024); test(1 * 1024 * 1024, 16 * 1024 * 1024); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/memalign.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef memalign_h #define memalign_h void benchmark_memalign(bool isParallel); #endif // memalign_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/message.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CPUCount.h" #include "message.h" #include #include #include #include "mbmalloc.h" namespace { size_t hash(size_t hash, unsigned short a, unsigned short b) { hash += a ^ b; return hash; } class Node { static const size_t payloadCount = 128; public: void* operator new(size_t size) { return mbmalloc(size); } void operator delete(void* p, size_t size) { mbfree(p, size); } Node() : m_payload() { } size_t hash(size_t hash) { for (size_t i = 0; i < payloadCount; i += 2) hash = ::hash(hash, m_payload[i], m_payload[i + 1]); return hash; } private: unsigned short m_payload[payloadCount]; }; class Message { static const size_t nodeCount = 1 * 1024; public: void* operator new(size_t size) { return mbmalloc(size); } void operator delete(void* p, size_t size) { mbfree(p, size); } Message() : m_buffer(static_cast(mbmalloc(nodeCount * sizeof(Node**)))) { for (size_t i = 0; i < nodeCount; ++i) m_buffer[i] = new Node; } ~Message() { for (size_t i = 0; i < nodeCount; ++i) delete m_buffer[i]; mbfree(m_buffer, nodeCount * sizeof(Node**)); } size_t hash() { size_t hash = 0; for (size_t i = 0; i < nodeCount; ++i) hash = m_buffer[i]->hash(hash); return hash; } private: Node** m_buffer; }; } // namespace void benchmark_message_one(bool isParallel) { if (isParallel) abort(); const size_t times = 2048; const size_t quantum = 16; dispatch_queue_t queue = dispatch_queue_create("message", 0); for (size_t i = 0; i < times; i += quantum) { for (size_t j = 0; j < quantum; ++j) { Message* message = new Message; dispatch_async(queue, ^{ size_t hash = message->hash(); if (hash) abort(); delete message; }); } dispatch_sync(queue, ^{ }); } dispatch_sync(queue, ^{ }); dispatch_release(queue); } void benchmark_message_many(bool isParallel) { if (isParallel) abort(); const size_t times = 768; const size_t quantum = 16; const size_t queueCount = cpuCount() - 1; dispatch_queue_t queues[queueCount]; for (size_t i = 0; i < queueCount; ++i) queues[i] = dispatch_queue_create("message", 0); for (size_t i = 0; i < times; i += quantum) { for (size_t j = 0; j < quantum; ++j) { for (size_t k = 0; k < queueCount; ++k) { Message* message = new Message; dispatch_async(queues[k], ^{ size_t hash = message->hash(); if (hash) abort(); delete message; }); } } for (size_t i = 0; i < queueCount; ++i) dispatch_sync(queues[i], ^{ }); } for (size_t i = 0; i < queueCount; ++i) dispatch_sync(queues[i], ^{ }); for (size_t i = 0; i < queueCount; ++i) dispatch_release(queues[i]); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/message.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef message_h #define message_h void benchmark_message_one(bool isParallel); void benchmark_message_many(bool isParallel); #endif // message_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/realloc.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "realloc.h" #include #include #include "mbmalloc.h" void benchmark_realloc(bool isParallel) { } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/realloc.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef realloc_h #define realloc_h void benchmark_realloc(bool isParallel); #endif // realloc_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/stress.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "Benchmark.h" #include "CPUCount.h" #include "stress.h" #include #include #include #include #include #include #include "mbmalloc.h" static const size_t kB = 1024; static const size_t MB = kB * kB; struct Object { Object(void* pointer, size_t size, long uuid) : pointer(pointer) , size(size) , uuid(uuid) { } void* pointer; size_t size; long uuid; }; class SizeStream { public: SizeStream() : m_state(Small) , m_count(0) { } size_t next() { switch (m_state) { case Small: { if (++m_count == smallCount) { m_state = Medium; m_count = 0; } return random() % smallMax; } case Medium: { if (++m_count == mediumCount) { m_state = Large; m_count = 0; } return random() % mediumMax; } case Large: { if (++m_count == largeCount) { m_state = Small; m_count = 0; } return random() % largeMax; } } } private: static const size_t smallCount = 1000; static const size_t smallMax = 16 * kB; static const size_t mediumCount = 100; static const size_t mediumMax = 512 * kB; static const size_t largeCount = 10; static const size_t largeMax = 4 * MB; enum { Small, Medium, Large } m_state; size_t m_count; }; Object allocate(size_t size) { Object object(mbmalloc(size), size, random()); for (size_t i = 0; i < size / sizeof(long); ++i) (static_cast(object.pointer))[i] = object.uuid; return object; } void deallocate(const Object& object) { for (size_t i = 0; i < object.size / sizeof(long); ++i) { if ((static_cast(object.pointer))[i] != object.uuid) abort(); } mbfree(object.pointer, object.size); } void benchmark_stress(bool isParallel) { const size_t heapSize = 100 * MB; const size_t churnSize = .05 * heapSize; const size_t churnCount = 10000; srandom(1); // For consistency between runs. std::vector objects; SizeStream sizeStream; size_t size = 0; for (size_t remaining = heapSize; remaining; remaining -= std::min(remaining, size)) { size = sizeStream.next(); objects.push_back(allocate(size)); } for (size_t i = 0; i < churnCount; ++i) { std::vector objectsToFree; for (size_t remaining = churnSize; remaining; remaining -= std::min(remaining, size)) { size = sizeStream.next(); Object object = allocate(size); size_t index = random() % objects.size(); objectsToFree.push_back(objects[index]); objects[index] = object; } for (auto& object : objectsToFree) deallocate(object); mbscavenge(); } for (auto& object : objects) mbfree(object.pointer, object.size); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/stress.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef stress_h #define stress_h void benchmark_stress(bool isParallel); #endif // stress_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/stress_aligned.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "Benchmark.h" #include "CPUCount.h" #include "stress_aligned.h" #include #include #include #include #include #include #include #include "mbmalloc.h" namespace { static const size_t kB = 1024; static const size_t MB = kB * kB; struct Object { Object(void* pointer, size_t size, long uuid) : pointer(pointer) , size(size) , uuid(uuid) { } void* pointer; size_t size; long uuid; }; class SizeStream { public: SizeStream() : m_state(Small) , m_count(0) { } size_t next() { switch (m_state) { case Small: { if (++m_count == smallCount) { m_state = Medium; m_count = 0; } return random() % smallMax; } case Medium: { if (++m_count == mediumCount) { m_state = Large; m_count = 0; } return random() % mediumMax; } case Large: { if (++m_count == largeCount) { m_state = Small; m_count = 0; } return random() % largeMax; } } } private: static const size_t smallCount = 1000; static const size_t smallMax = 16 * kB; static const size_t mediumCount = 100; static const size_t mediumMax = 512 * kB; static const size_t largeCount = 10; static const size_t largeMax = 4 * MB; enum { Small, Medium, Large } m_state; size_t m_count; }; Object allocate(size_t alignment, size_t size) { Object object(mbmemalign(alignment, size), size, random()); if ((uintptr_t)object.pointer & (alignment - 1)) abort(); for (size_t i = 0; i < size / sizeof(long); ++i) (static_cast(object.pointer))[i] = object.uuid; return object; } void deallocate(const Object& object) { for (size_t i = 0; i < object.size / sizeof(long); ++i) { if ((static_cast(object.pointer))[i] != object.uuid) abort(); } mbfree(object.pointer, object.size); } size_t randomAlignment() { switch (random() % 32) { case 0: return pow(2, random() % 26); default: return pow(2, random() % 14); } } } void benchmark_stress_aligned(bool isParallel) { const size_t heapSize = 100 * MB; const size_t churnSize = .05 * heapSize; const size_t churnCount = 100; srandom(1); // For consistency between runs. std::vector objects; SizeStream sizeStream; size_t size = 0; for (size_t remaining = heapSize; remaining; remaining -= std::min(remaining, size)) { size = sizeStream.next(); objects.push_back(allocate(randomAlignment(), size)); } for (size_t i = 0; i < churnCount; ++i) { std::vector objectsToFree; for (size_t remaining = churnSize; remaining; remaining -= std::min(remaining, size)) { size = sizeStream.next(); Object object = allocate(randomAlignment(), size); size_t index = random() % objects.size(); objectsToFree.push_back(objects[index]); objects[index] = object; } for (auto& object : objectsToFree) deallocate(object); mbscavenge(); } for (auto& object : objects) mbfree(object.pointer, object.size); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/stress_aligned.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef stress_aligned_h #define stress_aligned_h void benchmark_stress_aligned(bool isParallel); #endif // stress_aligned_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/tree.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "tree.h" #include #include #include #include "mbmalloc.h" namespace { struct Node { void* operator new(size_t size) { return mbmalloc(size); } void operator delete(void* p, size_t size) { mbfree(p, size); } Node(Node* left, Node* right, size_t payloadSize, size_t id) : m_refCount(1) , m_left(left) , m_right(right) , m_payload(static_cast(mbmalloc(payloadSize))) , m_payloadSize(payloadSize) , m_id(id) { if (m_left) m_left->ref(); if (m_right) m_right->ref(); bzero(m_payload, payloadSize); } ~Node() { if (m_left) m_left->deref(); if (m_right) m_right->deref(); mbfree(m_payload, m_payloadSize); } void ref() { ++m_refCount; } void deref() { if (m_refCount == 1) delete this; else --m_refCount; } size_t id() { return m_id; } Node* left() { return m_left; } Node* right() { return m_right; } void setLeft(Node* left) { left->ref(); if (m_left) m_left->deref(); m_left = left; } void setRight(Node* right) { right->ref(); if (m_right) m_right->deref(); m_right = right; } unsigned m_refCount; Node* m_left; Node* m_right; char* m_payload; size_t m_payloadSize; size_t m_id; }; void verify(Node* node, Node* left, Node* right) { if (left && left->id() >= node->id()) abort(); if (right && right->id() <= node->id()) abort(); } Node* createTree(size_t depth, size_t& counter) { if (!depth) return 0; Node* left = createTree(depth - 1, counter); size_t id = counter++; Node* right = createTree(depth - 1, counter); Node* result = new Node(left, right, ((depth * 8) & (64 - 1)) | 1, id); verify(result, left, right); if (left) left->deref(); if (right) right->deref(); return result; } Node* createTree(size_t depth) { size_t counter = 0; return createTree(depth, counter); } void churnTree(Node* node, size_t stride, size_t& counter) { if (!node) return; churnTree(node->left(), stride, counter); if (node->left() && !(counter % stride)) { Node* left = new Node(node->left()->left(), node->left()->right(), (counter & (64 - 1)) | 1, node->left()->id()); Node* right = new Node(node->right()->left(), node->right()->right(), (counter & (64 - 1)) | 1, node->right()->id()); node->setLeft(left); node->setRight(right); left->deref(); right->deref(); } ++counter; churnTree(node->right(), stride, counter); verify(node, node->left(), node->right()); } void churnTree(Node* tree, size_t stride) { size_t counter; churnTree(tree, stride, counter); } } // namespace void benchmark_tree_allocate(bool isParallel) { size_t times = 24; size_t depth = 16; if (isParallel) { times *= 4; depth = 13; } for (size_t time = 0; time < times; ++time) { Node* tree = createTree(depth); tree->deref(); } } void benchmark_tree_traverse(bool isParallel) { size_t times = 256; size_t depth = 15; if (isParallel) { times = 512; depth = 13; } Node* tree = createTree(depth); for (size_t time = 0; time < times; ++time) churnTree(tree, std::numeric_limits::max()); // Reuse this to iterate and validate. tree->deref(); } void benchmark_tree_churn(bool isParallel) { size_t times = 130; size_t depth = 15; if (isParallel) { times *= 4; depth = 12; } Node* tree = createTree(depth); for (size_t time = 0; time < times; ++time) churnTree(tree, 8); tree->deref(); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench/tree.h ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef tree_h #define tree_h void benchmark_tree_allocate(bool isParallel); void benchmark_tree_traverse(bool isParallel); void benchmark_tree_churn(bool isParallel); #endif // tree_h ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/MallocBench.cpp ================================================ /* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "Benchmark.h" #include "CommandLine.h" #include #include #include #include using namespace std; int main(int argc, char** argv) { CommandLine commandLine(BENCHMARK_NAME, PARALLEL); if (!commandLine.isValid()) { commandLine.printUsage(); exit(1); } Benchmark benchmark(commandLine.benchmarkName(), commandLine.isParallel(), commandLine.runs(), commandLine.heapSize()); if (!benchmark.isValid()) { cout << "Invalid benchmark: " << commandLine.benchmarkName() << endl << endl; benchmark.printBenchmarks(); exit(1); } benchmark.run(); benchmark.printReport(); cout << "TEST PASS: " << commandLine.benchmarkName() << endl; return 0; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/asan.c ================================================ #include #include #include #include #include #include "sanitizer/asan_interface.h" T_DECL(asan_sanity, "ASan Sanity Check", T_META_CHECK_LEAKS(NO)) { const char *dylib_path = TARGET_OS_OSX ? "@rpath/libclang_rt.asan_osx_dynamic.dylib" : TARGET_OS_IOS ? "@rpath/libclang_rt.asan_ios_dynamic.dylib" : TARGET_OS_TV ? "@rpath/libclang_rt.asan_tvos_dynamic.dylib" : TARGET_OS_WATCH ? "@rpath/libclang_rt.asan_watchos_dynamic.dylib" : TARGET_OS_BRIDGE ? "@rpath/libclang_rt.asan_bridgeos_dynamic.dylib" : NULL; void *asan_dylib = dlopen(dylib_path, RTLD_NOLOAD); T_ASSERT_NOTNULL(asan_dylib, "ASan dylib loaded"); void *ptr = malloc(16); free(ptr); T_PASS("I didn't crash!"); } jmp_buf longjmp_env = {0}; bool asan_report_hit = false; char *asan_report = NULL; void asan_report_handler(const char *report) { asan_report_hit = true; asan_report = strdup(report); longjmp(longjmp_env, 1); } __attribute__((optnone, noinline)) void write_byte(void *ptr, size_t offset) { ((char *)ptr)[offset] = 'x'; } T_DECL(asan_use_after_free, "ASan Detects use-after-free", T_META_CHECK_LEAKS(NO)) { asan_report = NULL; asan_report_hit = false; __asan_set_error_report_callback(asan_report_handler); if (setjmp(longjmp_env) == 0) { char *ptr = malloc(16); free(ptr); write_byte(ptr, 10); T_FAIL("use-after-free not detected"); } T_EXPECT_EQ(asan_report_hit, true, "asan finds use-after-free"); T_EXPECT_NOTNULL(strstr(asan_report, "AddressSanitizer: heap-use-after-free"), "asan header"); } T_DECL(asan_heap_buffer_overflow, "ASan Detects heap-buffer-overflow", T_META_CHECK_LEAKS(NO)) { asan_report = NULL; asan_report_hit = false; __asan_set_error_report_callback(asan_report_handler); if (setjmp(longjmp_env) == 0) { char *ptr = malloc(16); write_byte(ptr, 17); free(ptr); T_FAIL("heap-buffer-overflow not detected"); } T_EXPECT_EQ(asan_report_hit, true, "asan finds heap-buffer-overflow"); T_EXPECT_NOTNULL(strstr(asan_report, "AddressSanitizer: heap-buffer-overflow"), "asan header"); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/basic_malloc_free_perf.c ================================================ // // basic_malloc_free_perf.c // libmalloc // // Simple and repeatable performance tests for malloc/free, running tests // on the regular, Nanov1 and Nanov2 allocators. // #include #include #include <../src/internal.h> #include // This value is a guess that will be refined over time. #define PERFCHECK_THRESHOLD_PCT 10.0 static uint32_t ncpu(void) { static uint32_t activecpu, physicalcpu; if (!activecpu) { uint32_t n; size_t s = sizeof(n); sysctlbyname("hw.activecpu", &n, &s, NULL, 0); activecpu = n; s = sizeof(n); sysctlbyname("hw.physicalcpu", &n, &s, NULL, 0); physicalcpu = n; } return MIN(activecpu, physicalcpu); } // Code to run a single test and save the converged sample time as the DPS // metric. The code also measures the time taken in dispatch_apply(), but that // should be more or less constant in all cases. We are only interested in // whether the overall sampled time regresses, not in the absolute time value. static void run_test(void (^test)(size_t), bool singlethreaded) { uint32_t nthreads = 0; if (singlethreaded) { nthreads = 1; } else { const char *e; if ((e = getenv("DT_STAT_NTHREADS"))) { nthreads = strtoul(e, NULL, 0); } if (nthreads < 2) { nthreads = ncpu(); } } dt_stat_time_t s = dt_stat_time_create( nthreads > 1 ? "basic_malloc_free_perf multithreaded" : "basic_malloc_free_perf singlethreaded"); dt_stat_set_variable((dt_stat_t)s, "threads", nthreads); do { int batch_size = dt_stat_batch_size(s); dt_stat_token t = dt_stat_begin(s); // rdar://problem/40417821: disable thresholds for now. //dt_stat_set_variable((dt_stat_t)s, kPCFailureThresholdPctVar, // PERFCHECK_THRESHOLD_PCT); dispatch_apply(nthreads, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), test); dt_stat_end_batch(s, batch_size, t); } while (!dt_stat_stable(s)); dt_stat_finalize(s); } // Test content. Each of the functions is called from six different test cases, // with singlethreaded true and false and MallocNanoZone set to 0, V1 and V2. // The test content is biased towards the implementation of Nanov2. static void basic_perf_malloc_free_8_bytes(bool singlethreaded) { #define NUM_ALLOCS 512 run_test(^(size_t iteration __unused) { void *ptrs[NUM_ALLOCS]; for (int i = 0; i < NUM_ALLOCS; i++) { ptrs[i] = malloc(8); } for (int i = 0; i < NUM_ALLOCS; i++) { free(ptrs[i]); } }, singlethreaded); #undef NUM_ALLOCS } static void basic_perf_malloc_free_8_bytes_multi_block(bool singlethreaded) { // Use a large number of allocations to amortize the cost of scanning // for a new block. #define NUM_ALLOCS 65535 run_test(^(size_t iteration __unused) { void **ptrs = calloc(NUM_ALLOCS, sizeof(void *)); for (int i = 0; i < NUM_ALLOCS; i++) { ptrs[i] = malloc(8); } for (int i = 0; i < NUM_ALLOCS; i++) { free(ptrs[i]); } free(ptrs); }, singlethreaded); #undef NUM_ALLOCS } static void basic_perf_malloc_free_different_size_classes(bool singlethreaded) { #define NUM_ALLOCS 512 run_test(^(size_t iteration __unused) { void *ptrs[NUM_ALLOCS]; size_t sz = (iteration + 1) * 16; if (sz > 256) { // Too big for Nano. return; } for (int i = 0; i < NUM_ALLOCS; i++) { ptrs[i] = malloc(sz); } for (int i = 0; i < NUM_ALLOCS; i++) { free(ptrs[i]); } }, singlethreaded); #undef NUM_ALLOCS } static void basic_perf_malloc_free_by_size_class(bool singlethreaded) { #define NUM_LOOPS 16 run_test(^(size_t iteration __unused) { void *ptrs[NUM_LOOPS * 16]; int k = 0; for (int i = 0; i < NUM_LOOPS; i++) { for (int j = 0; j < 16; j++) { ptrs[k++] = malloc(16 * j + 8); } } for (int i = 0; i < k; i++) { free(ptrs[i]); } }, singlethreaded); #undef NUM_LOOPS } static void basic_perf_malloc_free_by_size_class_offset(bool singlethreaded) { #define NUM_LOOPS 16 int cpu_number = _os_cpu_number(); run_test(^(size_t iteration __unused) { void *ptrs[NUM_LOOPS * 16]; int k = 0; for (int i = 0; i < NUM_LOOPS; i++) { for (int j = 0; j < 16; j++) { ptrs[k++] = malloc(16 * ((j + cpu_number) % 16) + 8); } } for (int i = 0; i < k; i++) { free(ptrs[i]); } }, singlethreaded); #undef NUM_LOOPS } // The tests that follow are grouped as follows: // - single-thread non-Nano version // - single-threaded NanoV1 version // - single-threaded NanoV2 version // - parallel non-Nano version // - parallel NanoV1 version // - parallel NanoV2 version // Each group probably could be built with a macro, but that would be harder // to debug when there is a problem. #pragma mark - #pragma mark 8-byte allocation/free T_DECL(basic_perf_serial_8_bytes, "Malloc/Free 8 bytes single-threaded", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_8_bytes(true); } T_DECL(basic_perf_serial_8_bytes_V1, "Malloc/Free 8 bytes single-threaded on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_serial_8_bytes_V2, "Malloc/Free 8 bytes single-threaded on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_8_bytes, "Malloc/Free 8 bytes parallel", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_8_bytes(false); } T_DECL(basic_perf_parallel_8_bytes_V1, "Malloc/Free 8 bytes parallel on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_8_bytes_V2, "Malloc/Free 8 bytes single-threaded on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #pragma mark - #pragma mark 8-byte allocation/free forcing block overflow with default scan policy T_DECL(basic_perf_serial_8_bytes_multi_block_default_scan_policy, "Malloc/Free 8 bytes single-threaded with block overflow, default scan policy", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_8_bytes_multi_block(true); } T_DECL(basic_perf_serial_8_bytes_multi_block_default_scan_policy_V1, "Malloc/Free 8 bytes single-threaded with block overflow, default scan policy on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes_multi_block(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_serial_8_bytes_multi_block_default_scan_policy_V2, "Malloc/Free 8 bytes single-threaded with block overflow, default scan policy on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes_multi_block(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_8_bytes_multi_block_default_scan_policy, "Malloc/Free 8 bytes parallel with block overflow, default scan policy", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_8_bytes_multi_block(false); } T_DECL(basic_perf_parallel_8_bytes_multi_block_default_scan_policy_V1, "Malloc/Free 8 bytes parallel with block overflow, default scan policy on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes_multi_block(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_8_bytes_multi_block_default_scan_policy_V2, "Malloc/Free 8 bytes parallel with block overflow, default scan policy on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes_multi_block(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #pragma mark - #pragma mark 8-byte allocation/free forcing block overflow with first-fit // This test only makes sense on Nanov2 T_DECL(basic_perf_serial_8_bytes_multi_block_first_fit_V2, "Malloc/Free 8 bytes single-threaded with block overflow, first-fit on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=V2"), T_META_ENVVAR("MallocNanoScanPolicy=firstfit")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes_multi_block(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_8_bytes_multi_block_first_fit_V2, "Malloc/Free 8 bytes parallel with block overflow, first-fit on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V2"), T_META_ENVVAR("MallocNanoScanPolicy=firstfit")) { #if CONFIG_NANOZONE basic_perf_malloc_free_8_bytes_multi_block(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #pragma mark - #pragma mark Repeated allocation/free where each thread uses a different size class T_DECL(basic_perf_serial_different_size_classes, "Malloc/Free in different size classes single-threaded", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_different_size_classes(false); } T_DECL(basic_perf_serial_different_size_classes_V1, "Malloc/Free in different size classes single-threaded on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_different_size_classes(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_serial_different_size_classes_V2, "Malloc/Free in different size classes single-threaded on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_different_size_classes(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_different_size_classes, "Malloc/Free in different size classes parallel", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_different_size_classes(false); } T_DECL(basic_perf_parallel_different_size_classes_V1, "Malloc/Free in different size classes parallel on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_different_size_classes(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_different_size_classes_V2, "Malloc/Free in different size classes single-threaded on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_different_size_classes(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #pragma mark - #pragma mark Repeated allocation/free for each size class T_DECL(basic_perf_serial_by_size_class, "Malloc/Free by size class single-threaded", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_by_size_class(true); } T_DECL(basic_perf_serial_by_size_class_V1, "Malloc/Free by size class single-threaded on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_by_size_class(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_serial_by_size_class_V2, "Malloc/Free by size class single-threaded on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_by_size_class(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_by_size_class, "Malloc/Free by size class parallel", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_by_size_class(false); } T_DECL(basic_perf_parallel_by_size_class_V1, "Malloc/Free by size class parallel on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_by_size_class(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_by_size_class_V2, "Malloc/Free by size class single-threaded on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_by_size_class(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #pragma mark - #pragma mark Repeated allocation/free for each size class, offset by CPU T_DECL(basic_perf_serial_by_size_class_offset, "Malloc/Free by size class with offset single-threaded", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=0")) { basic_perf_malloc_free_by_size_class_offset(true); } T_DECL(basic_perf_serial_by_size_class_offset_V1, "Malloc/Free by size class with offset single-threaded on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_by_size_class_offset(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_serial_by_size_class_offset_V2, "Malloc/Free by size class with offset single-threaded on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_by_size_class_offset(true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_by_size_class_offset, "Malloc/Free by size class with offset parallel", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone")) { basic_perf_malloc_free_by_size_class_offset(false); } T_DECL(basic_perf_parallel_by_size_class_offset_V1, "Malloc/Free by size class with offset parallel on V1", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE basic_perf_malloc_free_by_size_class_offset(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(basic_perf_parallel_by_size_class_offset_V2, "Malloc/Free by size class with offset single-threaded on V2", T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE basic_perf_malloc_free_by_size_class_offset(false); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/calloc_test.c ================================================ // // malloc_calloc_test.c // libmalloc // // test calloc for various sizes // #include #include #include #include #include #include static inline void* t_calloc(size_t count, size_t s) { void *ptr = calloc(count, s); T_QUIET; T_ASSERT_NOTNULL(ptr, "allocation"); size_t sz = malloc_size(ptr); T_QUIET; T_EXPECT_GE(sz, s * count, "allocation size"); char *p = ptr; for (int i = 0; i < s * count; i++, p++) { T_QUIET; T_ASSERT_EQ(*p, '\0', "nonzero byte at offset %d\n", i); } return ptr; } static void test_calloc(size_t count, size_t min, size_t max, size_t incr) { for (size_t s = min; s <= max; s += incr) { void *ptr = t_calloc(count, s); free(ptr); } } static void test_calloc_random(size_t count, size_t min, size_t max, size_t incr, size_t n) { const size_t r = (max - min) / incr, P = 100; void *ptrs[P] = {}; for (size_t i = 0, j = 0, k = 0; i < n + P; i++, j = k, k = (k + 1) % P) { void *ptr = NULL; if (i < n) ptr = t_calloc(count, min + arc4random_uniform(r) * incr); free(ptrs[j]); ptrs[k] = ptr; } } T_DECL(calloc_overflow_nano, "calloc with overflow (nano)", T_META_ENVVAR("MallocNanoZone=1")) { void *ptr = calloc(LONG_MAX, 256); T_ASSERT_EQ(ptr, NULL, "calloc overflow check #1"); free(ptr); ptr = calloc(256, LONG_MAX); T_ASSERT_EQ(ptr, NULL, "calloc overflow check #2"); free(ptr); } T_DECL(calloc_overflow, "calloc with overflow", T_META_ENVVAR("MallocNanoZone=0")) { void *ptr = calloc(LONG_MAX, 1000); T_ASSERT_EQ(ptr, NULL, "calloc overflow check #1"); free(ptr); ptr = calloc(1000, LONG_MAX); T_ASSERT_EQ(ptr, NULL, "calloc overflow check #2"); free(ptr); } T_DECL(calloc_nano, "nano calloc all sizes <= 256", T_META_ENVVAR("MallocNanoZone=1")) { test_calloc(1, 0, 256, 1); // NANO_MAX_SIZE test_calloc_random(1, 0, 256, 1, 100); test_calloc(16, 0, 256/16, 1); // NANO_MAX_SIZE test_calloc_random(16, 0, 256/16, 1, 100); test_calloc(32, 0, 256/32, 1); // NANO_MAX_SIZE test_calloc_random(32, 0, 256/32, 1, 100); } T_DECL(calloc_tiny, "tiny calloc 16b increments <= 1008", T_META_ENVVAR("MallocNanoZone=0")) { test_calloc(1, 0, 1008, 16); // SMALL_THRESHOLD test_calloc_random(1, 0, 1008, 16, 100); test_calloc(4, 0, 1008/4, 16); // SMALL_THRESHOLD test_calloc_random(4, 0, 1008/4, 16, 100); test_calloc(16, 0, 1008/16, 16); // SMALL_THRESHOLD test_calloc_random(16, 0, 1008/16, 16, 100); } // The next test is too demanding for some watches (taking over 15 minutes to // run) and for some AppleTVs, so use a cut-down version. #if TARGET_OS_WATCH || TARGET_OS_TV T_DECL(calloc, "calloc all 2048b increments <= 130kb", T_META_ENVVAR("MallocNanoZone=0")) { test_calloc(1, 1024, 130 * 1024, 2048); // > LARGE_THRESHOLD_LARGEMEM test_calloc_random(1, 1024, 130 * 1024, 2048, 50); test_calloc(16, 1024/16, 130 * 1024/16, 2048); test_calloc_random(16, 1024/16, 130 * 1024/16, 2048, 50); test_calloc(64, 1024/64, 130 * 1024/64, 2048); test_calloc_random(64, 1024/64, 130 * 1024/64, 2048, 50); } #else // !TARGET_OS_WATCH && !TARGET_OS_TV T_DECL(calloc, "calloc all 512b increments <= 256kb", T_META_ENVVAR("MallocNanoZone=0")) { test_calloc(1, 1024, 256 * 1024, 512); // > LARGE_THRESHOLD_LARGEMEM test_calloc_random(1, 1024, 256 * 1024, 512, 100); test_calloc(16, 1024/16, 256 * 1024/16, 512); test_calloc_random(16, 1024/16, 256 * 1024/16, 512, 100); test_calloc(64, 1024/64, 256 * 1024/64, 512); test_calloc_random(64, 1024/64, 256 * 1024/64, 512, 100); } #endif // !TARGET_OS_WATCH && !TARGET_OS_TV ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/libmalloc_tests.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXFileReference section */ 92D71A2B1B99355C00108CA1 /* stress_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stress_test.c; path = ../tests/stress_test.c; sourceTree = ""; }; 92D71A321B99364200108CA1 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXGroup section */ 92D71A1C1B99354500108CA1 = { isa = PBXGroup; children = ( 92D71A321B99364200108CA1 /* Makefile */, 92D71A2B1B99355C00108CA1 /* stress_test.c */, ); sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXLegacyTarget section */ 92D71A211B99354500108CA1 /* libmalloc_tests */ = { isa = PBXLegacyTarget; buildArgumentsString = "$(ACTION)"; buildConfigurationList = 92D71A241B99354500108CA1 /* Build configuration list for PBXLegacyTarget "libmalloc_tests" */; buildPhases = ( ); buildToolPath = /usr/bin/make; dependencies = ( ); name = libmalloc_tests; passBuildSettingsInEnvironment = 1; productName = libmalloc_tests; }; /* End PBXLegacyTarget section */ /* Begin PBXProject section */ 92D71A1D1B99354500108CA1 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Apple Inc"; TargetAttributes = { 92D71A211B99354500108CA1 = { CreatedOnToolsVersion = 7.0; }; }; }; buildConfigurationList = 92D71A201B99354500108CA1 /* Build configuration list for PBXProject "libmalloc_tests" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 92D71A1C1B99354500108CA1; projectDirPath = ""; projectRoot = ""; targets = ( 92D71A211B99354500108CA1 /* libmalloc_tests */, ); }; /* End PBXProject section */ /* Begin XCBuildConfiguration section */ 92D71A221B99354500108CA1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Debug; }; 92D71A231B99354500108CA1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Release; }; 92D71A251B99354500108CA1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; }; name = Debug; }; 92D71A261B99354500108CA1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 92D71A201B99354500108CA1 /* Build configuration list for PBXProject "libmalloc_tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 92D71A221B99354500108CA1 /* Debug */, 92D71A231B99354500108CA1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 92D71A241B99354500108CA1 /* Build configuration list for PBXLegacyTarget "libmalloc_tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 92D71A251B99354500108CA1 /* Debug */, 92D71A261B99354500108CA1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 92D71A1D1B99354500108CA1 /* Project object */; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/madvise.c ================================================ #include #include #include #include #include #include #include #include "base.h" #include "platform.h" #include "nano_zone_common.h" #include "nano_zone.h" extern int malloc_engaged_nano(void); #define T_EXPECT_BYTES(p, len, byte, msg, ...) do { \ char *_p = (char *)(p); \ bool bytes = true; \ for (int i=0; i= vm_page_size) { T_SKIP("vm_kernel_page_size >= vm_page_size, skipping subpage tests"); return; } // Map 32k of memory. size_t memsz = 32 * vm_page_size; void *mem = mmap(NULL, memsz, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, 0, 0); T_EXPECT_NE_PTR(mem, MAP_FAILED, "mapped pages should not be MAP_FAILED"); // Fill it with scribble. memset(mem, 0xa, 32 * vm_page_size); // Madvise a specfic page. T_EXPECT_POSIX_ZERO( madvise(mem + (4 * vm_kernel_page_size), vm_kernel_page_size, MADV_FREE_REUSABLE), "madvise (mem + 4 pages)"); // Check the entire page is empty. T_EXPECT_BYTES(mem + (4 * vm_kernel_page_size), vm_kernel_page_size, 0x0, "madvise'd memory is all zeros"); // Check that the subsequent page is 0xaa. T_EXPECT_BYTES(mem + (5 * vm_kernel_page_size), vm_kernel_page_size, 0xa, "un-madvise'd memory is all 0xaa"); T_EXPECT_POSIX_SUCCESS(munmap(mem, memsz), "munmap"); } // disable nano_subpage_madvise due to consistent // failures. #if 0 T_DECL(nano_subpage_madvise, "nano allocator madvise", T_META_SYSCTL_INT("vm.madvise_free_debug=1"), T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO), T_META_ASROOT(YES)) { T_EXPECT_TRUE(malloc_engaged_nano(), "nano zone enabled"); void *ptr = malloc(128); T_EXPECT_EQ_PTR( (void *)(((uintptr_t)ptr) >> (64-NANO_SIGNATURE_BITS)), (void *)NANOZONE_SIGNATURE, "malloc == nano allocation"); free(ptr); const size_t granularity = 128; const size_t allocations = 128 * 1024; void *bank[allocations / granularity]; for (int i=0; i<(sizeof(bank)/sizeof(*bank)); i++) { // allocate 128k of memory, scribble them bank[i] = malloc(granularity); memset(bank[i], 'A', granularity); } ptr = NULL; size_t limit = vm_kernel_page_size; for (int i=0; i<256; i++) { // find the first entry that lies on the user page // boundary, rather than kernel, to try and find // bugs where we accidentally round up to other page // sizes. if (!ptr && trunc_page((uintptr_t)bank[i]) != (uintptr_t)bank[i]) { continue; } // mark active, free the entry, then decrement the // limit until we get to a full page. if (!ptr) { ptr = bank[i]; } free(bank[i]); bank[i] = NULL; limit -= 128; if (limit == 0) { // finished, break. break; } } // force the nano alloc to madvise things malloc_zone_pressure_relief(malloc_default_zone(), 0); // we should be able to test for the entire range that's // madvised being zeros now. T_EXPECT_BYTES(ptr, vm_kernel_page_size, 0x0, "madvised region was cleared"); // and that the page immediately after the kernel page is // stil intacted. T_EXPECT_BYTES(ptr + vm_kernel_page_size, vm_kernel_page_size, 'A', "non-madvised page check"); for (int i=0; i<(sizeof(bank)/sizeof(*bank)); i++) { free(bank[i]); } } #endif #if 0 // OS X has the recirc depot enabled, so more has to be done to reliably test // madvise on that platform. T_DECL(tiny_subpage_madvise, "tiny allocator madvise", T_META_SYSCTL_INT("vm.madvise_free_debug=1"), T_META_ENVVAR("MallocNanoZone=0"), T_META_ASROOT(YES)) { T_EXPECT_TRUE(!malloc_engaged_nano(), "nano zone disabled"); malloc_zone_t *zone = malloc_create_zone(0, 0); const size_t granularity = 16; const size_t allocations = 128 * 1024; void *bank[allocations / granularity]; for (int i=0; i<(sizeof(bank)/sizeof(*bank)); i++) { // allocate 128k of memory, scribble them bank[i] = malloc_zone_malloc(zone, granularity); memset(bank[i], 'A', granularity); printf("%p\n", bank[i]); if (i>0) { T_QUIET; T_ASSERT_EQ_PTR(((uintptr_t)bank[i-1]) + granularity, (uintptr_t)bank[i], "contiguous allocations required"); } } void *ptr = NULL; size_t num_needed = vm_kernel_page_size / granularity + 1; for (int i=1; i<(sizeof(bank)/sizeof(*bank)); i++) { // find the first page aligned entry if (!ptr && ((uintptr_t)bank[i] > round_page_kernel((uintptr_t)bank[i]) || (uintptr_t)bank[i] + granularity - 1 < round_page_kernel((uintptr_t)bank[i]))) { continue; } // when we find the entry, take this pointer and // also free the entry before. if (!ptr) { ptr = (void *)round_page_kernel((uintptr_t)bank[i]); } malloc_zone_free(zone, bank[i]); bank[i] = NULL; num_needed--; if (num_needed == 0) { break; } } T_ASSERT_NOTNULL(ptr, "expected pointer"); // we should be able to test for the entire range that's // madvised being zeros now. T_EXPECT_BYTES(ptr, vm_kernel_page_size, 0x0, "madvised region was cleared"); // and that the page immediately after the kernel page is // stil intacted. T_EXPECT_BYTES(ptr + vm_kernel_page_size + granularity, vm_kernel_page_size, 'A', "non-madvised page check"); for (int i=0; i<(sizeof(bank)/sizeof(*bank)); i++) { malloc_zone_free(zone, bank[i]); } } T_DECL(small_subpage_madvise, "small allocator madvise", T_META_SYSCTL_INT("vm.madvise_free_debug=1"), T_META_ENVVAR("MallocNanoZone=0")) { T_EXPECT_TRUE(!malloc_engaged_nano(), "nano zone disabled"); const size_t granularity = 512; const size_t allocations = 128 * 1024; void *bank[allocations / granularity]; for (int i=0; i<(sizeof(bank)/sizeof(*bank)); i++) { // allocate 128k of memory, scribble them bank[i] = malloc(granularity); memset(bank[i], 'A', granularity); } void *ptr = NULL; size_t num_needed = vm_kernel_page_size / granularity + 1; for (int i=1; i<(sizeof(bank)/sizeof(*bank)); i++) { // find the first page aligned entry if (!ptr && ((uintptr_t)bank[i] > round_page_kernel((uintptr_t)bank[i]) || (uintptr_t)bank[i] + granularity - 1 < round_page_kernel((uintptr_t)bank[i]))) { continue; } // when we find the entry, take this pointer and // also free the entry before. if (!ptr) { ptr = (void *)round_page_kernel((uintptr_t)bank[i]); } free(bank[i]); bank[i] = NULL; num_needed--; if (num_needed == 0) { break; } } T_ASSERT_NOTNULL(ptr, "expected pointer"); // we should be able to test for the entire range that's // madvised being zeros now. T_EXPECT_BYTES(ptr, vm_kernel_page_size, 0x0, "madvised region was cleared"); // and that the page immediately after the kernel page is // stil intacted. T_EXPECT_BYTES(ptr + vm_kernel_page_size + granularity, vm_kernel_page_size, 'A', "non-madvised page check"); for (int i=0; i<(sizeof(bank)/sizeof(*bank)); i++) { free(bank[i]); } } #endif // #if 0 ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/magazine_malloc.c ================================================ // // magazine_malloc.c // libmalloc // // Created by Kim Topley on 11/8/17. // #include #include #include T_DECL(malloc_zone_batch, "malloc_zone_batch_malloc and malloc_zone_batch_free") { const unsigned count = 10; void **results; unsigned number; // Use malloc_zone_batch_malloc() with a size that maps to the tiny // allocator. This should succeed. results = calloc(count, sizeof(void *)); number = malloc_zone_batch_malloc(malloc_default_zone(), 32, results, count); T_ASSERT_EQ(number, count, "allocated from tiny zone"); for (int i = 0; i < count; i++) { T_QUIET; T_ASSERT_NOTNULL(results[i], "pointer %d is NULL", i); } malloc_zone_batch_free(malloc_default_zone(), results, count); free(results); // Use malloc_zone_batch_malloc() with a size that maps to the small // allocator. This should fail. results = calloc(count, sizeof(void *)); number = malloc_zone_batch_malloc(malloc_default_zone(), 2048, results, count); T_ASSERT_EQ(0, number, "could not allocat from small zone"); for (int i = 0; i < count; i++) { T_QUIET; T_ASSERT_NULL(results[i], "pointer %d is not NULL", i); } malloc_zone_batch_free(malloc_default_zone(), results, count); free(results); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/magazine_rack.c ================================================ // // magazine_rack.c // libmalloc // // Created by Matt Wright on 8/29/16. // // #include #include "magazine_testing.h" T_DECL(basic_magazine_init, "allocate magazine counts") { struct rack_s rack; for (int i=1; i < 64; i++) { memset(&rack, 'a', sizeof(rack)); rack_init(&rack, RACK_TYPE_NONE, i, 0); T_ASSERT_NOTNULL(rack.magazines, "%d magazine initialisation", i); } } T_DECL(basic_magazine_deinit, "allocate deallocate magazines") { struct rack_s rack; memset(&rack, 'a', sizeof(rack)); rack_init(&rack, RACK_TYPE_NONE, 1, 0); T_ASSERT_NOTNULL(rack.magazines, "magazine init"); rack_destroy(&rack); T_ASSERT_NULL(rack.magazines, "magazine deinit"); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/magazine_small_test.c ================================================ // // magazine_small_test.c // libmalloc // // Created by Matt Wright on 8/22/16. // // #include #include "../src/magazine_small.c" #include "magazine_testing.h" static inline void test_rack_setup(rack_t *rack) { memset(rack, 'a', sizeof(rack)); rack_init(rack, RACK_TYPE_SMALL, 1, 0); T_QUIET; T_ASSERT_NOTNULL(rack->magazines, "magazine initialisation"); } T_DECL(basic_small_alloc, "small rack init and alloc") { struct rack_s rack; test_rack_setup(&rack); void *ptr = small_malloc_should_clear(&rack, SMALL_MSIZE_FOR_BYTES(512), false); T_ASSERT_NOTNULL(ptr, "allocation"); region_t *rgn = small_region_for_ptr_no_lock(&rack, ptr); T_ASSERT_NOTNULL(rgn, "allocation region found in rack"); size_t sz = small_size(&rack, ptr); T_ASSERT_EQ((int)sz, 512, "size == 32"); } T_DECL(basic_small_teardown, "small rack init, alloc, teardown") { struct rack_s rack; test_rack_setup(&rack); void *ptr = small_malloc_should_clear(&rack, TINY_MSIZE_FOR_BYTES(512), false); T_ASSERT_NOTNULL(ptr, "allocation"); rack_destroy_regions(&rack, SMALL_REGION_SIZE); for (int i=0; i < rack.region_generation->num_regions_allocated; i++) { T_QUIET; T_ASSERT_TRUE(rack.region_generation->hashed_regions[i] == HASHRING_OPEN_ENTRY || rack.region_generation->hashed_regions[i] == HASHRING_REGION_DEALLOCATED, "all regions destroyed"); } rack_destroy(&rack); T_ASSERT_NULL(rack.magazines, "magazines destroyed"); } T_DECL(basic_small_free, "small free") { struct rack_s rack; test_rack_setup(&rack); void *ptr = small_malloc_should_clear(&rack, SMALL_MSIZE_FOR_BYTES(512), false); T_ASSERT_NOTNULL(ptr, "allocation"); // free doesn't return an error (unless we assert here) free_small(&rack, ptr, SMALL_REGION_FOR_PTR(ptr), 0); size_t sz = small_size(&rack, ptr); T_ASSERT_EQ((int)sz, 0, "allocation freed (sz == 0)"); } T_DECL(basic_small_shrink, "small rack shrink") { struct rack_s rack; test_rack_setup(&rack); void *ptr = small_malloc_should_clear(&rack, SMALL_MSIZE_FOR_BYTES(1024), false); T_ASSERT_NOTNULL(ptr, "allocation"); size_t sz = small_size(&rack, ptr); T_ASSERT_EQ((int)sz, 1024, "size == 1024"); void *nptr = small_try_shrink_in_place(&rack, ptr, sz, 512); size_t nsz = small_size(&rack, nptr); T_ASSERT_EQ_PTR(ptr, nptr, "ptr == nptr"); T_ASSERT_EQ((int)nsz, 512, "nsz == 512"); } T_DECL(basic_small_realloc_in_place, "small rack realloc in place") { struct rack_s rack; test_rack_setup(&rack); // Allocate two blocks and free the second, then try to realloc() the first. // This should extend in-place using the one-level death row cache that's // occupied by the second block. void *ptr = small_malloc_should_clear(&rack, SMALL_MSIZE_FOR_BYTES(512), false); T_ASSERT_NOTNULL(ptr, "allocation"); size_t sz = small_size(&rack, ptr); T_ASSERT_EQ((int)sz, 512, "size == 512"); void *ptr2 = small_malloc_should_clear(&rack, SMALL_MSIZE_FOR_BYTES(512), false); T_ASSERT_NOTNULL(ptr2, "allocation 2"); T_ASSERT_EQ_PTR(ptr2, (void *)((uintptr_t)ptr + 512), "sequential allocations"); free_small(&rack, ptr2, SMALL_REGION_FOR_PTR(ptr2), 0); // Attempt to realloc up to 1024 bytes, this should happen in place // because of the death-row cache. boolean_t reallocd = small_try_realloc_in_place(&rack, ptr, sz, 1024); T_ASSERT_TRUE(reallocd, "realloced"); size_t nsz = small_size(&rack, ptr); T_ASSERT_EQ((int)nsz, 1024, "realloc size == 1024"); // Try another realloc(). This should extend in place because the rest of // the rack is empty. reallocd = small_try_realloc_in_place(&rack, ptr, nsz, 2048); T_ASSERT_TRUE(reallocd, "realloced #2"); nsz = small_size(&rack, ptr); T_ASSERT_EQ((int)nsz, 2048, "realloc size == 2048"); free_small(&rack, ptr, SMALL_REGION_FOR_PTR(ptr), 0); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/magazine_testing.h ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __MAGAZINE_TESTING #define __MAGAZINE_TESTING // Import the mvm_* functions for magazines to use #import "../src/vm.c" #import "../src/magazine_rack.c" boolean_t malloc_tracing_enabled = 0; int recirc_retained_regions = DEFAULT_RECIRC_RETAINED_REGIONS; // Stub out cross-file dependencies so that they just assert. void malloc_report(uint32_t flags, const char *fmt, ...) { __builtin_trap(); } void malloc_zone_error(uint32_t flags, bool is_corruption, const char *fmt, ...) { __builtin_trap(); } void malloc_zone_check_fail(const char *msg, const char *fmt, ...) { __builtin_trap(); } void szone_free(szone_t *szone, void *ptr) { __builtin_trap(); } void * szone_malloc(szone_t *szone, size_t size) { __builtin_trap(); } #endif // __MAGAZINE_TESTING ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/magazine_tiny_test.c ================================================ // // magazine_tiny_test.c // libmalloc // // Created by Matt Wright on 8/22/16. // // #include #include "../src/magazine_tiny.c" #include "magazine_testing.h" static inline void test_rack_setup(rack_t *rack) { memset(rack, 'a', sizeof(rack)); rack_init(rack, RACK_TYPE_TINY, 1, 0); T_QUIET; T_ASSERT_NOTNULL(rack->magazines, "magazine initialisation"); } T_DECL(basic_tiny_alloc, "tiny rack init and alloc") { struct rack_s rack; test_rack_setup(&rack); void *ptr = tiny_malloc_should_clear(&rack, TINY_MSIZE_FOR_BYTES(32), false); T_ASSERT_NOTNULL(ptr, "allocation"); region_t *rgn = tiny_region_for_ptr_no_lock(&rack, ptr); T_ASSERT_NOTNULL(rgn, "allocation region found in rack"); size_t sz = tiny_size(&rack, ptr); T_ASSERT_EQ((int)sz, 32, "size == 32"); } T_DECL(basic_tiny_teardown, "tiny rack init, alloc, teardown") { struct rack_s rack; test_rack_setup(&rack); void *ptr = tiny_malloc_should_clear(&rack, TINY_MSIZE_FOR_BYTES(32), false); T_ASSERT_NOTNULL(ptr, "allocation"); rack_destroy_regions(&rack, TINY_REGION_SIZE); for (int i=0; i < rack.region_generation->num_regions_allocated; i++) { T_QUIET; T_ASSERT_TRUE(rack.region_generation->hashed_regions[i] == HASHRING_OPEN_ENTRY || rack.region_generation->hashed_regions[i] == HASHRING_REGION_DEALLOCATED, "all regions destroyed"); } rack_destroy(&rack); T_ASSERT_NULL(rack.magazines, "magazines destroyed"); } T_DECL(basic_tiny_free, "tiny free") { struct rack_s rack; test_rack_setup(&rack); void *ptr = tiny_malloc_should_clear(&rack, TINY_MSIZE_FOR_BYTES(32), false); T_ASSERT_NOTNULL(ptr, "allocation"); // free doesn't return an error (unless we assert here) free_tiny(&rack, ptr, TINY_REGION_FOR_PTR(ptr), 0); size_t sz = tiny_size(&rack, ptr); T_ASSERT_EQ((int)sz, 0, "allocation freed (sz == 0)"); } T_DECL(basic_tiny_shrink, "tiny rack shrink") { struct rack_s rack; test_rack_setup(&rack); void *ptr = tiny_malloc_should_clear(&rack, TINY_MSIZE_FOR_BYTES(64), false); T_ASSERT_NOTNULL(ptr, "allocation"); size_t sz = tiny_size(&rack, ptr); T_ASSERT_EQ((int)sz, 64, "size == 64"); void *nptr = tiny_try_shrink_in_place(&rack, ptr, sz, 16); size_t nsz = tiny_size(&rack, nptr); T_ASSERT_EQ_PTR(ptr, nptr, "ptr == nptr"); T_ASSERT_EQ((int)nsz, 16, "nsz == 16"); } T_DECL(basic_tiny_realloc_in_place, "tiny rack realloc in place") { struct rack_s rack; test_rack_setup(&rack); // Allocate two blocks and free the second, then try to realloc() the first. // This should extend in-place using the one-level death row cache that's // occupied by the second block. void *ptr = tiny_malloc_should_clear(&rack, TINY_MSIZE_FOR_BYTES(16), false); T_ASSERT_NOTNULL(ptr, "allocation"); size_t sz = tiny_size(&rack, ptr); T_ASSERT_EQ((int)sz, 16, "size == 16"); void *ptr2 = tiny_malloc_should_clear(&rack, TINY_MSIZE_FOR_BYTES(16), false); T_ASSERT_NOTNULL(ptr2, "allocation 2"); T_ASSERT_EQ_PTR(ptr2, (void *)((uintptr_t)ptr + 16), "sequential allocations"); free_tiny(&rack, ptr2, TINY_REGION_FOR_PTR(ptr2), 0); // Attempt to realloc up to 32 bytes, this should happen in place // because of the death-row cache. boolean_t reallocd = tiny_try_realloc_in_place(&rack, ptr, sz, 32); T_ASSERT_TRUE(reallocd, "realloced #1"); size_t nsz = tiny_size(&rack, ptr); T_ASSERT_EQ((int)nsz, 32, "realloc size == 32"); // Try another realloc(). This should extend in place because the rest of // the rack is empty. reallocd = tiny_try_realloc_in_place(&rack, ptr, nsz, 64); T_ASSERT_TRUE(reallocd, "realloced #2"); nsz = tiny_size(&rack, ptr); T_ASSERT_EQ((int)nsz, 64, "realloc size == 64"); free_tiny(&rack, ptr, TINY_REGION_FOR_PTR(ptr), 0); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/malloc_claimed_address_tests.c ================================================ // // malloc_claimed_address_tests.c // libmalloc // // Tests for malloc_claimed_address() and malloc_zone_claimed_address(). // #include #include #include #include #include #include #include #include #include T_DECL(malloc_claimed_address_default_zone_test, "Tests for malloc_claimed_address, default zone only", T_META_ENVVAR("MallocNanoZone=0")) { // NULL is never a possible pointer. boolean_t result = malloc_claimed_address(NULL); T_EXPECT_FALSE(result, "NULL is never a valid pointer"); // Allocate from tiny, check that it's claimed. void *ptr = malloc(16); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from tiny"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 8); T_EXPECT_TRUE(result, "allocation from tiny with offset"); free(ptr); // Allocate from small, check that it's claimed. ptr = malloc(2048); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from small"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 1000); T_EXPECT_TRUE(result, "allocation from small with offset"); free(ptr); // Allocate from large, check that it's claimed. ptr = malloc(140000); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from large"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 1000); T_EXPECT_TRUE(result, "allocation from large with offset"); free(ptr); // Allocate some memory with vm_allocate() and make sure it's not claimed. mach_vm_address_t addr; kern_return_t kr = mach_vm_allocate(mach_task_self(), &addr, 1024, VM_FLAGS_ANYWHERE); T_ASSERT_TRUE(kr == KERN_SUCCESS, "allocate vm space"); result = malloc_claimed_address((void *)addr); T_EXPECT_FALSE(result, "address in VM allocated memory"); mach_vm_deallocate(mach_task_self(), addr, 1024); } T_DECL(malloc_zone_claimed_address_test, "Tests for malloc_zone_claimed_address", T_META_ENVVAR("MallocNanoZone=0")) { malloc_zone_t *zone = malloc_create_zone(0, 0); // NULL is never a possible pointer. boolean_t result = malloc_zone_claimed_address(zone, NULL); T_EXPECT_FALSE(result, "NULL is never a valid pointer"); // Allocate from tiny, check that it's claimed. void *ptr = malloc_zone_malloc(zone, 16); result = malloc_zone_claimed_address(zone, ptr); T_EXPECT_TRUE(result, "allocation from tiny"); // Offset from the pointer, check that it's still claimed. result = malloc_zone_claimed_address(zone, ptr + 8); T_EXPECT_TRUE(result, "allocation from tiny with offset"); free(ptr); // Allocate with tiny from the default zone, check that it's not claimed. ptr = malloc(16); result = malloc_zone_claimed_address(zone, ptr); T_EXPECT_FALSE(result, "allocation from tiny in default zone"); result = malloc_zone_claimed_address(zone, ptr + 8); T_EXPECT_FALSE(result, "allocation from tiny in default zone with offset"); free(ptr); // Allocate from small, check that it's claimed. ptr = malloc_zone_malloc(zone, 2048); result = malloc_zone_claimed_address(zone, ptr); T_EXPECT_TRUE(result, "allocation from small"); // Offset from the pointer, check that it's still claimed. result = malloc_zone_claimed_address(zone, ptr + 1000); T_EXPECT_TRUE(result, "allocation from small with offset"); free(ptr); // Allocate with small from the default zone, check that it's not claimed. ptr = malloc(2048); result = malloc_zone_claimed_address(zone, ptr); T_EXPECT_FALSE(result, "allocation from small in default zone"); result = malloc_zone_claimed_address(zone, ptr + 8); T_EXPECT_FALSE(result, "allocation from small in default zone with offset"); free(ptr); // Allocate from large, check that it's claimed. ptr = malloc_zone_malloc(zone, 140000); result = malloc_zone_claimed_address(zone, ptr); T_EXPECT_TRUE(result, "allocation from large"); // Offset from the pointer, check that it's still claimed. result = malloc_zone_claimed_address(zone, ptr + 1000); T_EXPECT_TRUE(result, "allocation from large with offset"); free(ptr); // Allocate with large from the default zone, check that it's not claimed. ptr = malloc(140000); result = malloc_zone_claimed_address(zone, ptr); T_EXPECT_FALSE(result, "allocation from large in default zone"); result = malloc_zone_claimed_address(zone, ptr + 8); T_EXPECT_FALSE(result, "allocation from large in default zone with offset"); free(ptr); // Allocate some memory with vm_allocate() and make sure it's not claimed. mach_vm_address_t addr; kern_return_t kr = mach_vm_allocate(mach_task_self(), &addr, 1024, VM_FLAGS_ANYWHERE); T_ASSERT_TRUE(kr == KERN_SUCCESS, "allocate vm space"); result = malloc_zone_claimed_address(zone, (void *)addr); T_EXPECT_FALSE(result, "address in VM allocated memory"); mach_vm_deallocate(mach_task_self(), addr, 1024); malloc_destroy_zone(zone); } T_DECL(malloc_claimed_address_zone_test, "Tests for malloc_claimed_address with another zone", T_META_ENVVAR("MallocNanoZone=0")) { // Allocate in a custom zone, check that we can still use // malloc_claimed_address() to check whether an address is claimed. malloc_zone_t *zone = malloc_create_zone(0, 0); // Allocate from tiny, check that it's claimed. void *ptr = malloc_zone_malloc(zone, 16); boolean_t result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from tiny"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 8); T_EXPECT_TRUE(result, "allocation from tiny with offset"); free(ptr); // Allocate from small, check that it's claimed. ptr = malloc_zone_malloc(zone, 2048); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from small"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 1000); T_EXPECT_TRUE(result, "allocation from small with offset"); free(ptr); // Allocate from large, check that it's claimed. ptr = malloc_zone_malloc(zone, 140000); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from large"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 1000); T_EXPECT_TRUE(result, "allocation from large with offset"); free(ptr); malloc_destroy_zone(zone); } T_DECL(malloc_claimed_address_nanozone_test, "Tests for malloc_claimed_address with nano", T_META_ENVVAR("MallocNanoZone=1")) { // NULL is never a possible pointer. boolean_t result = malloc_claimed_address(NULL); T_EXPECT_FALSE(result, "NULL is never a valid pointer"); // Allocate various sizes, check that they are claimed and that offset // pointers are also claimed. for (size_t sz = 16; sz <= 256; sz += 16) { void *ptr = malloc(sz); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "nano allocation size %d", (int)sz); result = malloc_claimed_address(ptr + sz/2); T_EXPECT_TRUE(result, "nano allocation size %d offset %d", (int)sz, (int)sz/2); free(ptr); } // Allocate some memory with vm_allocate() and make sure it's not claimed. mach_vm_address_t addr; kern_return_t kr = mach_vm_allocate(mach_task_self(), &addr, 1024, VM_FLAGS_ANYWHERE); T_ASSERT_TRUE(kr == KERN_SUCCESS, "allocate vm space"); result = malloc_claimed_address((void *)addr); T_EXPECT_FALSE(result, "address in VM allocated memory"); mach_vm_deallocate(mach_task_self(), addr, 1024); } T_DECL(malloc_claimed_address_custom_zone_test, "Tests for malloc_claimed_address in a zone that does not implement it", T_META_ENVVAR("MallocNanoZone=0")) { // Custom zones that do not support claimed_address must always appear // to return true. malloc_zone_t *zone = malloc_create_zone(0, 0); mprotect(zone, sizeof(*zone), PROT_READ | PROT_WRITE); zone->version = 9; zone->claimed_address = NULL; mprotect(zone, sizeof(*zone), PROT_READ); // NULL must still be disclaimed. boolean_t result = malloc_zone_claimed_address(zone, NULL); T_EXPECT_FALSE(result, "NULL is never a valid pointer"); // Allocate from tiny, check that it's claimed. void *ptr = malloc_zone_malloc(zone, 16); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from tiny"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 8); T_EXPECT_TRUE(result, "allocation from tiny with offset"); free(ptr); // Allocate from small, check that it's claimed. ptr = malloc_zone_malloc(zone, 2048); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from small"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 1000); T_EXPECT_TRUE(result, "allocation from small with offset"); free(ptr); // Allocate from large, check that it's claimed. ptr = malloc_zone_malloc(zone, 140000); result = malloc_claimed_address(ptr); T_EXPECT_TRUE(result, "allocation from large"); // Offset from the pointer, check that it's still claimed. result = malloc_claimed_address(ptr + 1000); T_EXPECT_TRUE(result, "allocation from large with offset"); free(ptr); malloc_destroy_zone(zone); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/malloc_free_test.c ================================================ // // malloc_free_test.c // libmalloc // // test allocating and freeing all sizes // #include #include #include #include static inline void* t_malloc(size_t s) { void *ptr = malloc(s); T_QUIET; T_ASSERT_NOTNULL(ptr, "allocation"); size_t sz = malloc_size(ptr); T_QUIET; T_EXPECT_LE(s, sz, "allocation size"); const uint64_t pat = 0xdeadbeefcafebabeull; memset_pattern8(ptr, &pat, s); return ptr; } static void test_malloc_free(size_t min, size_t max, size_t incr) { for (size_t s = min; s <= max; s += incr) { void *ptr = t_malloc(s); free(ptr); // try to go through mag_last_free SMALL_CACHE } for (size_t s = min, t = max; s <= max; s += incr, t -= incr) { void *ptr1 = t_malloc(s); void *ptr2 = t_malloc(t); free(ptr1); // try to defeat mag_last_free SMALL_CACHE free(ptr2); } } static void test_malloc_free_random(size_t min, size_t max, size_t incr, size_t n) { const size_t r = (max - min) / incr, P = 100; void *ptrs[P] = {}; for (size_t i = 0, j = 0, k = 0; i < n + P; i++, j = k, k = (k + 1) % P) { void *ptr = NULL; if (i < n) ptr = t_malloc(min + arc4random_uniform(r) * incr); free(ptrs[j]); ptrs[k] = ptr; } } T_DECL(malloc_free_nano, "nanomalloc and free all sizes <= 256", T_META_ENVVAR("MallocNanoZone=1")) { test_malloc_free(0, 256, 1); // NANO_MAX_SIZE test_malloc_free_random(0, 256, 1, 10000); } T_DECL(malloc_free_tiny, "tiny malloc and free 16b increments <= 1008", T_META_ENVVAR("MallocNanoZone=0")) { test_malloc_free(0, 1008, 16); // SMALL_THRESHOLD test_malloc_free_random(0, 1008, 16, 10000); } T_DECL(malloc_free, "malloc and free all 512b increments <= 256kb", T_META_ENVVAR("MallocNanoZone=0")) { test_malloc_free(1024, 256 * 1024, 512); // > LARGE_THRESHOLD_LARGEMEM test_malloc_free_random(1024, 256 * 1024, 512, 100000); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/malloc_size_test.c ================================================ // // malloc_size_test.c // libmalloc // // Tests for malloc_size() on both good and bad pointers. // #include #include #include static void test_malloc_size_valid(size_t min, size_t max, size_t incr) { for (size_t sz = min; sz <= max; sz += incr) { void *ptr = malloc(sz); T_ASSERT_NOTNULL(ptr, "Allocate size %llu\n", (uint64_t)sz); T_ASSERT_EQ(malloc_size(ptr), malloc_good_size(sz), "Check size value"); free(ptr); } } static void test_malloc_size_invalid(size_t min, size_t max, size_t incr) { for (size_t sz = min; sz <= max; sz += incr) { void *ptr = malloc(sz); T_ASSERT_NOTNULL(ptr, "Allocate size %llu\n", (uint64_t)sz); T_ASSERT_EQ(malloc_size(ptr + 1), 0UL, "Check offset by 1 size value"); T_ASSERT_EQ(malloc_size(ptr + sz/2), 0UL, "Check offset by half size value"); free(ptr); } } T_DECL(malloc_size_valid, "Test malloc_size() on valid pointers, non-Nano", T_META_ENVVAR("MallocNanoZone=0")) { // Test various sizes, roughly targetting each allocator range. test_malloc_size_valid(2, 256, 16); test_malloc_size_valid(512, 8192, 256); test_malloc_size_valid(8192, 65536, 1024); } T_DECL(malloc_size_valid_nanov1, "Test malloc_size() on valid pointers for Nanov1", T_META_ENVVAR("MallocNanoZone=V1")) { test_malloc_size_valid(2, 256, 16); } T_DECL(malloc_size_valid_nanov2, "Test malloc_size() on valid pointers for Nanov2", T_META_ENVVAR("MallocNanoZone=V2")) { test_malloc_size_valid(2, 256, 16); } T_DECL(malloc_size_invalid, "Test malloc_size() on invalid pointers, non-Nano", T_META_ENVVAR("MallocNanoZone=0")) { // Test various sizes, roughly targetting each allocator range. test_malloc_size_invalid(2, 256, 16); test_malloc_size_invalid(512, 8192, 256); test_malloc_size_invalid(8192, 32768, 1024); } T_DECL(malloc_size_invalid_nanov1, "Test malloc_size() on valid pointers for Nanov1", T_META_ENVVAR("MallocNanoZone=V1")) { test_malloc_size_invalid(2, 256, 16); } T_DECL(malloc_size_invalid_nanov2, "Test malloc_size() on valid pointers for Nanov2", T_META_ENVVAR("MallocNanoZone=V2")) { test_malloc_size_invalid(2, 256, 16); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/nano_tests.c ================================================ // // nano_tests.c // libmalloc // // Tests that are specific to the implementation details of Nanov2. // #include #include #include #include #include #include #include #include #include <../private/malloc_private.h> #include <../src/internal.h> #if CONFIG_NANOZONE #pragma mark - #pragma mark Enumerator access static int range_count; // Total number of allocated ranges static int ptr_count; // Total number of allocated pointers static size_t total_ranges_size; // Size of all allocated ranges static size_t total_in_use_ptr_size; // Size of all allocated pointers static void range_recorder(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned count) { for (int i = 0; i < count; i++) { total_ranges_size += ranges[i].size; } range_count += count; } static void pointer_recorder(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned count) { for (int i = 0; i < count; i++) { total_in_use_ptr_size += ranges[i].size; } ptr_count += count; } static kern_return_t memory_reader(task_t remote_task, vm_address_t remote_address, vm_size_t size, void **local_memory) { if (local_memory) { *local_memory = (void*)remote_address; return KERN_SUCCESS; } return KERN_FAILURE; } static void run_enumerator() { total_ranges_size = 0; total_in_use_ptr_size = 0; range_count = 0; ptr_count = 0; malloc_zone_t *zone = malloc_default_zone(); zone->introspect->enumerator(mach_task_self(), NULL, MALLOC_PTR_REGION_RANGE_TYPE, (vm_address_t)zone, memory_reader, range_recorder); zone->introspect->enumerator(mach_task_self(), NULL, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, memory_reader, pointer_recorder); } #endif // CONFIG_NANOZONE #pragma mark - #pragma mark Enumerator tests #if TARGET_OS_WATCH #define ALLOCATION_COUNT 10000 #else // TARGET_OS_WATCH #define ALLOCATION_COUNT 100000 #endif // TARGET_OS_WATCH static void *allocations[ALLOCATION_COUNT]; T_DECL(nano_active_test, "Test that Nano is activated", T_META_ENVVAR("MallocNanoZone=1")) { #if CONFIG_NANOZONE void *ptr = malloc(16); T_LOG("Nano ptr is %p\n", ptr); T_ASSERT_EQ(NANOZONE_SIGNATURE, (uint64_t)((uintptr_t)ptr) >> SHIFT_NANO_SIGNATURE, "Nanozone is active"); T_ASSERT_NE(malloc_engaged_nano(), 0, "Nanozone engaged"); free(ptr); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(nano_enumerator_test, "Test the Nanov2 enumerator", T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE T_ASSERT_EQ(malloc_engaged_nano(), 2, "Nanozone V2 engaged"); // This test is problematic because the allocator is used before the test // starts, so we can't start everything from zero. // Grab a baseline malloc_statistics_t stats; malloc_zone_statistics(malloc_default_zone(), &stats); const unsigned int initial_blocks_in_use = stats.blocks_in_use; const size_t initial_size_in_use = stats.size_in_use; const size_t initial_size_allocated = stats.size_allocated; run_enumerator(); const int initial_ptrs = ptr_count; const size_t initial_ranges_size = total_ranges_size; const size_t initial_in_use_ptr_size = total_in_use_ptr_size; // Allocate memory of random sizes, all less than the max Nano size. size_t total_requested_size = 0; for (int i = 0; i < ALLOCATION_COUNT; i++) { size_t sz = malloc_good_size(arc4random_uniform(257)); allocations[i] = malloc(sz); total_requested_size += sz; } // Get the stats and enumerator values again and check whether the result is consistent. malloc_zone_statistics(malloc_default_zone(), &stats); run_enumerator(); T_ASSERT_EQ(stats.blocks_in_use, initial_blocks_in_use + ALLOCATION_COUNT, "Incorrect blocks_in_use"); T_ASSERT_EQ(stats.size_in_use, initial_size_in_use + total_requested_size, "Incorrect size_in_use"); T_ASSERT_TRUE(stats.size_allocated - initial_size_allocated >= total_requested_size, "Size allocated must be >= size requested"); T_ASSERT_EQ(ptr_count, initial_ptrs + ALLOCATION_COUNT, "Incorrect number of pointers"); T_ASSERT_EQ(total_in_use_ptr_size, initial_in_use_ptr_size + total_requested_size, "Incorrect in-use pointer size"); // Free half of the memory and recheck the statistics size_t size_freed = 0; for (int i = 0; i < ALLOCATION_COUNT / 2; i++) { size_freed += malloc_size(allocations[i]); free(allocations[i]); } // Check the stats and enumerator values. malloc_zone_statistics(malloc_default_zone(), &stats); run_enumerator(); T_ASSERT_EQ(stats.blocks_in_use, initial_blocks_in_use + ALLOCATION_COUNT/2, "Incorrect blocks_in_use after half free"); T_ASSERT_EQ(stats.size_in_use, initial_size_in_use + total_requested_size - size_freed, "Incorrect size_in_use after half free"); T_ASSERT_TRUE(stats.size_allocated >= initial_size_allocated , "Size allocated must be >= size requested"); T_ASSERT_EQ(ptr_count, initial_ptrs + ALLOCATION_COUNT / 2, "Incorrect number of pointers after half free"); T_ASSERT_EQ(total_in_use_ptr_size, initial_in_use_ptr_size + total_requested_size - size_freed, "Incorrect in-use pointer size after half free"); // Free the rest the memory and recheck the statistics for (int i = ALLOCATION_COUNT / 2; i < ALLOCATION_COUNT; i++) { free(allocations[i]); } // Check the stats and enumerator values one more time. malloc_zone_statistics(malloc_default_zone(), &stats); run_enumerator(); T_ASSERT_EQ(stats.blocks_in_use, initial_blocks_in_use, "Incorrect blocks_in_use after full free"); T_ASSERT_EQ(stats.size_in_use, initial_size_in_use, "Incorrect size_in_use after full free"); T_ASSERT_TRUE(stats.size_allocated >= initial_size_allocated , "Size allocated must be >= size requested"); T_ASSERT_EQ(ptr_count, initial_ptrs, "Incorrect number of pointers after free"); T_ASSERT_EQ(total_in_use_ptr_size, initial_in_use_ptr_size, "Incorrect in-use pointer size after free"); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #pragma mark - #pragma mark Nano realloc tests // These tests are specific to the Nano implementation of realloc(). They // don't necessarily work with other allocators, since the behavior tested is // not part of the documented behavior of realloc(). const char * const data = "abcdefghijklm"; T_DECL(realloc_nano_size_class_change, "realloc with size class change", T_META_ENVVAR("MallocNanoZone=1")) { #if CONFIG_NANOZONE void *ptr = malloc(16); strcpy(ptr, data); void *new_ptr; // Each pass of the loop realloc's to the next size class up. We must // get a new pointer each time and the content must have been copied. for (int i = 32; i <= 256; i += 16) { new_ptr = realloc(ptr, i); T_QUIET; T_ASSERT_TRUE(ptr != new_ptr, "realloc pointer should change"); T_QUIET; T_ASSERT_EQ(i, (int)malloc_size(new_ptr), "Check size for new allocation"); T_QUIET; T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content must be copied"); T_QUIET; T_ASSERT_EQ(0, (int)malloc_size(ptr), "Old allocation not freed"); ptr = new_ptr; } free(new_ptr); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(realloc_nano_ptr_change, "realloc with pointer change", T_META_ENVVAR("MallocNanoZone=1")) { #if CONFIG_NANOZONE void *ptr = malloc(32); strcpy(ptr, data); void *new_ptr = realloc(ptr, 128); T_ASSERT_TRUE(ptr != new_ptr, "realloc pointer should change"); T_ASSERT_EQ(128, (int)malloc_size(new_ptr), "Wrong size for new allocation"); T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content must be copied"); T_ASSERT_EQ(0, (int)malloc_size(ptr), "Old allocation not freed"); free(new_ptr); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(realloc_nano_to_other, "realloc with allocator change (nano)", T_META_ENVVAR("MallocNanoZone=1")) { #if CONFIG_NANOZONE void *ptr = malloc(32); // From Nano strcpy(ptr, data); void *new_ptr = realloc(ptr, 1024); // Cannot be Nano. T_ASSERT_TRUE(ptr != new_ptr, "realloc pointer should change"); T_ASSERT_EQ(1024, (int)malloc_size(new_ptr), "Wrong size for new allocation"); T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content must be copied"); T_ASSERT_EQ(0, (int)malloc_size(ptr), "Old allocation not freed"); free(new_ptr); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(realloc_nano_to_zero_size, "realloc with target size zero", T_META_ENVVAR("MallocNanoZone=1")) { #if CONFIG_NANOZONE void *ptr = malloc(16); // Realloc to 0 frees the old memory and returns a valid pointer. void *new_ptr = realloc(ptr, 0); T_ASSERT_EQ(0, (int)malloc_size(ptr), "Old allocation not freed"); T_ASSERT_NOTNULL(new_ptr, "New allocation must be non-NULL"); T_ASSERT_TRUE(malloc_size(new_ptr) > 0, "New allocation not known"); free(new_ptr); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(realloc_nano_shrink, "realloc to smaller size", T_META_ENVVAR("MallocNanoZone=1")) { #if CONFIG_NANOZONE void *ptr = malloc(64); strcpy(ptr, data); // Reallocate to greater than half the current size - should remain // in-place. void *new_ptr = realloc(ptr, 40); T_ASSERT_TRUE(ptr == new_ptr, "realloc pointer should not change"); T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content changed"); // Reallocate to less than half the current size - should get a new pointer // Realloc to 0 frees the old memory and returns a valid pointer. ptr = new_ptr; new_ptr = realloc(ptr, 16); T_ASSERT_TRUE(ptr != new_ptr, "realloc pointer should change"); T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content must be copied"); free(new_ptr); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #pragma mark - #pragma mark Nanov2 tests // These tests are specific to the implementation of the Nanov2 allocator. // Guaranteed number of 256-byte allocations to be sure we fill an arena. #define ALLOCS_PER_ARENA ((NANOV2_ARENA_SIZE)/256) T_DECL(overspill_arena, "force overspill of an arena", T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE void **ptrs = calloc(ALLOCS_PER_ARENA, sizeof(void *)); T_QUIET; T_ASSERT_NOTNULL(ptrs, "Unable to allocate pointers"); int index; nanov2_addr_t first_ptr; ptrs[0] = malloc(256); T_QUIET; T_ASSERT_NOTNULL(ptrs[index], "Failed to allocate"); first_ptr.addr = ptrs[0]; for (index = 1; index < ALLOCS_PER_ARENA; index++) { ptrs[index] = malloc(256); T_QUIET; T_ASSERT_NOTNULL(ptrs[index], "Failed to allocate"); // Stop allocating once we have crossed into a new arena. nanov2_addr_t current_ptr; current_ptr.addr = ptrs[index]; if (current_ptr.fields.nano_arena != first_ptr.fields.nano_arena) { break; } } // Free everything, which is a check that the book-keeping works across // arenas. for (int i = 0; i <= index; i++) { free(ptrs[i]); } free(ptrs); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #if TARGET_OS_OSX // Guaranteed number of 256-byte allocations to be sure we fill a region. #define ALLOCS_PER_REGION ((NANOV2_REGION_SIZE)/256) // This test is required only on macOS because iOS only uses one region. T_DECL(overspill_region, "force overspill of a region", T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE void **ptrs = calloc(ALLOCS_PER_REGION, sizeof(void *)); T_QUIET; T_ASSERT_NOTNULL(ptrs, "Unable to allocate pointers"); int index; nanov2_addr_t first_ptr; ptrs[0] = malloc(256); T_QUIET; T_ASSERT_NOTNULL(ptrs[index], "Failed to allocate"); first_ptr.addr = ptrs[0]; for (index = 1; index < ALLOCS_PER_REGION; index++) { ptrs[index] = malloc(256); T_QUIET; T_ASSERT_NOTNULL(ptrs[index], "Failed to allocate"); // Stop allocating once we have crossed into a new region. nanov2_addr_t current_ptr; current_ptr.addr = ptrs[index]; if (current_ptr.fields.nano_region != first_ptr.fields.nano_region) { break; } } // Free everything, which is a check that the book-keeping works across // regions. for (int i = 0; i <= index; i++) { free(ptrs[i]); } free(ptrs); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } #endif // TARGET_OS_OSX ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/perf_contended_malloc_free.c ================================================ #include #include #include #include #include #include #include #include #include #include #include // number of times malloc & free are called per dt_stat batch #define ITERATIONS_PER_DT_STAT_BATCH 10000ull // number of times large malloc is called per dt_stat batch #define ITERATIONS_PER_DT_STAT_BATCH_LARGE_MALLOC 1000ull // max number of allocations kept live during the benchmark (< iterations above) #define LIVE_ALLOCATIONS 256 // maintain and print progress counters in between measurement batches #define COUNTERS 0 // move the darwintest assertion code out of the straight line execution path // since it is has non-trivial overhead and codegen impact even if the assertion // is never triggered. #define iferr(_e) if(__builtin_expect(!!(_e), 0)) #pragma mark - uint64_t random_busy_counts(unsigned int *seed, uint64_t *first, uint64_t *second) { uint64_t random = rand_r(seed); *first = 0x4 + (random & (0x10 - 1)); random >>= 4; *second = 0x4 + (random & (0x10 - 1)); random >>= 4; return random; } // By default busy() does no cpu busy work in the malloc bench enum { busy_is_nothing = 0, busy_is_cpu_busy, busy_is_cpu_yield, }; static int busy_select = busy_is_nothing; static double cpu_busy(uint64_t n) { double d = M_PI; uint64_t i; for (i = 0; i < n; i++) d *= M_PI; return d; } static double cpu_yield(uint64_t n) { uint64_t i; for (i = 0; i < n; i++) { #if defined(__arm__) || defined(__arm64__) asm volatile("yield"); #elif defined(__x86_64__) || defined(__i386__) asm volatile("pause"); #else #error Unrecognized architecture #endif } return 0; } __attribute__((noinline)) static double busy(uint64_t n) { switch(busy_select) { case busy_is_cpu_busy: return cpu_busy(n); case busy_is_cpu_yield: return cpu_yield(n); default: return 0; } } #pragma mark - static semaphore_t ready_sem, start_sem, end_sem; static uint32_t nthreads; static _Atomic uint32_t active_thr; static _Atomic int64_t todo; uint64_t iterations_per_dt_stat_batch = ITERATIONS_PER_DT_STAT_BATCH; #if COUNTERS static _Atomic uint64_t total_mallocs; #define ctr_inc(_t) atomic_fetch_add_explicit(&(_t), 1, memory_order_relaxed) #else #define ctr_inc(_t) #endif static uint32_t ncpu(void) { static uint32_t activecpu, physicalcpu; if (!activecpu) { uint32_t n; size_t s = sizeof(n); sysctlbyname("hw.activecpu", &n, &s, NULL, 0); activecpu = n; s = sizeof(n); sysctlbyname("hw.physicalcpu", &n, &s, NULL, 0); physicalcpu = n; } return MIN(activecpu, physicalcpu); } __attribute__((noinline)) static void threaded_bench(dt_stat_time_t s, int batch_size) { kern_return_t kr; for (int i = 0; i < nthreads; i++) { kr = semaphore_wait(ready_sem); iferr (kr) {T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait");} } atomic_init(&active_thr, nthreads); atomic_init(&todo, batch_size * iterations_per_dt_stat_batch); dt_stat_token t = dt_stat_begin(s); kr = semaphore_signal_all(start_sem); iferr (kr) {T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_signal_all");} kr = semaphore_wait(end_sem); iferr (kr) {T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait");} dt_stat_end_batch(s, batch_size, t); } static void setup_threaded_bench(void* (*thread_fn)(void*), bool singlethreaded) { kern_return_t kr; int r; char *e; if (singlethreaded) { nthreads = 1; } else { if ((e = getenv("DT_STAT_NTHREADS"))) nthreads = strtoul(e, NULL, 0); if (nthreads < 2) nthreads = ncpu(); } if ((e = getenv("DT_STAT_CPU_BUSY"))) busy_select = strtoul(e, NULL, 0); kr = semaphore_create(mach_task_self(), &ready_sem, SYNC_POLICY_FIFO, 0); T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_create"); kr = semaphore_create(mach_task_self(), &start_sem, SYNC_POLICY_FIFO, 0); T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_create"); kr = semaphore_create(mach_task_self(), &end_sem, SYNC_POLICY_FIFO, 0); T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_create"); pthread_attr_t attr; r = pthread_attr_init(&attr); T_QUIET; T_ASSERT_POSIX_ZERO(r, "pthread_attr_init"); r = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); T_QUIET; T_ASSERT_POSIX_ZERO(r, "pthread_attr_setdetachstate"); for (int i = 0; i < nthreads; i++) { pthread_t th; r = pthread_create(&th, &attr, thread_fn, (void *)(uintptr_t)(i+1)); T_QUIET; T_ASSERT_POSIX_ZERO(r, "pthread_create"); } } #pragma mark - static _Atomic(void*) allocations[LIVE_ALLOCATIONS]; static size_t max_rand, min_size, incr_size; static void * malloc_bench_thread(void * arg) { kern_return_t kr; int r; unsigned int seed; volatile double dummy; uint64_t pos, remaining_frees; void *alloc; restart: seed = (uintptr_t)arg; // each thread repeats its own sequence // start threads off in different positions in allocations array pos = (seed - 1) * (LIVE_ALLOCATIONS / nthreads); remaining_frees = LIVE_ALLOCATIONS; kr = semaphore_wait_signal(start_sem, ready_sem); T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait_signal"); while (1) { uint64_t first, second; uint64_t random = random_busy_counts(&seed, &first, &second); if (atomic_fetch_sub_explicit(&todo, 1, memory_order_relaxed) > 0) { dummy = busy(first); alloc = malloc(min_size + (random % (max_rand + 1)) * incr_size); iferr (!alloc) { T_ASSERT_POSIX_ZERO(errno, "malloc"); } ctr_inc(total_mallocs); } else { if (!remaining_frees--) break; alloc = NULL; } alloc = atomic_exchange(&allocations[(pos++)%LIVE_ALLOCATIONS], alloc); if (alloc) { dummy = busy(second); free(alloc); } } if (atomic_fetch_sub_explicit(&active_thr, 1, memory_order_relaxed) == 1) { kr = semaphore_signal(end_sem); T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_signal"); } goto restart; } static void malloc_bench(bool singlethreaded, size_t from, size_t to, size_t incr) { int r; int batch_size; #if COUNTERS uint64_t batch = 0; #endif setup_threaded_bench(malloc_bench_thread, singlethreaded); incr_size = incr; min_size = from; max_rand = (to - from) / incr; assert((to - from) % incr == 0); dt_stat_time_t s = dt_stat_time_create( nthreads > 1 ? "%llu malloc & free multithreaded" : "%llu malloc & free singlethreaded", iterations_per_dt_stat_batch); dt_stat_set_variable((dt_stat_t)s, "threads", nthreads); // For now, set the A/B failure threshold to 50% of baseline. // 40292129 tracks removing noise and setting a more useful threshold. dt_stat_set_variable((dt_stat_t) s, kPCFailureThresholdPctVar, 50.0); do { batch_size = dt_stat_batch_size(s); threaded_bench(s, batch_size); #if COUNTERS fprintf(stderr, "\rbatch: %4llu\t size: %4d\tmallocs: %8llu", ++batch, batch_size, atomic_load_explicit(&total_mallocs, memory_order_relaxed)); #endif } while (!dt_stat_stable(s)); #if COUNTERS fprintf(stderr, "\n"); #endif dt_stat_finalize(s); } T_DECL(perf_uncontended_nano_bench, "Uncontended nano malloc", T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=1"), T_META_TAG_PERF) { malloc_bench(true, 16, 256, 16); // NANO_MAX_SIZE } T_DECL(perf_contended_nano_bench, "Contended nano malloc", T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=1"), T_META_TAG_PERF) { malloc_bench(false, 16, 256, 16); // NANO_MAX_SIZE } T_DECL(perf_uncontended_tiny_bench, "Uncontended tiny malloc", T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=0"), T_META_TAG_PERF) { malloc_bench(true, 16, 1008, 16); // SMALL_THRESHOLD } T_DECL(perf_contended_tiny_bench, "Contended tiny malloc", T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_ENVVAR("MallocNanoZone=0"), T_META_TAG_PERF) { malloc_bench(false, 16, 1008, 16); // SMALL_THRESHOLD } T_DECL(perf_uncontended_small_bench, "Uncontended small malloc", T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_TAG_PERF) { malloc_bench(true, 1024, 15 * 1024, 512); // LARGE_THRESHOLD } T_DECL(perf_contended_small_bench, "Contended small malloc", T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_TAG_PERF) { malloc_bench(false, 1024, 15 * 1024, 512); // LARGE_THRESHOLD } T_DECL(perf_uncontended_large_bench, "Uncontended large malloc", T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_TAG_PERF) { iterations_per_dt_stat_batch = ITERATIONS_PER_DT_STAT_BATCH_LARGE_MALLOC; malloc_bench(true, 16 * 1024, 256 * 1024, 16 * 1024); } T_DECL(perf_contended_large_bench, "Contended large malloc", T_META_ALL_VALID_ARCHS(NO), T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false), T_META_TAG_PERF) { iterations_per_dt_stat_batch = ITERATIONS_PER_DT_STAT_BATCH_LARGE_MALLOC; malloc_bench(false, 16 * 1024, 256 * 1024, 16 * 1024); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/perf_realloc.c ================================================ #include #include #include <../src/internal.h> #include #include #pragma mark - #pragma mark Helper code #define PAGE_SPREAD 8 #define MAX_NAME_SIZE 256 #define OUTLIER_TIME 20000 // This value is a guess that will be refined over time. #define PERFCHECK_THRESHOLD_PCT 10.0 static char name[MAX_NAME_SIZE]; #define NUMBER_OF_SAMPLES_FOR_BATCH(s) _dt_stat_batch_size((dt_stat_t)s) // Measures the time required to realloc a block of memory of given size // by a specified amount. static void realloc_by_amount(const char *base_metric_name, size_t size, ssize_t amount, bool amount_is_random) { if (amount_is_random) { snprintf(name, MAX_NAME_SIZE, amount >= 0 ? "%s_from_%llu_up_random" : "%s_from_%llu_down_random", base_metric_name, (uint64_t)size); } else { snprintf(name, MAX_NAME_SIZE, amount >= 0 ? "%s_from_%llu_up_%ld" : "%s_from_%llu_down_%ld", base_metric_name, (uint64_t)size, labs(amount)); } uint64_t total_time = 0; int count = 0; dt_stat_time_t s = dt_stat_time_create(name); dt_stat_set_variable((dt_stat_t)s, "size (bytes)", (unsigned int)size); dt_stat_set_variable((dt_stat_t)s, "amount (bytes)", (int)amount); // rdar://problem/40417821: disable thresholds for now. //dt_stat_set_variable((dt_stat_t)s, kPCFailureThresholdPctVar, // PERFCHECK_THRESHOLD_PCT); dt_stat_token now = dt_stat_time_begin(s); for (;;) { void *ptr = malloc(size); T_QUIET; T_ASSERT_NOTNULL(ptr, "malloc for size %llu failed", (uint64_t)size); dt_stat_token start = dt_stat_time_begin(s); ptr = realloc(ptr, size + amount); dt_stat_token end = dt_stat_time_begin(s); T_QUIET; T_ASSERT_NOTNULL(ptr, "realloc from size %llu to size %llu failed", (uint64_t)size, (uint64_t)(size + amount)); total_time += end - start; free(ptr); if (++count >= NUMBER_OF_SAMPLES_FOR_BATCH(s)) { // Discard outliers or the test won't converge -- this should // be done in libdarwintest if (total_time/count < OUTLIER_TIME) { dt_stat_mach_time_add_batch(s, count, total_time); } total_time = 0; count = 0; if (dt_stat_stable(s)) { break; } } } dt_stat_finalize(s); } // Times a set of size adjustments from a given base. static void realloc_test_set(const char *base_name, size_t start_size) { ssize_t adj = 1; realloc_by_amount(base_name, start_size, adj, false); realloc_by_amount(base_name, start_size + adj, -adj, false); adj = 8; realloc_by_amount(base_name, start_size, adj, false); realloc_by_amount(base_name, start_size + adj, -adj, false); adj = 16; realloc_by_amount(base_name, start_size, adj, false); realloc_by_amount(base_name, start_size + adj, -adj, false); #if INCLUDE_RANDOM_SIZE_TESTS adj = 1 + arc4random_uniform(vm_page_size - 1); realloc_by_amount(base_name, start_size, adj, true); realloc_by_amount(base_name, start_size + adj, -adj, true); uint32_t pages = 1 + arc4random_uniform(PAGE_SPREAD); adj = pages * vm_page_size; realloc_by_amount(base_name, start_size, adj, true); realloc_by_amount(base_name, start_size + adj, -adj, true); #else // INCLUDE_RANDOM_SIZE_TESTS T_LOG("Skipping random size tests"); #endif // INCLUDE_RANDOM_SIZE_TESTS adj = vm_page_size; realloc_by_amount(base_name, start_size, adj, false); realloc_by_amount(base_name, start_size + adj, -adj, false); adj = 4 * vm_page_size; realloc_by_amount(base_name, start_size, adj, false); realloc_by_amount(base_name, start_size + adj, -adj, false); // Adjust up and then down by over half the size of the allocation -- // this skips the fast path in some allocators. adj = 4 * vm_page_size; realloc_by_amount(base_name, start_size, adj, false); realloc_by_amount(base_name, start_size + adj, -(((ssize_t)start_size) + adj)/2 - 1, false); adj = 32; realloc_by_amount(base_name, start_size, adj, false); realloc_by_amount(base_name, start_size + adj, -(((ssize_t)start_size) + adj)/2 - 1, false); } static void realloc_tests(const char *base_name, boolean_t using_nano) { // tiny or nano realloc_test_set(using_nano ? base_name : "Tiny", 8); if (!using_nano) { // No point in running these tests three times. // tiny realloc_test_set("Tiny", 512); // small realloc_test_set("Small", 2048); // large realloc_test_set("Large", 128 * 1024); } T_END; } #pragma mark - #pragma mark Tests for realloc() T_DECL(realloc_perf_base, "realloc without nano", T_META_TAG_PERF, T_META_ENVVAR("MallocNanoZone=0")) { realloc_tests("NoNano", false); } T_DECL(realloc_perf_nanov1, "realloc with nanoV1", T_META_TAG_PERF, T_META_ENVVAR("MallocNanoZone=V1")) { #if CONFIG_NANOZONE realloc_tests("Nanov1", true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } T_DECL(realloc_perf_nanov2, "realloc with nanoV2", T_META_TAG_PERF, T_META_ENVVAR("MallocNanoZone=V2")) { #if CONFIG_NANOZONE realloc_tests("Nanov2", true); #else // CONFIG_NANOZONE T_SKIP("Nano allocator not configured"); #endif // CONFIG_NANOZONE } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/radix_tree_test.m ================================================ #import #include #include #include #include #include #include #include #include #include "../src/radix_tree_debug.c" bool failed = false; #if 0 #define ASSERT(x) \ if (!(x)) { \ abort(); \ } #define ASSERT_EQ(x, y) \ if ((x) != (y)) { \ abort(); \ } #else #define ASSERT(x) \ if (!(x)) { \ failed = true; \ } #define ASSERT_EQ(x, y) \ if ((x) != (y)) { \ failed = true; \ } #endif static int log2i(uint64_t x) { if (x == 1) { return 0; } if (x & 1) { abort(); } return 1 + log2(x >> 1); } T_DECL(radix_tree_test, "radix_tree_test") { bool ok; // size_t size = 100 * 4096; // void *buf = malloc(size); // memset(buf, 0xde, size); // struct radix_tree *tree = radix_tree_init(buf, size); struct radix_tree *tree = radix_tree_create(); ASSERT(tree); const int n = 999; uint64_t minsize = 4096; for (uint64_t i = 0; i < n * minsize; i += minsize) { ok = radix_tree_insert(&tree, i, minsize, 3 * i); ASSERT(ok); /* printf("@@@@@@@@@@@--------\n"); */ /* radix_tree_print(tree); */ /* printf("@@@@@@@@@@---------\n"); */ for (uint64_t j = 0; j < n * minsize; j += minsize) { // printf ("testing %lld %lld\n", i, j); if (j <= i) { ASSERT(radix_tree_lookup(tree, j) == 3 * j); } else { ASSERT(radix_tree_lookup(tree, j) == radix_tree_invalid_value); } } } T_EXPECT_FALSE(failed, "insert 1 to n"); for (uint64_t i = 0; i < n * minsize; i += minsize) { ok = radix_tree_delete(&tree, i, minsize); ASSERT(ok); for (uint64_t j = 0; j < n * minsize; j += minsize) { if (j > i) { ASSERT(radix_tree_lookup(tree, j) == 3 * j); } else { ASSERT(radix_tree_lookup(tree, j) == radix_tree_invalid_value); } } } T_EXPECT_FALSE(failed, "delete 1 to n"); for (uint64_t i = 0; i < n * minsize; i += minsize) { ok = radix_tree_insert(&tree, i, minsize, 3 * i); ASSERT(ok); } for (uint64_t i = n * minsize; i > 0;) { i -= minsize; // printf("@@@@@@@@@@@--------\n"); // radix_tree_print(tree); // printf("@@@@@@@@@@---------\n"); ok = radix_tree_delete(&tree, i, minsize); ASSERT(ok); for (uint64_t j = 0; j < n * minsize; j += minsize) { if (j < i) { ASSERT_EQ(radix_tree_lookup(tree, j), 3 * j); } else { ASSERT_EQ(radix_tree_lookup(tree, j), radix_tree_invalid_value); } } } T_EXPECT_FALSE(failed, "delete n to 1"); srand(12345); NSMutableDictionary *d = [[NSMutableDictionary alloc] init]; for (uint64_t i = 0; i < n; i++) { @autoreleasepool { for (int j = 0; j < 3; j++) { uint64_t key = minsize * (rand() + ((uint64_t)rand() << 32)); uint64_t value = rand(); // printf("@@@@@@@@@@@--------\n"); // radix_tree_print(tree); // printf("@@@@@@@@@@---------inserting %llx\n", key); ok = radix_tree_insert(&tree, key, minsize, value); // printf("@@@@@@@@@@---------ok\n"); ASSERT(ok); d[@(key)] = @(value); } NSArray *array = [d allKeys]; id k = [array objectAtIndex:rand() % [array count]]; ok = radix_tree_delete(&tree, [k unsignedLongLongValue], minsize); ASSERT(ok); [d removeObjectForKey:k]; for (id k in d) { ASSERT_EQ(radix_tree_lookup(tree, [k unsignedLongLongValue]), [d[k] unsignedLongLongValue]); } uint64_t count = radix_tree_count(tree); // for (id k in d) { // printf("key %llx -> %llx\n", [k unsignedLongLongValue], [d[k] unsignedLongLongValue]); // } // radix_tree_print(tree); ASSERT_EQ((long)(count % minsize), 0l); ASSERT_EQ([[d allKeys] count], (long)(count / minsize)); } } T_EXPECT_FALSE(failed, "random"); for (id k in d) { radix_tree_delete(&tree, [k unsignedLongLongValue], minsize); } T_EXPECT_EQ_ULLONG(0ull, radix_tree_count(tree), "delete randoms"); ASSERT_EQ(radix_tree_lookup(tree, 0), -1); ASSERT_EQ(radix_tree_lookup(tree, minsize - 1), -1); ASSERT_EQ(radix_tree_lookup(tree, minsize), -1); ok = radix_tree_insert(&tree, 0, minsize, 0xf00); ASSERT(ok); ASSERT_EQ(radix_tree_lookup(tree, 0), 0xf00); ASSERT_EQ(radix_tree_lookup(tree, minsize - 1), 0xf00); ASSERT_EQ(radix_tree_lookup(tree, minsize), -1); // this would abort: // ok = radix_tree_insert(&tree, -minsize, minsize, 0xb00); // ASSERT(!ok); ok = radix_tree_insert(&tree, -2 * minsize, minsize, 0xb00); ASSERT(ok); ASSERT_EQ(radix_tree_lookup(tree, -2 * minsize), 0xb00); ASSERT_EQ(radix_tree_lookup(tree, -2 * minsize - 1), -1); ASSERT_EQ(radix_tree_lookup(tree, -2 * minsize + 1), 0xb00); ASSERT_EQ(radix_tree_lookup(tree, -2 * minsize + minsize - 1), 0xb00); ASSERT_EQ(radix_tree_lookup(tree, -1 * minsize), -1); ASSERT_EQ(radix_tree_lookup(tree, minsize), -1); radix_tree_delete(&tree, -2 * minsize, minsize); radix_tree_delete(&tree, 0, minsize); ASSERT_EQ(radix_tree_count(tree), 0); T_EXPECT_FALSE(failed, "off by 1"); int modelsize = 1024; uint32_t model[modelsize]; while (log2i(modelsize) + log2i(minsize) <= 64) { memset(model, 0xff, sizeof(model)); for (int i = 0; i < n; i++) { uint64_t start = rand() % modelsize; if (start == modelsize - 1 && log2i(modelsize) + log2i(minsize) == 64) { start = modelsize - 2; } uint64_t maxsize = modelsize - start; uint64_t size; if (maxsize / 4 > 1) { size = (rand() % (maxsize / 4)) + (rand() % (maxsize / 4)) + (rand() % (maxsize / 4)) + (rand() % (maxsize / 4)); } else if (maxsize > 1) { size = rand() % maxsize; } else { size = 1; } if (size == 0) { size = 1; } if (minsize * start + minsize * size < minsize * start) { size--; } if (minsize * start + minsize * size < minsize * start) { abort(); } if (size == 0) { abort(); } uint32_t value = rand(); // printf("inserting %llx, %llx\n", start*minsize, size*minsize); // radix_tree_print(tree); ok = radix_tree_insert(&tree, start * minsize, size * minsize, value); ASSERT(ok); // radix_tree_print(tree); ASSERT(radix_tree_fsck(tree)); for (uint64_t i = start; i < start + size; i++) { model[i] = value; } for (uint64_t j = 0; j < modelsize; j++) { uint64_t expected; if (model[j] == (uint32_t)-1) { expected = (uint64_t)-1; } else { expected = model[j]; } // radix_tree_print(tree); uint64_t ans = radix_tree_lookup(tree, j * minsize); // printf("j*minsize=%llx expected=%llx ans=%llx\n", j*minsize, expected, ans); ASSERT_EQ(expected, ans); } } T_EXPECT_FALSE(failed, "model %d", log2i(minsize) + log2i(modelsize)); radix_tree_delete(&tree, 0, -1); T_EXPECT_EQ_ULLONG(0ull, radix_tree_count(tree), "delete model"); minsize *= 2; tree->leaf_size_shift++; } } T_DECL(radix_tree_holes, "radix_tree_holes_test") { bool ok; struct radix_tree *tree = radix_tree_create(); T_ASSERT_NOTNULL(tree, "radix_tree_create()"); uint64_t size = 0xff00000; uint64_t minsize = 0x1000; uint64_t start = 0x10303c000; ok = radix_tree_insert(&tree, start, size, 0xf00ba); T_ASSERT_TRUE(ok, "created region"); ok = radix_tree_fsck(tree); T_QUIET; T_ASSERT_TRUE(ok, "fsck"); for (uint64_t addr = start; addr < start + size; addr += minsize) { T_QUIET; T_ASSERT_EQ_ULLONG(radix_tree_lookup(tree, addr), 0xf00ball, "stackid"); uint64_t index = (addr - start) / minsize; if (index % 2) { ok = radix_tree_delete(&tree, addr, minsize); T_QUIET; T_ASSERT_TRUE(ok, "deleted odd %lld", index); } } for (uint64_t addr = start; addr < start + size; addr += minsize) { uint64_t index = (addr - start) / minsize; T_QUIET; T_ASSERT_EQ_ULLONG(radix_tree_lookup(tree, addr), index % 2 ? -1 : 0xf00ball, "stackid"); if (!(index % 2)) { ok = radix_tree_delete(&tree, addr, minsize); T_QUIET; T_ASSERT_TRUE(ok, "deleted even, %lld", index); } } ok = radix_tree_fsck(tree); T_ASSERT_TRUE(ok, "fsck"); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/reallocarray.c ================================================ #include #include #include T_DECL(reallorarray, "reallocarray(3)", T_META_CHECK_LEAKS(NO)){ void *ptr; T_WITH_ERRNO; T_EXPECT_NOTNULL((ptr = reallocarray(NULL, 8, 8)), NULL); T_WITH_ERRNO; T_EXPECT_NOTNULL(reallocarray(ptr, 8, 8), NULL); T_EXPECT_NULL(reallocarray(NULL, SIZE_MAX >> 3, 1 << 5), NULL); T_EXPECT_EQ(errno, ENOMEM, NULL); T_EXPECT_NULL(reallocarray(ptr, SIZE_MAX >> 3, 1 << 5), NULL); T_EXPECT_EQ(errno, ENOMEM, NULL); } T_DECL(reallorarrayf, "reallocarrayf(3)", T_META_CHECK_LEAKS(NO)){ void *ptr; T_WITH_ERRNO; T_EXPECT_NOTNULL((ptr = reallocarrayf(NULL, 8, 8)), NULL); T_WITH_ERRNO; T_EXPECT_NOTNULL(reallocarrayf(ptr, 8, 8), NULL); T_EXPECT_NULL(reallocarrayf(NULL, SIZE_MAX >> 3, 1 << 5), NULL); T_EXPECT_EQ(errno, ENOMEM, NULL); T_EXPECT_NULL(reallocarrayf(ptr, SIZE_MAX >> 3, 1 << 5), NULL); T_EXPECT_EQ(errno, ENOMEM, NULL); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/stack_logging_test.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #if DARWINTEST #include #define FAIL(msg, ...) \ T_QUIET; \ T_FAIL(msg, ## __VA_ARGS__) #define EXPECT_TRUE(expr, msg, ...) \ T_QUIET; \ T_EXPECT_TRUE(expr, msg, ## __VA_ARGS__) #define EXPECT_EQ(val1, val2, msg, ...) \ T_QUIET; \ T_EXPECT_EQ(val1, val2, msg, ## __VA_ARGS__) #define PAUSE(msg) #else #define FAIL(msg, ...) \ { \ printf("test failure:"); \ printf(msg, ## __VA_ARGS__); \ printf("\n"); \ getchar(); \ } #define EXPECT_TRUE(expr, msg, ...) \ if (!(expr)) \ FAIL(msg, ## __VA_ARGS__); #define EXPECT_EQ(val1, val2, msg, ...) \ if (val1 != val2) \ FAIL(msg, ## __VA_ARGS__); // change this to actually pause if you want to examine the stacks using SamplingTools #define PAUSE(msg) \ printf(msg); \ printf("\n"); \ //getchar(); #endif const int max_size = 100; const int allocation_count = 10; const int item_count = 20; #define MAX_FRAMES 512 static void free_ptrs(malloc_zone_t *zone, char *ptrs[], int num_ptrs, boolean_t use_zone_free) { for (int i = 0; i < num_ptrs; i++) { size_t len = malloc_size(ptrs[i]); // set the memory to different values for possible diagnostics later on if (use_zone_free) { memset(ptrs[i], '!', len); zone->free(zone, ptrs[i]); } else if (zone) { memset(ptrs[i], '@', len); malloc_zone_free(zone, ptrs[i]); } else { memset(ptrs[i], '%', len); free(ptrs[i]); } } } static uint64_t get_stack_id_from_ptr(void *ptr) { size_t ptr_size = malloc_size(ptr) + 8; void *idptr = ptr + ptr_size - sizeof(uint64_t); return * (uint64_t *) idptr; } extern uint64_t __mach_stack_logging_shared_memory_address; static void check_stacks(char *ptrs[], int num_ptrs, boolean_t lite_mode) { mach_vm_address_t frames[MAX_FRAMES]; uint32_t frames_count; for (int i = 0; i < num_ptrs; i++) { kern_return_t ret = (lite_mode) ? __mach_stack_logging_get_frames_for_stackid(mach_task_self(), get_stack_id_from_ptr(ptrs[i]), frames, MAX_FRAMES, &frames_count, NULL) : __mach_stack_logging_get_frames(mach_task_self(), (mach_vm_address_t) ptrs[i], frames, MAX_FRAMES, &frames_count); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_get_frames = %d\n", (int) ret); EXPECT_TRUE(frames_count > 0, "number of frames returned from __mach_stack_logging_get_frames = %u\n", frames_count); } } static void test_malloc(malloc_zone_t *zone, boolean_t lite_mode, boolean_t validate_stacks, boolean_t use_zone_functions, boolean_t use_zone_free) { char *ptrs[allocation_count]; for (int i = 0; i < allocation_count; i++) { size_t size = rand() % max_size; if (use_zone_functions) { ptrs[i] = zone->malloc(zone, size); } else { ptrs[i] = zone ? malloc_zone_malloc(zone, size) : malloc(size); } // fill ptr with numbers in case a leak shows up for (int j = 0; j < size; j++) { ptrs[i][j] = '0' + i; } } if (validate_stacks) { check_stacks(ptrs, allocation_count, lite_mode); } PAUSE(zone ? "malloc_zone_malloc" : "malloc"); free_ptrs(zone, ptrs, allocation_count, use_zone_free); } static void test_calloc(malloc_zone_t *zone, boolean_t lite_mode, boolean_t validate_stacks, boolean_t use_zone_functions, boolean_t use_zone_free) { char *ptrs[allocation_count]; for (int i = 0; i < allocation_count; i++) { size_t size = rand() % max_size; if (use_zone_functions) { ptrs[i] = zone->calloc(zone, item_count, size); } else { ptrs[i] = zone ? malloc_zone_calloc(zone, item_count, size) : calloc(item_count, size); } // fill ptr with numbers in case a leak shows up for (int j = 0; j < size; j++) { ptrs[i][j] = 'A' + i; } } if (validate_stacks) { check_stacks(ptrs, allocation_count, lite_mode); } PAUSE(zone ? "malloc_zone_calloc" : "calloc"); free_ptrs(zone, ptrs, allocation_count, use_zone_free); } static void test_valloc(malloc_zone_t *zone, boolean_t lite_mode, boolean_t validate_stacks, boolean_t use_zone_functions, boolean_t use_zone_free) { char *ptrs[allocation_count]; for (int i = 0; i < allocation_count; i++) { size_t size = rand() % max_size; if (use_zone_functions) { ptrs[i] = zone->valloc(zone, size); } else { ptrs[i] = zone ? malloc_zone_valloc(zone, size) : valloc(size); } // fill ptr with numbers in case a leak shows up for (int j = 0; j < size; j++) { ptrs[i][j] = 'a' + i; } } if (validate_stacks) { check_stacks(ptrs, allocation_count, lite_mode); } PAUSE(zone ? "malloc_zone_valloc" : "valloc"); free_ptrs(zone, ptrs, allocation_count, use_zone_free); } static void test_realloc(malloc_zone_t *zone, boolean_t lite_mode, boolean_t validate_stacks, boolean_t use_zone_functions, boolean_t use_zone_free) { char *ptrs[allocation_count]; for (int i = 0; i < allocation_count; i++) { size_t size = rand() % max_size; ptrs[i] = zone ? malloc_zone_malloc(zone, size) : malloc(size); } for (int i = 0; i < allocation_count; i++) { size_t size = rand() % max_size; if (use_zone_functions) { ptrs[i] = zone->realloc(zone, ptrs[i], size); } else { ptrs[i] = zone ? malloc_zone_realloc(zone, ptrs[i], size) : realloc(ptrs[i], size); } // fill ptr with numbers in case a leak shows up for (int j = 0; j < size; j++) { ptrs[i][j] = 'r' + i; } } if (validate_stacks) { check_stacks(ptrs, allocation_count, lite_mode); } PAUSE(zone ? "malloc_zone_realloc" : "realloc"); free_ptrs(zone, ptrs, allocation_count, use_zone_free); } static void test_batch_malloc(malloc_zone_t *zone, boolean_t lite_mode, boolean_t validate_stacks, boolean_t use_zone_functions, boolean_t use_zone_free) { size_t size = rand() % max_size; void *results[allocation_count]; unsigned num_allocated; if (use_zone_functions) { num_allocated = zone->batch_malloc(zone, size, results, allocation_count); } else { num_allocated = malloc_zone_batch_malloc(zone, size, results, allocation_count); } if (validate_stacks && num_allocated > 0) { check_stacks((char**) results, num_allocated, lite_mode); } PAUSE("malloc_zone_batch_malloc"); for (int i = 0; i < num_allocated; i++) { size_t len = malloc_size(results[i]); memset(results[i], '$', len); } if (use_zone_free) { zone->batch_free(zone, results, num_allocated); } else { malloc_zone_batch_free(zone, results, num_allocated); } } static void test_memalign(malloc_zone_t *zone, boolean_t lite_mode, boolean_t validate_stacks, boolean_t use_zone_functions, boolean_t use_zone_free) { char *ptrs[allocation_count]; for (int i = 0; i < allocation_count; i++) { size_t size = rand() % max_size; if (use_zone_functions) { ptrs[i] = zone->memalign(zone, 1024, size); } else { ptrs[i] = malloc_zone_memalign(zone, 1024, size); } } if (validate_stacks) { check_stacks(ptrs, allocation_count, lite_mode); } PAUSE("malloc_zone_memalign"); free_ptrs(zone, ptrs, allocation_count, use_zone_free); } // tests calling zone->size and zone->free static void test_malloc_zone_functions(malloc_zone_t *zone) { void *ptrs[allocation_count]; for (int i = 0; i < allocation_count; i++) { size_t size = rand() % max_size; ptrs[i] = malloc(size); size_t allocated_size = malloc_size(ptrs[i]); EXPECT_TRUE(allocated_size >= size, "allocated size=%lu requested size=%lu", allocated_size, size); size_t zone_size = zone->size(zone, ptrs[i]); EXPECT_EQ(allocated_size, zone_size, "allocated size=%lu zone size=%lu", allocated_size, zone_size); } for (int i = 0; i < allocation_count; i++) { size_t len = malloc_size(ptrs[i]); memset(ptrs[i], '&', len); zone->free(zone, ptrs[i]); } } typedef struct { void *ptr1; size_t ptr1_size; void *ptr2; size_t ptr2_size; int num_ptrs_found; } zone_enumerator_info; static void zone_enumerator(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned count) { zone_enumerator_info *info = (zone_enumerator_info *) context; for (unsigned int i = 0; i < count; i++) { void *ptr = (void*) ranges[i].address; size_t size = ranges[i].size; if (ptr == info->ptr1 && size == info->ptr1_size) { info->num_ptrs_found++; } else if (ptr == info->ptr2 && size == info->ptr2_size) { info->num_ptrs_found++; } } } static void test_zone_enumeration(malloc_zone_t *zone, boolean_t lite_mode_enabled) { // allocate some ptrs with msl char *new_ptr_1 = malloc(10); char *new_ptr_2 = malloc(10); // now check to see if enumerating the default zone finds both ptrs zone_enumerator_info info; info.ptr1 = new_ptr_1; info.ptr1_size = malloc_size(info.ptr1); if (lite_mode_enabled) { // need to add 8 bytes to get raw size info.ptr1_size += 8; } info.ptr2 = new_ptr_2; info.ptr2_size = malloc_size(info.ptr2); if (lite_mode_enabled) { // need to add 8 bytes to get raw size info.ptr2_size += 8; } info.num_ptrs_found = 0; int expected_ptrs_found = 2; kern_return_t err = zone->introspect->enumerator(mach_task_self(), &info, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t) zone, NULL, zone_enumerator); EXPECT_EQ(err, KERN_SUCCESS, "return from default_zone->introspect->enumerator: %d", err); EXPECT_EQ(info.num_ptrs_found, expected_ptrs_found, "info.num_ptrs_found:%d expected:%d", info.num_ptrs_found, expected_ptrs_found); free(new_ptr_1); free(new_ptr_2); } static void test_virtual_default_zone(malloc_zone_t *zone, boolean_t nano_allocator_enabled, boolean_t lite_mode_enabled) { // leak in nano zone enumerator if (!nano_allocator_enabled) test_zone_enumeration(zone, lite_mode_enabled); } static void test_introspection_functions(malloc_zone_t *zone, boolean_t nano_allocator_enabled) { malloc_introspection_t *introspect = zone->introspect; size_t size = introspect->good_size(zone, 16); size_t expected_size = 16; EXPECT_EQ(size, expected_size, "introspect->good_size=%lu expected_size=%lu", size, expected_size); // malloc heap checking still crashes // boolean_t ret = introspect->check(zone); // EXPECT_EQ(ret, true, "introspect->check=%d", (int) ret); introspect->force_lock(zone); introspect->force_unlock(zone); boolean_t locked = introspect->zone_locked(zone); // can't check return value for nano allocator // nano_locked checks both the nano zone and helper zone, but nano force lock and force unlock only operate on the nano zone if (!nano_allocator_enabled) { EXPECT_EQ(locked, false, "introspect->zone_locked=%d", (int) locked); } malloc_statistics_t stats; char *p = zone->malloc(zone, 10); introspect->statistics(zone, &stats); // don't check the valus in status because of bug in szone_statistics? // also I imagine they could change over time so best not to rely on checing these internals zone->free(zone, p); } static void test_pressure_relief(malloc_zone_t *default_zone) { // call both the single zone and all zone versions // can't rely on return value to be consistent, so just make sure we don't crash // or corrupt memory malloc_zone_pressure_relief(default_zone, 0); malloc_zone_pressure_relief(NULL, 0); } static void test_realloc_non_lite_ptr(char *ptr) { // the ptr was malloc'd before lite mode was turned on, therefore not in the lite zone. // make sure realloc succeeds, and that the new ptr has a valid stack associated with it char *new_ptr = realloc(ptr, 200); mach_vm_address_t frames[MAX_FRAMES]; uint32_t frames_count; kern_return_t ret = __mach_stack_logging_get_frames_for_stackid(mach_task_self(), get_stack_id_from_ptr(new_ptr), frames, MAX_FRAMES, &frames_count, NULL); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_get_frames = %d\n", (int) ret); EXPECT_TRUE(frames_count > 0, "number of frames returned from __mach_stack_logging_get_frames = %u\n", frames_count); // test that we can realloc the ptr now that it's in the lite zone new_ptr = realloc(new_ptr, 100); ret = __mach_stack_logging_get_frames_for_stackid(mach_task_self(), get_stack_id_from_ptr(new_ptr), frames, MAX_FRAMES, &frames_count, NULL); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_get_frames = %d\n", (int) ret); EXPECT_TRUE(frames_count > 0, "number of frames returned from __mach_stack_logging_get_frames = %u\n", frames_count); free(new_ptr); } static void test_realloc_after_lite_mode_turned_off(char *lite_ptr, char *non_lite_ptr) { // make sure realloc works for both ptrs - do twice to test after ptr gets out of the lite zone char *new_lite_ptr = realloc(lite_ptr, 100); EXPECT_TRUE(new_lite_ptr != NULL, "realloc of new_lite_ptr"); new_lite_ptr = realloc(new_lite_ptr, 200); EXPECT_TRUE(new_lite_ptr != NULL, "realloc of new_lite_ptr"); char *new_non_lite_ptr = realloc(non_lite_ptr, 100); EXPECT_TRUE(new_non_lite_ptr != NULL, "realloc of new_non_lite_ptr"); new_non_lite_ptr = realloc(new_non_lite_ptr, 100); EXPECT_TRUE(new_non_lite_ptr != NULL, "realloc of new_non_lite_ptr"); free(new_lite_ptr); free(new_non_lite_ptr); } static void do_test(stack_logging_mode_type mode, boolean_t validate_stacks, boolean_t nano_allocator_enabled, boolean_t lite_mode_enabled) { printf("do_test. stack_logging_mode_type=%d validate_stacks=%d nano_allocator_enabled=%d\n", (int) mode, (int) validate_stacks, (int) nano_allocator_enabled); malloc_zone_t *default_zone = malloc_default_zone(); malloc_zone_t *default_purgeable_zone = malloc_default_purgeable_zone(); char *ptr = malloc(10); char *non_lite_ptr = malloc(10); // used in the realloc test later for lite mode malloc_zone_t *zone_from_ptr = malloc_zone_from_ptr(ptr); EXPECT_EQ(zone_from_ptr, default_zone, "malloc_zone_from_ptr:%p default_zone:%p\n", zone_from_ptr, default_zone); if (mode != stack_logging_mode_none) { test_introspection_functions(default_zone, nano_allocator_enabled); test_pressure_relief(default_zone); printf("turning on stack logging mode %d\n", (int) mode); turn_on_stack_logging(mode); // check to make sure returned default zone hasn't changed EXPECT_EQ(default_zone, malloc_default_zone(), "cached default zone:%p malloc_default_zone():%p", default_zone, malloc_default_zone()); EXPECT_EQ(default_purgeable_zone, malloc_default_purgeable_zone(), "cached default purgeable zone:%p malloc_default_purgeable_zone():%p", default_purgeable_zone, malloc_default_purgeable_zone()); malloc_zone_t *zone_from_ptr = malloc_zone_from_ptr(ptr); EXPECT_EQ(zone_from_ptr, default_zone, "malloc_zone_from_ptr:%p default_zone:%p\n", zone_from_ptr, default_zone); test_pressure_relief(default_zone); } test_introspection_functions(default_zone, nano_allocator_enabled); test_virtual_default_zone(default_zone, nano_allocator_enabled, lite_mode_enabled); // test to see if zone->size works on the ptr allocated at the beginning size_t ptr_size = default_zone->size(default_zone, ptr); EXPECT_TRUE(ptr_size > 0, "ptr_size=%d\n", (int) ptr_size); boolean_t lite_mode = lite_mode_enabled; if (validate_stacks) { kern_return_t ret = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_mode); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_start_reading = %d", ret); } // lite mode test: check realloc on a ptr that was created pre-enabling lite mode if (mode == stack_logging_mode_lite) { // this will free the ptr test_realloc_non_lite_ptr(ptr); } else { free(ptr); } // test regular versions test_malloc(NULL, lite_mode, validate_stacks, false, false); test_calloc(NULL, lite_mode, validate_stacks, false, false); test_valloc(NULL, lite_mode, validate_stacks, false, false); test_realloc(NULL, lite_mode, validate_stacks, false, false); // test malloc_zone versions test_malloc(default_zone, lite_mode, validate_stacks, false, false); test_calloc(default_zone, lite_mode, validate_stacks, false, false); test_valloc(default_zone, lite_mode, validate_stacks, false, false); test_realloc(default_zone, lite_mode, validate_stacks, false, false); test_batch_malloc(default_zone, lite_mode, validate_stacks, false, false); test_memalign(default_zone, lite_mode, validate_stacks, false, false); // test zone-> versions // if not lite mode then don't validate stacks, as this goes behind the back of the standard recorder if (!lite_mode) { validate_stacks = false; } test_malloc(default_zone, lite_mode, validate_stacks, true, false); test_calloc(default_zone, lite_mode, validate_stacks, true, false); test_valloc(default_zone, lite_mode, validate_stacks, true, false); test_realloc(default_zone, lite_mode, validate_stacks, true, false); test_batch_malloc(default_zone, lite_mode, validate_stacks, true, false); test_memalign(default_zone, lite_mode, validate_stacks, true, false); test_malloc(default_zone, lite_mode, validate_stacks, false, true); test_calloc(default_zone, lite_mode, validate_stacks, false, true); test_valloc(default_zone, lite_mode, validate_stacks, false, true); test_realloc(default_zone, lite_mode, validate_stacks, false, true); test_batch_malloc(default_zone, lite_mode, validate_stacks, false, true); test_memalign(default_zone, lite_mode, validate_stacks, false, true); test_malloc(default_zone, lite_mode, validate_stacks, true, true); test_calloc(default_zone, lite_mode, validate_stacks, true, true); test_valloc(default_zone, lite_mode, validate_stacks, true, true); test_realloc(default_zone, lite_mode, validate_stacks, true, true); test_batch_malloc(default_zone, lite_mode, validate_stacks, true, true); test_memalign(default_zone, lite_mode, validate_stacks, true, true); test_malloc_zone_functions(default_zone); char *lite_ptr = malloc(10); zone_from_ptr = malloc_zone_from_ptr(lite_ptr); EXPECT_EQ(zone_from_ptr, default_zone, "malloc_zone_from_ptr:%p default_zone:%p\n", zone_from_ptr, default_zone); if (mode != stack_logging_mode_none) { turn_off_stack_logging(); } zone_from_ptr = malloc_zone_from_ptr(lite_ptr); EXPECT_EQ(zone_from_ptr, default_zone, "malloc_zone_from_ptr:%p default_zone:%p\n", zone_from_ptr, default_zone); if (mode == stack_logging_mode_lite) { // this will free the ptrs test_realloc_after_lite_mode_turned_off(lite_ptr, non_lite_ptr); } else { free(lite_ptr); free(non_lite_ptr); } test_pressure_relief(default_zone); // check that the default zone hasn't changed after turning off stack logging EXPECT_EQ(default_zone, malloc_default_zone(), "cached default zone:%p malloc_default_zone():%p", default_zone, malloc_default_zone()); EXPECT_EQ(default_purgeable_zone, malloc_default_purgeable_zone(), "cached default purgeable zone:%p malloc_default_purgeable_zone():%p", default_purgeable_zone, malloc_default_purgeable_zone()); if (mode != stack_logging_mode_lite) { // if lite mode was turned on and then turned off, the zone is still around but allocations will not be done in the lite zone // so the enumerator will not find them - that's expected. This is similar to the situation where the nano zone is the default allocator // but some of the allocations occur in the helper zone, and calling the nano zone enumerator won't find these either. // This test uses small enough allocations that the nano zone always handles the allocations so we can test in that case. test_virtual_default_zone(default_zone, nano_allocator_enabled, lite_mode_enabled); } } static void test_enable_disable_enable_msl(unsigned long enable_value_1, unsigned long enable_value_2, boolean_t vm_only) { unsigned long event = enable_value_1; // Turn on MSL malloc mode malloc_memory_event_handler(event); char *ptrs[1]; ptrs[0] = malloc(10); boolean_t lite_or_vmlite_mode; boolean_t expected_lite_mode = (enable_value_1 & MEMORYSTATUS_ENABLE_MSL_LITE) != 0; kern_return_t ret = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_or_vmlite_mode); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_start_reading = %d", ret); EXPECT_TRUE(lite_or_vmlite_mode == expected_lite_mode, "return from __mach_stack_logging_start_reading - lite_or_vmlite_mode = %d", lite_or_vmlite_mode); // check to see if malloc stacks are present if (!vm_only) { check_stacks(ptrs, 1, lite_or_vmlite_mode); } // Turn off malloc mode event = MEMORYSTATUS_DISABLE_MSL; malloc_memory_event_handler(event); // verify that the stacks are still there // First have to clear any cached uniquing table, then check again __mach_stack_logging_stop_reading(mach_task_self()); ret = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_or_vmlite_mode); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_start_reading = %d", ret); EXPECT_TRUE(lite_or_vmlite_mode == expected_lite_mode, "return from __mach_stack_logging_start_reading - lite_or_vmlite_mode = %d", lite_or_vmlite_mode); if (!vm_only) { check_stacks(ptrs, 1, lite_or_vmlite_mode); } // now see if we can turn on malloc stack logging again event = enable_value_2; malloc_memory_event_handler(event); __mach_stack_logging_stop_reading(mach_task_self()); ret = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_or_vmlite_mode); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_start_reading = %d", ret); EXPECT_TRUE(lite_or_vmlite_mode == expected_lite_mode, "return from __mach_stack_logging_start_reading - lite_or_vmlite_mode = %d", lite_or_vmlite_mode); extern int stack_logging_enable_logging; extern boolean_t is_stack_logging_lite_enabled(void); if (lite_or_vmlite_mode && enable_value_1 == enable_value_2 && enable_value_1 != MEMORYSTATUS_ENABLE_MSL_LITE_VM) { EXPECT_TRUE(is_stack_logging_lite_enabled(), "is_stack_logging_lite_enabled() = %d", is_stack_logging_lite_enabled()); } else { int expected_stack_logging_enable_logging = (enable_value_1 == enable_value_2); EXPECT_TRUE(expected_stack_logging_enable_logging == stack_logging_enable_logging, "stack_logging_enable_logging = %d", stack_logging_enable_logging); } if (!vm_only) { check_stacks(ptrs, 1, lite_or_vmlite_mode); } free(ptrs[0]); } #if DARWINTEST T_DECL(msl_test_full_runtime_no_nano, "Test full mode of malloc stack logging enabled during runtime - not using nano allocator", T_META_ENVVAR("MallocNanoZone=0"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = false; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_all, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_malloc_runtime_no_nano, "Test malloc mode of malloc stack logging enabled during runtime - not using nano allocator", T_META_ENVVAR("MallocNanoZone=0"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = false; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_malloc, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_vm_runtime_no_nano, "Test vm mode of malloc stack logging enabled during runtime - not using nano allocator", T_META_ENVVAR("MallocNanoZone=0"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = false; boolean_t nano_allocator_enabled = false; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_vm, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_lite_runtime_no_nano, "Test lite mode of malloc stack logging enabled during runtime - not using nano allocator", T_META_ENVVAR("MallocNanoZone=0"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = false; boolean_t lite_mode_enabled = true; do_test(stack_logging_mode_lite, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_full_atstart_no_nano, "Test full mode of malloc stack logging enabled at start - not using nano allocator", T_META_ENVVAR("MallocStackLogging=1"), T_META_ENVVAR("MallocNanoZone=0"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = false; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_malloc_atstart_no_nano, "Test malloc mode of malloc stack logging enabled at start - not using nano allocator", T_META_ENVVAR("MallocStackLogging=malloc"), T_META_ENVVAR("MallocNanoZone=0"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = false; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_vm_atstart_no_nano, "Test vm mode of malloc stack logging enabled at start - not using nano allocator", T_META_ENVVAR("MallocStackLogging=vm"), T_META_ENVVAR("MallocNanoZone=0"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = false; boolean_t nano_allocator_enabled = false; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_lite_atstart_no_nano, "Test lite mode of malloc stack logging enabled at start - not using nano allocator", T_META_ENVVAR("MallocStackLogging=lite"), T_META_ENVVAR("MallocNanoZone=0"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = false; boolean_t lite_mode_enabled = true; do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_full_runtime_with_nano, "Test full mode of malloc stack logging enabled during runtime - using nano allocator", T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = true; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_all, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_malloc_runtime_with_nano, "Test malloc mode of malloc stack logging enabled during runtime - using nano allocator", T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = true; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_malloc, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_vm_runtime_with_nano, "Test vm mode of malloc stack logging enabled during runtime - using nano allocator", T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = false; boolean_t nano_allocator_enabled = true; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_vm, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_lite_runtime_with_nano, "Test lite mode of malloc stack logging enabled during runtime - using nano allocator", T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = true; boolean_t lite_mode_enabled = true; do_test(stack_logging_mode_lite, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_full_atstart_with_nano, "Test full mode of malloc stack logging enabled at start - using nano allocator", T_META_ENVVAR("MallocStackLogging=1"), T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = true; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_malloc_atstart_with_nano, "Test malloc mode of malloc stack logging enabled at start - using nano allocator", T_META_ENVVAR("MallocStackLogging=malloc"), T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = true; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_vm_atstart_with_nano, "Test vm mode of malloc stack logging enabled at start - using nano allocator", T_META_ENVVAR("MallocStackLogging=vm"), T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = false; boolean_t nano_allocator_enabled = true; boolean_t lite_mode_enabled = false; do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_lite_atstart_with_nano, "Test lite mode of malloc stack logging enabled at start - using nano allocator", T_META_ENVVAR("MallocStackLogging=lite"), T_META_ENVVAR("MallocNanoZone=1"), T_META_CHECK_LEAKS(NO)) { boolean_t validate_stacks = true; boolean_t nano_allocator_enabled = true; boolean_t lite_mode_enabled = true; do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } T_DECL(msl_test_serialize_uniquing_table, "Test that that stack uniquing table can be serialized, deserialized and read", T_META_ENVVAR("MallocStackLogging=lite")) { uintptr_t *foo = malloc(sizeof(uintptr_t)); T_ASSERT_NOTNULL(foo, "malloc"); uint64_t stackid = foo[1]; mach_vm_address_t frames1[STACK_LOGGING_MAX_STACK_SIZE]; uint32_t count1; kern_return_t kr; boolean_t lite_mode; kr = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_mode); T_ASSERT_MACH_SUCCESS(kr, "start reading"); kr = __mach_stack_logging_get_frames_for_stackid(mach_task_self(), stackid, frames1, STACK_LOGGING_MAX_STACK_SIZE, &count1, NULL); T_ASSERT_MACH_SUCCESS(kr, "get frames"); T_ASSERT_TRUE(count1 > 0, "frames not empty"); struct backtrace_uniquing_table *table = __mach_stack_logging_copy_uniquing_table(mach_task_self()); T_ASSERT_NOTNULL(table, "get a copy of the uniquing table"); mach_vm_size_t size = 0; void *serialized = __mach_stack_logging_uniquing_table_serialize(table, &size); T_ASSERT_NOTNULL(serialized, "serialize the table"); __mach_stack_logging_uniquing_table_release(table); table = NULL; table = __mach_stack_logging_uniquing_table_copy_from_serialized(serialized, size); T_ASSERT_NOTNULL(table, "deserialize the table"); kr = mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)serialized, size); T_ASSERT_MACH_SUCCESS(kr, "deallocate buffer"); mach_vm_address_t frames2[STACK_LOGGING_MAX_STACK_SIZE]; uint32_t count2; kr = __mach_stack_logging_uniquing_table_read_stack(table, stackid, frames2, &count2, STACK_LOGGING_MAX_STACK_SIZE); T_ASSERT_MACH_SUCCESS(kr, "get frames gain"); T_ASSERT_EQ(count1, count2, "frame counts match"); T_ASSERT_EQ(0, memcmp(frames1, frames2, sizeof(mach_vm_address_t) * count1), "frames match"); __mach_stack_logging_uniquing_table_release(table); free(foo); __mach_stack_logging_stop_reading(mach_task_self()); } static vm_address_t __attribute__((noinline)) allocate() { vm_address_t region = 0; vm_allocate(mach_task_self(), ®ion, 0x1000, VM_FLAGS_ANYWHERE); return region; } static void __attribute__((noinline)) allocate_end() { } static void do_test_msl_vm_stacks() { vm_address_t region = allocate(); T_ASSERT_NOTNULL((void *)region, "allocated region"); boolean_t lite_mode; kern_return_t kr = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_mode); T_ASSERT_MACH_SUCCESS(kr, "start reading"); T_ASSERT_TRUE(lite_mode, "check lite mode"); uint64_t stackid = __mach_stack_logging_stackid_for_vm_region(mach_task_self(), region); T_EXPECT_FALSE(stackid == -1, "check that stackid is valid"); mach_vm_address_t frames[512]; uint32_t count =0; bool last_frame_is_threadid; kr = __mach_stack_logging_get_frames_for_stackid(mach_task_self(), stackid, frames, sizeof(frames)/sizeof(frames[0]), &count, &last_frame_is_threadid); T_ASSERT_MACH_SUCCESS(kr, "get frames"); void *allocate_ptr = allocate; void *allocate_end_ptr = allocate_end; #if __has_feature(ptrauth_calls) allocate_ptr = ptrauth_strip(allocate_ptr, ptrauth_key_function_pointer); allocate_end_ptr = ptrauth_strip(allocate_end_ptr, ptrauth_key_function_pointer); #endif /* __has_feature(ptrauth_calls) */ T_LOG("allocate = %p", allocate_ptr); T_LOG("allocate_end = %p", allocate_end_ptr); bool found = false; for (uint32_t i = 0; i < count; i++) { T_LOG("frames[%d] = %llx", (int)i, frames[i]); if (frames[i] >= (uintptr_t)allocate_ptr && frames[i] < (uintptr_t)allocate_end_ptr) { T_LOG("found!"); found = true; } } T_EXPECT_TRUE(found, "found allocate() in the frames"); vm_deallocate(mach_task_self(), region, 0x1000); __mach_stack_logging_stop_reading(mach_task_self()); } static void do_test_msl_no_vm_stacks() { vm_address_t region = allocate(); T_ASSERT_NOTNULL((void *)region, "allocated region"); boolean_t lite_mode = false; kern_return_t kr = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_mode); if (__mach_stack_logging_shared_memory_address) { T_ASSERT_MACH_SUCCESS(kr, "start_reading return code with initialized __mach_stack_logging_shared_memory_address"); T_ASSERT_TRUE(lite_mode, "check lite mode with initialized __mach_stack_logging_shared_memory_address"); } else { T_ASSERT_MACH_ERROR_(kr, KERN_FAILURE, "start_reading return code with uninitialized __mach_stack_logging_shared_memory_address"); T_ASSERT_FALSE(lite_mode, "check lite mode with uninitialized __mach_stack_logging_shared_memory_address"); } uint64_t stackid = __mach_stack_logging_stackid_for_vm_region(mach_task_self(), region); T_EXPECT_TRUE(stackid == -1, "check that no stackid is available for VM region"); __mach_stack_logging_stop_reading(mach_task_self()); } T_DECL(msl_lite_vm_stacks, "test that we can read stack logs for VM region in lite mode", T_META_ENVVAR("MallocStackLogging=lite")) { do_test_msl_vm_stacks(); } T_DECL(msl_lite_vm_stacks_no_env, "like msl_vmlite but we turn on stack logging with a function call ") { turn_on_stack_logging(stack_logging_mode_lite); do_test_msl_vm_stacks(); } T_DECL(msl_vmlite_vm_stacks, "test that we can read stack logs for VM region in vmlite mode", T_META_ENVVAR("MallocStackLogging=vmlite")) { do_test_msl_vm_stacks(); } T_DECL(msl_vmlite_no_malloc_stacks, "test that no malloc stacks are logged in vmlite mode", T_META_ENVVAR("MallocStackLogging=vmlite")) { extern boolean_t is_stack_logging_lite_enabled(void); T_EXPECT_FALSE(is_stack_logging_lite_enabled(), "is_stack_logging_lite_enabled() = %d", is_stack_logging_lite_enabled()); } T_DECL(msl_vmlite_vm_stacks_no_env, "test absence/presence of vm stacks in vmlite mode, controlled with function calls") { do_test_msl_no_vm_stacks(); turn_on_stack_logging(stack_logging_mode_vmlite); do_test_msl_vm_stacks(); turn_off_stack_logging(); do_test_msl_no_vm_stacks(); } T_DECL(msl_vmlite_stress, "stress test for VM region in lite mode", T_META_ENVVAR("MallocStackLogging=lite")) { vm_size_t size = 0xff00000; vm_size_t minsize = 0x1000; vm_address_t region; vm_allocate(mach_task_self(), ®ion, size, VM_FLAGS_ANYWHERE); T_EXPECT_TRUE(region != 0, "allocated region %llx", (long long) region); for (vm_address_t addr = region; addr < region + size; addr += minsize) { vm_size_t index = (addr - region) / minsize; if (index % 2) { //T_LOG("deallocate %llx", (long long)addr); vm_deallocate(mach_task_self(), addr, minsize); } } for (vm_address_t addr = region; addr < region + size; addr += minsize) { vm_size_t index = (addr - region) / minsize; if (!(index % 2)) { //T_LOG("deallocate %llx", (long long)addr); vm_deallocate(mach_task_self(), addr, minsize); } } ; vm_address_t regions[5000]; for (int i = 0; i < sizeof(regions)/sizeof(regions[0]); i++) { vm_allocate(mach_task_self(), ®ions[i], minsize, VM_FLAGS_ANYWHERE); T_QUIET; T_EXPECT_TRUE(regions[i] != 0, "allocation succeeded %llx", (long long) regions[i]); } for (int i = 0; i < sizeof(regions)/sizeof(regions[0]); i++) { vm_deallocate(mach_task_self(), regions[i], minsize); } T_END; } T_DECL(msl_test_malloc_memory_event_handler, "Test the memory event handler") { #if !TARGET_OS_OSX T_SKIP("Skipping for non-OSX platform") #endif unsigned long event = NOTE_MEMORYSTATUS_PROC_LIMIT_WARN; // Trigger a memory resource exception warning malloc_memory_event_handler(event); char *ptrs[1]; ptrs[0] = malloc(10); boolean_t lite_mode; kern_return_t ret = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_mode); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_start_reading = %d", ret); EXPECT_TRUE(lite_mode, "return from __mach_stack_logging_start_reading - lite_mode = %d", lite_mode); // check to see if malloc stacks are present check_stacks(ptrs, 1, true); // enter critical, should turn off stack logging and delete stack table event = NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL; malloc_memory_event_handler(event); // verify that there are no stacks // First have to clear any cached uniquing table, then check again __mach_stack_logging_stop_reading(mach_task_self()); ret = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_mode); EXPECT_TRUE(ret != KERN_SUCCESS, "return from __mach_stack_logging_start_reading = %d", ret); __mach_stack_logging_stop_reading(mach_task_self()); // now see if we can turn on malloc stack logging via the MSL commands - this should fail event = MEMORYSTATUS_ENABLE_MSL_MALLOC; malloc_memory_event_handler(event); ret = __mach_stack_logging_start_reading(mach_task_self(), __mach_stack_logging_shared_memory_address, &lite_mode); EXPECT_TRUE(ret != KERN_SUCCESS, "return from __mach_stack_logging_start_reading = %d", ret); free(ptrs[0]); } T_DECL(msl_test_enable_disable_msl_malloc_malloc, "Test enabling and disabling msl. malloc:malloc") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_MALLOC, MEMORYSTATUS_ENABLE_MSL_MALLOC, false); } T_DECL(msl_test_enable_disable_msl_vm_vm, "Test enabling and disabling msl. vm:vm") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_VM, MEMORYSTATUS_ENABLE_MSL_VM, true); } T_DECL(msl_test_enable_disable_msl_all, "Test enabling and disabling msl. all:all") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_MALLOC | MEMORYSTATUS_ENABLE_MSL_VM, MEMORYSTATUS_ENABLE_MSL_MALLOC | MEMORYSTATUS_ENABLE_MSL_VM, false); } T_DECL(msl_test_enable_disable_msl_litefull_litefull, "Test enabling and disabling msl. litefull:litefull") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_LITE_FULL, MEMORYSTATUS_ENABLE_MSL_LITE_FULL, false); } T_DECL(msl_test_enable_disable_msl_litefull_malloc, "Test enabling and disabling msl. litefull:malloc") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_LITE_FULL, MEMORYSTATUS_ENABLE_MSL_MALLOC, false); } T_DECL(msl_test_enable_disable_msl_malloc_litefull, "Test enabling and disabling msl. malloc:litefull") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_MALLOC, MEMORYSTATUS_ENABLE_MSL_LITE_FULL, false); } T_DECL(msl_test_enable_disable_msl_litevm_malloc, "Test enabling and disabling msl. litevm:malloc") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_LITE_VM, MEMORYSTATUS_ENABLE_MSL_MALLOC, true); } T_DECL(msl_test_enable_disable_msl_malloc_litevm, "Test enabling and disabling msl. malloc:litevm") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_MALLOC, MEMORYSTATUS_ENABLE_MSL_LITE_VM, false); } T_DECL(msl_test_enable_disable_msl_litevm_litevm, "Test enabling and disabling msl. litevm:litevm") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_LITE_VM, MEMORYSTATUS_ENABLE_MSL_LITE_VM, true); } T_DECL(msl_test_enable_disable_msl_litefull_litevm, "Test enabling and disabling msl. litefull:litevm") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_LITE_FULL, MEMORYSTATUS_ENABLE_MSL_LITE_VM, false); } T_DECL(msl_test_enable_disable_msl_litevm_litefull, "Test enabling and disabling msl. litevm:litefull") { test_enable_disable_enable_msl(MEMORYSTATUS_ENABLE_MSL_LITE_VM, MEMORYSTATUS_ENABLE_MSL_LITE_FULL, true); } #else int main(int argc, const char * argv[]) { boolean_t nano_allocator_enabled = true; boolean_t validate_stacks = false; boolean_t lite_mode_enabled = false; char *nano_zone = getenv("MallocNanoZone"); if (nano_zone) { if (strcmp(nano_zone, "0") == 0) { nano_allocator_enabled = false; } } // get the mode from the environment char *mode = getenv("MallocStackLogging"); if (!mode) { stack_logging_mode_type mode_type = stack_logging_mode_none; mode = getenv("MallocStackLoggingMode"); if (mode) { if (strcmp(mode, "all") == 0) { mode_type = stack_logging_mode_all; validate_stacks = true; } else if (strcmp(mode, "vm") == 0) { mode_type = stack_logging_mode_vm; validate_stacks = false; } else if (strcmp(mode, "malloc") == 0) { mode_type = stack_logging_mode_malloc; validate_stacks = true; } else if (strcmp(mode, "lite") == 0) { mode_type = stack_logging_mode_lite; validate_stacks = true; lite_mode_enabled = true; } else if (strcmp(mode, "none") == 0) { mode_type = stack_logging_mode_none; validate_stacks = false; } } do_test(mode_type, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } else { // stack logging already turned on, so don't pass in a mode to dynamically enable if (strcmp(mode, "lite") == 0) { lite_mode_enabled = true; validate_stacks = true; } else if (strcmp(mode, "vm") == 0) { lite_mode_enabled = false; validate_stacks = false; } else { lite_mode_enabled = false; validate_stacks = true; } do_test(stack_logging_mode_none, validate_stacks, nano_allocator_enabled, lite_mode_enabled); } PAUSE("At end of test. Run leaks now if desired.\n"); return 0; } #endif ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/stress_test.c ================================================ /* * malloc_stress: Stress the heck out of malloc(), and do a lot of * sanity checks that the malloc()'ed buffers are legit. * * usage: malloc_stress [options...] * * Default: Do random-sized malloc() calls until malloc() returns NULL * or some signal kills the process. * Randomly do a free() 10% of the time. * * Options: * -min #bytes malloc() at least #bytes per malloc() call. * -max #bytes malloc() no more than #byte per malloc() call. * -mem #bytes Stop when #bytes has been allocated. * -calls # Stop when #calls to malloc() have been executed. * -time #sec Stop if the number of seconds has elapsed. * -seed # Set the random seed to #. * -free % Randomly free() some % of the time. * -dbg Dump internal structures (could be voluminous!) * * Exits with status code: * 0 PASS * 1 FAIL (plus lots of stdout output) * 99 Illegal arguments or internal error. */ #include #include #include #include #include #include #include #if DARWINTEST #include #endif /* globals */ int rseed; /* initial random seed value */ int min_bytes; /* minimum bytes to malloc */ int max_bytes; /* maximum bytes to malloc */ long long max_mem; /* maximum total memory to allocate */ long long mem_allocated; /* memory allocated so far */ int free_pct; /* call free() this pct of the time */ int max_malloc_calls; /* Maximum number of malloc() calls to make */ int malloc_calls_made; /* Actual count of malloc()'s done */ int malloc_bufs; /* # of active malloc()'ed buffers (calls - freed) */ int time_limit; /* How many seconds should this program run? */ int debug_dump; /* Set to 1 to dump internal structures */ /* Array size for remembering malloc info */ #define MAX_MALLOC_INFO 10000000 /* 10 million */ /* Remember significant info of each malloc() done. */ struct malloc_info { void *buf_ptr; /* the ptr returned by malloc() */ int buf_size; /* count of bytes allocated */ int set_val; /* the value the buffer was set to */ int this_buffer_freed; /* has this buffer been free()'d? */ } minfo_array[MAX_MALLOC_INFO]; /* Generate a random percentage (1-100) */ #define D100 (1 + (rand() % 100)) int signal_happened = 0; /* gets set to signal# if one happens */ void sig_handler(int signo) { signal_happened = signo; return; } void trap_signals() { int signum; for (signum = 1; signum < NSIG; signum++) { if (signal(signum, sig_handler) == SIG_ERR && (signum != SIGKILL && signum != SIGSTOP)) { #if DARWINTEST T_FAIL("Could not trap signal %d (OK)\n", signum); #else printf("INFO: Could not trap signal %d (OK)\n", signum); #endif }; } } /* Display a brief usage message and exit with status 99 */ void usage() { printf("\nusage: malloc_stress [options...]\n"); printf("Default: Allocate up to %d buffers, with a %d percent free() chance.\n", MAX_MALLOC_INFO, free_pct); printf(" Buffer sizes range from %d to %d bytes.\n", min_bytes, max_bytes); printf("\nOptions:\n"); printf(" -min #bytes Minimum buffer size to allocate (at least 1).\n"); printf(" -max #bytes Maximum buffer size (up to 2gb allowed).\n"); printf(" -mem #bytes Stop when total allocations surpasses this.\n"); printf(" -free # Percent chance to free a buffer.\n"); printf(" -calls # Maximum allocations to do, then stop.\n"); printf(" -seed # Set the random seed to this number.\n"); printf(" -dbg Produce some debugging outputs.\n"); exit(99); } /* * summarize(): Give a brief synopsis of the number of malloc() calls made, * free() calls made, total memory allocated. */ void summarize() { int mx; printf("INFO: %d total malloc() calls made.\n", malloc_calls_made); printf("INFO: %d total free() calls made during testing.\n", malloc_calls_made - malloc_bufs); printf("INFO: Total memory allocated: %lld bytes\n", mem_allocated); if (debug_dump) { for (mx = 0; mx < malloc_calls_made; mx++) { printf("Buffer index %d: Address = 0x%llx, Size=%d, Fill=%d ", mx, (unsigned long long)(minfo_array[mx].buf_ptr), minfo_array[mx].buf_size, minfo_array[mx].set_val); if (minfo_array[mx].this_buffer_freed) { printf("[freed]"); } printf("\n"); } } printf("INFO: Random seed value = %d\n", rseed); return; } /* * When we hit an end condition, every allocated buffer should be free()-able. */ void cleanup() { int mx; #ifndef DARWINTEST printf("Cleanup: Started.\n"); printf("Cleanup: Every allocated buffer should be free-able.\n"); #endif for (mx = 0; mx < malloc_calls_made; mx++) { if (!(minfo_array[mx].this_buffer_freed)) { free(minfo_array[mx].buf_ptr); if (signal_happened) { #if DARWINTEST T_FAIL("Signal %d occurred during free(0x%llx)", signal_happened, (unsigned long long)(minfo_array[mx].buf_ptr)); #else printf("FAIL: Signal %d occurred during free(0x%llx)\n", signal_happened, (unsigned long long)(minfo_array[mx].buf_ptr)); exit(1); #endif } } } return; } /* * Take a ptr and size, and another ptr and size, and verify they do not * overlap. Return 0 if they are disjoint, or 1 if they overlap. */ int check_overlap(void *p1, int len1, void *p2, int len2) { unsigned long long bob1, eob1, bob2, eob2; /* make begin and end ptrs in an integer form for easier comparison */ bob1 = (unsigned long long)p1; eob1 = bob1 + (unsigned long long)len1 - 1LL; bob2 = (unsigned long long)p2; eob2 = bob2 + (unsigned long long)len2 - 1LL; /* * The begin and end points of one buffer should not be contained * between the begin and end points of the other buffer. */ if ((bob1 >= bob2 && bob1 <= eob2) || (eob1 >= bob2 && eob1 <= eob2) || (bob2 >= bob1 && bob2 <= eob1) || (eob2 >= bob1 && eob2 <= eob1)) { #ifdef DARWINTEST T_FAIL("Buffers overlap: Buffer 1 (0x%llx, %d bytes), Buffer 2 (0x%llx, %d bytes)", bob1, len1, bob2, len2); #else printf("FAIL: Buffers overlap: Buffer 1 (0x%llx, %d bytes), Buffer 2 (0x%llx, %d bytes)\n", bob1, len1, bob2, len2); #endif return 1; /* Indicate buffers overlap */ } return 0; /* buffers do not overlap */ } void do_test(void); #ifndef DARWINTEST int main(int argc, char *argv[]) { int argx; malloc_calls_made = malloc_bufs = 0; mem_allocated = 0; /* Set defaults */ rseed = (int)time(0); min_bytes = 1; max_bytes = (1024 * 1024); /* 1mb */ max_mem = 0; /* Continue until malloc() returns NULL */ free_pct = 10; /* Do a free() 10% of the time */ max_malloc_calls = MAX_MALLOC_INFO; /* no larger than our array */ time_limit = 0; debug_dump = 0; /* * Process arguments. */ for (argx = 1; argx < argc; argx++) { if (strcmp("-min", argv[argx]) == 0) { min_bytes = atoi(argv[++argx]); } else if (strcmp("-max", argv[argx]) == 0) { max_bytes = atoi(argv[++argx]); } else if (strcmp("-mem", argv[argx]) == 0) { max_mem = atoll(argv[++argx]); } else if (strcmp("-seed", argv[argx]) == 0) { rseed = atoi(argv[++argx]); } else if (strcmp("-free", argv[argx]) == 0) { free_pct = atoi(argv[++argx]); } else if (strcmp("-calls", argv[argx]) == 0) { max_malloc_calls = atoi(argv[++argx]); } else if (strcmp("-time", argv[argx]) == 0) { time_limit = atoi(argv[++argx]); } else if (strcmp("-dbg", argv[argx]) == 0) { debug_dump = 1; } else if (strcmp("-h", argv[argx]) == 0) { usage(); } else { printf("Unknown argument: '%s'\n", argv[argx]); usage(); } } /* Sanity check the arguments */ if (min_bytes < 1) { printf("Minimum bytes (-min %d) must be at least 1.\n", min_bytes); usage(); } if (max_bytes < 1) { printf("Maximum bytes (-max %d) must be at least 1.\n", max_bytes); usage(); } if (min_bytes > max_bytes) { printf("Minimum bytes (-min %d) must not exceed max bytes (-max %d)\n", min_bytes, max_bytes); usage(); } if (free_pct < 0 || free_pct > 100) { printf("Percentage to free (-free %d) must be between 0 and 100.\n", free_pct); usage(); } if (max_malloc_calls < 1) { printf("Maximum malloc calls (-calls %d) must be at least 1.\n", max_malloc_calls); usage(); } if (max_malloc_calls > MAX_MALLOC_INFO) { printf("This program has been compiled for %d max allocations.\n", MAX_MALLOC_INFO); printf("To support more, re-compile malloc_stress.c with a larger MAX_MALLOC_INFO\n"); usage(); } if (time_limit < 0) { printf("The maximum execution time specified (%d seconds) must be 0 or positive.\n", time_limit); usage(); } /* Describe our inputs and actions to be taken */ if (max_malloc_calls) { printf("Will call malloc() up to %d times.\n", max_malloc_calls); } else { printf("Will call malloc() repeatedly until it returns NULL.\n"); } if (min_bytes == max_bytes) { printf("Will allocate buffers all of size %d bytes.\n", min_bytes); } else { printf("Will allocate buffers between %d and %d bytes.\n", min_bytes, max_bytes); } if (free_pct) { printf("Will free() a malloc'ed buffer %d%% of the time.\n", free_pct); } else { printf("free() will NOT be called between malloc's.\n"); } if (time_limit > 0) { printf("Will stop after %d elapsed seconds.\n", time_limit); } srand(rseed); printf("Random seed value = %d\n", rseed); fflush(stdout); do_test(); return 0; } #endif void do_test(void) { time_t start_time = time(0); /* Trap all signals that are possible to trap */ trap_signals(); /* * Loop until we have some reason to quit. */ for (;;) { int buf_size; int bx; char *malloc_buf; int set_buf_val; int save_errno; /* Have we exceeded our execution time limit? */ if (time_limit > 0 && (time(0) - start_time >= time_limit)) { #if DARWINTEST cleanup(); T_PASS("Ran until time limit without incident."); return; #else printf("INFO: Execution time limit has been reached.\n"); summarize(); cleanup(); printf("PASS\n"); exit(0); #endif } /* Is this a good time to free() a buffer? */ if (malloc_bufs > 0 && (D100 < free_pct)) { /* Choose a random malloc'd buffer to free up */ int random_buf; /* Find a currently allocated buffer to free */ for (;;) { random_buf = rand() % malloc_calls_made; if (!(minfo_array[random_buf].this_buffer_freed)) { free(minfo_array[random_buf].buf_ptr); /* If a signal happened, Fail */ if (signal_happened) { #if DARWINTEST T_FAIL("Signal %d caught during free() of buffer 0x%llx\n", signal_happened, (unsigned long long)(minfo_array[random_buf].buf_ptr)); return; #else summarize(); printf("FAIL: Signal %d caught during free() of buffer 0x%llx\n", signal_happened, (unsigned long long)(minfo_array[random_buf].buf_ptr)); exit(1); #endif } minfo_array[random_buf].this_buffer_freed = 1; malloc_bufs--; /* decrease the count of allocated bufs */ if (debug_dump) { printf("INFO: Freed buffer index %d, (%d bytes) at address 0x%llx\n", random_buf, minfo_array[random_buf].buf_size, (unsigned long long)(minfo_array[random_buf].buf_ptr)); } break; } } continue; } /* Else it is a good time to malloc() a buffer */ buf_size = min_bytes + (rand() % (max_bytes - min_bytes + 1)); errno = 0; malloc_buf = malloc(buf_size); save_errno = errno; /* If a signal was caught, summarize and FAIL */ if (signal_happened) { #if DARWINTEST T_FAIL("Signal %d caught during malloc(%d).", signal_happened, buf_size); return; #else summarize(); printf("FAIL: Signal %d caught during malloc()!\n", signal_happened); printf("Buffer size being allocated was %d bytes.\n", buf_size); exit(1); #endif } if (debug_dump) { printf("INFO: Allocated buffer (%d bytes) at address 0x%llx\n", buf_size, (unsigned long long)malloc_buf); } /* Did the malloc() succeed? */ if (malloc_buf == NULL) { /* malloc() returned NULL; make sure it was due to ENOMEM */ #if DARWINTEST T_ASSERT_EQ_INT(save_errno, ENOMEM, "malloc failed, but with errno = %d instead of ENOMEM.", save_errno); cleanup(); T_PASS("Ran out of memory without incident."); return; #else if (save_errno != ENOMEM) { printf("FAIL: malloc failed, but with errno = %d instead of ENOMEM.\n", save_errno); summarize(); exit(1); } summarize(); cleanup(); /* Every malloc()'d buffer should be free()-able */ printf("PASS\n"); exit(0); #endif } /* * Sanity check that this buffer does not overlap with any other * buffer we have previously allocated. NOTE: Be sure to * skip minfo_array elements which have been free()'d previously. */ for (bx = 0; bx < malloc_calls_made; bx++) { /* Skip to the next if this buffer was free()'d before */ if (minfo_array[bx].this_buffer_freed) { continue; } /* * The new buffer should not overlap with any other buffer. */ if (check_overlap(malloc_buf, buf_size, minfo_array[bx].buf_ptr, minfo_array[bx].buf_size)) { #if DARWINTEST return; #else summarize(); printf("FAIL: Allocated buffer overlaps with buffer index %d\n", bx); exit(1); #endif } } /* * Verify that we can read the bytes of the buffer. * Note that malloc() does not (have to) initialize the buffer. * If any signal occurs while reading, FAIL. */ for (bx = 0; bx < buf_size; bx++) { char byte; *((volatile char *)&byte) = *((volatile char *)malloc_buf + bx); if (signal_happened) { #if DARWINTEST T_FAIL("Signal %d caught reading buffer!", signal_happened); return; #else summarize(); printf("FAIL: Signal %d caught reading buffer!\n", signal_happened); exit(1); #endif } } /* * One malloc() requirement is that the buffer should be aligned * such that any numeric type can be stored using the base address, * assuming the buffer is large enough to hold that numeric type. * If the buffer is not well-aligned we might get a signal like * SIGSEGV (segmentation violation) or SIGBUS. * Be sure to do this test BEFORE the memset() operation below. */ if (sizeof(short) <= buf_size) { *((short *)malloc_buf) = 1; } if (sizeof(int) <= buf_size) { *((int *)malloc_buf) = 2; } if (sizeof(long) <= buf_size) { *((long *)malloc_buf) = 3L; } if (sizeof(long long) <= buf_size) { *((long long *)malloc_buf) = 4LL; } if (sizeof(float) <= buf_size) { *((float *)malloc_buf) = 5.0; } if (sizeof(double) <= buf_size) { *((double *)malloc_buf) = 6.1; } if (sizeof(long double) <= buf_size) { *((long double *)malloc_buf) = 7.2; } if (signal_happened) { #if DARWINTEST T_FAIL("Signal %d occurred storing numeric types at address 0x%llx (%d bytes)", signal_happened, (unsigned long long)malloc_buf, buf_size); return; #else printf("\nFAIL: Signal %d occurred storing numeric types at address 0x%llx (%d bytes)\n", signal_happened, (unsigned long long)malloc_buf, buf_size); summarize(); exit(1); #endif } /* Pick a random byte value to set the bytes of the buffer to */ set_buf_val = rand() & 0xFF; memset(malloc_buf, set_buf_val, buf_size); if (signal_happened) { #if DARWINTEST T_FAIL("Signal %d caught initializing buffer to byte value %d!", signal_happened, set_buf_val); return; #else summarize(); printf("FAIL: Signal %d caught initializing buffer to byte value %d!\n", signal_happened, set_buf_val); exit(1); #endif } /* Save the new malloc info */ minfo_array[malloc_calls_made].buf_ptr = malloc_buf; minfo_array[malloc_calls_made].buf_size = buf_size; minfo_array[malloc_calls_made].set_val = set_buf_val; minfo_array[malloc_calls_made].this_buffer_freed = 0; /* * Update counts and sums. Break out if we hit a requested limit. */ malloc_calls_made++; malloc_bufs++; mem_allocated += buf_size; if (malloc_calls_made >= max_malloc_calls) { #if DARWINTEST cleanup(); T_PASS("Maximum malloc calls reached: %d", malloc_calls_made); return; #else printf("Maximum malloc calls reached: %d\n", malloc_calls_made); summarize(); cleanup(); printf("PASS\n"); exit(0); #endif } if (max_mem > 0 && mem_allocated >= max_mem) { #if DARWINTEST cleanup(); T_PASS("Maximum memory allocation reached: %lld", mem_allocated); return; #else printf("Maximum memory allocation reached: %lld\n", mem_allocated); cleanup(); printf("PASS\n"); exit(0); #endif } /* Now verify that no existing buffer has been stepped on */ for (bx = 0; bx < malloc_calls_made; bx++) { int cx; unsigned char *bptr = minfo_array[bx].buf_ptr; int expected_val = minfo_array[bx].set_val; /* A previously free()'d buffer might get re-used */ if (minfo_array[bx].this_buffer_freed) { continue; } for (cx = 0; cx < minfo_array[bx].buf_size; cx++) { if (bptr[cx] != expected_val) { #if DARWINTEST T_FAIL("Allocated buffer [%d] appears to have been stepped on.", bx); return; #else summarize(); printf("FAIL: Allocated buffer [%d] appears to have been stepped on.\n", bx); printf("Buffer address: 0x%llx, bad byte index %d\n", (unsigned long long)bptr, cx); printf("Expected byte value %d, but found %d\n", expected_val, (int)(bptr[cx])); exit(1); #endif } } /* If any signal occurred, that's a FAIL too. */ if (signal_happened) { #if DARWINTEST T_FAIL("Signal %d caught validating buffer contents.\n", signal_happened); return; #else summarize(); printf("FAIL: Signal %d caught validating buffer contents.\n", signal_happened); exit(1); #endif } } /* end then malloc succeeded */ } /* end forever loop, until some end-point is reached */ } #if DARWINTEST static void setup_and_test(void) { /* Set defaults */ rseed = 42; min_bytes = 1; max_bytes = (1024 * 1024); /* 1mb */ max_mem = (512 * 1024 * 1024); /* Continue until malloc() returns NULL */ free_pct = 10; /* Do a free() 10% of the time */ max_malloc_calls = MAX_MALLOC_INFO; /* no larger than our array */ time_limit = 15; debug_dump = 0; do_test(); } T_DECL(malloc_stress, "Stress the heck out of malloc()") { setup_and_test(); } T_DECL(malloc_stress_msl_full, "Stress the heck out of malloc() - enable malloc stack logging, full mode", T_META_ENVVAR("MallocStackLogging=1")) { setup_and_test(); } T_DECL(malloc_stress_msl_malloc, "Stress the heck out of malloc() - enable malloc stack logging, malloc mode", T_META_ENVVAR("MallocStackLogging=malloc")) { setup_and_test(); } T_DECL(malloc_stress_msl_vm, "Stress the heck out of malloc() - enable malloc stack logging, vm mode", T_META_ENVVAR("MallocStackLogging=vm")) { setup_and_test(); } T_DECL(malloc_stress_msl_lite, "Stress the heck out of malloc() - enable malloc stack logging, lite mode", T_META_ENVVAR("MallocStackLogging=lite")) { setup_and_test(); } #endif ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tests/tsan.c ================================================ #include #include #include #include T_DECL(tsan_sanity, "TSan Sanity Check", T_META_CHECK_LEAKS(NO)) { void *tsan_dylib = dlopen("@rpath/libclang_rt.tsan_osx_dynamic.dylib", RTLD_NOLOAD); T_ASSERT_NOTNULL(tsan_dylib, "TSan dylib loaded"); void *ptr = malloc(16); free(ptr); T_PASS("I didn't crash!"); } typedef unsigned long long invisible_barrier_t; void __tsan_testonly_barrier_init(invisible_barrier_t *barrier, unsigned count); void __tsan_testonly_barrier_wait(invisible_barrier_t *barrier); int __tsan_get_report_data(void *report, const char **description, int *count, int *stack_count, int *mop_count, int *loc_count, int *mutex_count, int *thread_count, int *unique_tid_count, void **sleep_trace, unsigned long trace_size); bool tsan_report_hit = false; char *tsan_description = NULL; invisible_barrier_t barrier; const char *__tsan_default_options() { return "abort_on_error=0:exitcode=0"; } void __tsan_on_report(void *report) { tsan_report_hit = true; const char *description; int count; int stack_count, mop_count, loc_count, mutex_count, thread_count, unique_tid_count; void *sleep_trace[16] = {0}; __tsan_get_report_data(report, &description, &count, &stack_count, &mop_count, &loc_count, &mutex_count, &thread_count, &unique_tid_count, sleep_trace, 16); tsan_description = strdup(description); } void *thread1(void *arg) { __tsan_testonly_barrier_wait(&barrier); *((long *)arg) = 42; return NULL; } void *thread2(void *arg) { *((long *)arg) = 43; __tsan_testonly_barrier_wait(&barrier); return NULL; } T_DECL(tsan_data_race_stack, "TSan Detects data-race on stack", T_META_CHECK_LEAKS(NO)) { tsan_description = NULL; tsan_report_hit = false; __tsan_testonly_barrier_init(&barrier, 2); long racy_stack_value = 0; pthread_t t1; pthread_create(&t1, NULL, thread1, &racy_stack_value); pthread_t t2; pthread_create(&t2, NULL, thread2, &racy_stack_value); pthread_join(t2, NULL); pthread_join(t1, NULL); T_EXPECT_EQ(tsan_report_hit, true, "tsan finds data-race"); T_EXPECT_NOTNULL(strstr(tsan_description, "data-race"), "tsan header"); } T_DECL(tsan_data_race_heap, "TSan Detects data-race on heap", T_META_CHECK_LEAKS(NO)) { tsan_description = NULL; tsan_report_hit = false; __tsan_testonly_barrier_init(&barrier, 2); long *racy_heap_value = malloc(sizeof(long)); pthread_t t1; pthread_create(&t1, NULL, thread1, racy_heap_value); pthread_t t2; pthread_create(&t2, NULL, thread2, racy_heap_value); pthread_join(t2, NULL); pthread_join(t1, NULL); T_EXPECT_EQ(tsan_report_hit, true, "tsan finds data-race"); T_EXPECT_NOTNULL(strstr(tsan_description, "data-race"), "tsan header"); } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tools/malloc_replay.cpp ================================================ /* * Copyright (c) 2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include "malloc_replay.h" #include #include #include #include #include #define capture_thread_counters(x, c) \ if (c & (CONFIG_REC_COUNTERS | CONFIG_REC_STATS)) { \ x = thread_instruction_count(); \ } #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR // Maximum size to map when reading replay file chunks #define MAX_REPLAY_FILE_CHUNK_SIZE (100 * 1024 * 1024) #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR static void (*s_funcMagSetThreadIndex)(unsigned int index); extern "C" int thread_selfcounts(int type, void *buf, size_t nbytes); // //Store counter values for each (call, size) tuple. // typedef std::pair CallSizePair; typedef std::vector> ReplayVector; static std::map, ReplayAllocator>> s_counterDistributions; static std::map, ReplayAllocator > > s_addressMap; static uint64_t s_totalEvents = 0; static uint64_t s_totalLibMallocEvents = 0; static uint64_t s_totalMallocEvents = 0; static uint64_t s_totalMalignEvents = 0; static uint64_t s_totalCallocEvents = 0; static uint64_t s_totalReallocEvents = 0; static uint64_t s_totalFreeEvents = 0; static uint64_t s_totalVallocEvents = 0; static uint64_t s_totalFailedFreeEvents = 0; static uint64_t s_totalFailedReallocEvents = 0; uint64_t call_ins_retired[operation_count] = {0}; uint64_t call_count[operation_count] = {0}; static const char *_DefaultFragMetricName = "DefaultZoneFragmentation"; static const char *_DefaultNanoZone = "DefaultMallocZone"; enum { CONFIG_REC_COUNTERS = 1 << 0, CONFIG_REC_STATS = 1 << 1, CONFIG_RUN_REPLAY = 1 << 2, CONFIG_CONVERT_FILE = 1 << 3, CONFIG_PAUSE = 1 << 4, }; typedef uint8_t replay_config_t; // //Our allocator to allocate from a specific zone. // malloc_zone_t* s_zone = NULL; static void configure_ktrace_session(ktrace_session_t s) { ktrace_set_execnames_enabled(s, KTRACE_FEATURE_DISABLED); ktrace_set_walltimes_enabled(s, KTRACE_FEATURE_DISABLED); ktrace_set_uuid_map_enabled(s, KTRACE_FEATURE_DISABLED); ktrace_set_thread_groups_enabled(s, KTRACE_FEATURE_DISABLED); } static uint64_t thread_instruction_count(void) { uint64_t instrCounts[2] = {}; int err; err = thread_selfcounts(1, &instrCounts, sizeof(instrCounts)); return instrCounts[0]; } //////////////////////////////////////////////////////////////////////////////// // // run_ktrace - Takes a nullable input ktrace file path and an output file path. // If the input file is NULL, this will setup a ktrace recording // session targeted at a file in the output file path. If an input // ktrace file path is provided, this will convert the ktrace file // to the compressed mtrace format, targeted at the output file // path. // //////////////////////////////////////////////////////////////////////////////// const int chunk_buffer_size = 16 * 1024 * 1024; typedef union { struct compressed_alloc alloc; struct compressed_calloc calloc; struct compressed_realloc realloc; struct compressed_free free; struct compressed_memalign memalign; } compressed_op_params; static bool run_ktrace(const char* inputFile, const char* outputFile) { __block uint32_t blockBytesWritten = 0; ktrace_file_t output_file = ktrace_file_create(NULL, outputFile); if (!output_file) { printf("Couldn't create output file: %s\n", outputFile); return false; } ktrace_session_t s = ktrace_session_create(); if (inputFile) { if (ktrace_set_file(s, inputFile)) { printf("Couldn't open file: %s\n", inputFile); ktrace_file_close(output_file); ktrace_session_destroy(s); return false; } } else { assert(outputFile); ktrace_set_signal_handler(s); } configure_ktrace_session(s); ktrace_chunk_t events_chunk = ktrace_file_append_start(output_file, MALLOC_EVENTS_TAG, MALLOC_EVENTS_V_MAJOR, MALLOC_EVENTS_V_MINOR); if (!events_chunk) { ktrace_file_close(output_file); ktrace_session_destroy(s); return false; } void *buffer = malloc(chunk_buffer_size); if (!buffer) { printf("Could not allocate buffer for events\n"); ktrace_file_close(output_file); ktrace_session_destroy(s); return false; } __block void *next_ptr = buffer; __block size_t space_left = chunk_buffer_size; dispatch_group_t g = dispatch_group_create(); dispatch_queue_t q = dispatch_queue_create("Read Source File", DISPATCH_QUEUE_SERIAL); ktrace_events_subclass(s, DBG_UMALLOC, DBG_UMALLOC_EXTERNAL, (^(ktrace_event_t event) { s_totalEvents++; if (space_left < sizeof(compressed_operation) + sizeof(compressed_op_params)) { ktrace_file_append_data(output_file, events_chunk, buffer, chunk_buffer_size - space_left); blockBytesWritten += chunk_buffer_size - space_left; next_ptr = buffer; space_left = chunk_buffer_size; } unsigned int debugid = event->debugid; size_t entry_size = 0; struct compressed_operation *operation = (struct compressed_operation *)next_ptr; operation->core = (uint8_t)event->cpuid; operation->opcode = (uint8_t)KDBG_EXTRACT_CODE(debugid); switch (debugid) { case TRACE_malloc|DBG_FUNC_END: case TRACE_valloc|DBG_FUNC_END: { s_totalLibMallocEvents++; s_totalMallocEvents++; struct compressed_alloc *allocp = (struct compressed_alloc *)operation->body; allocp->size = (uint32_t)event->arg2; allocp->address = event->arg3; entry_size = sizeof(compressed_operation) + sizeof(struct compressed_alloc); break; } case TRACE_calloc|DBG_FUNC_END: { s_totalLibMallocEvents++; s_totalCallocEvents++; struct compressed_calloc *callocp = (struct compressed_calloc *)operation->body; callocp->count = (uint32_t)event->arg2; callocp->size = (uint32_t)event->arg3; callocp->address = event->arg4; entry_size = sizeof(compressed_operation) + sizeof(struct compressed_calloc); break; } case TRACE_memalign|DBG_FUNC_END: { s_totalLibMallocEvents++; s_totalMalignEvents++; struct compressed_memalign *malignp = (struct compressed_memalign *)operation->body; malignp->alignment = (uint32_t)event->arg2; malignp->size = (uint32_t)event->arg3; malignp->address = event->arg4; entry_size = sizeof(compressed_operation) + sizeof(struct compressed_memalign); break; } case TRACE_realloc|DBG_FUNC_END: { s_totalLibMallocEvents++; s_totalReallocEvents++; struct compressed_realloc *reallocp = (struct compressed_realloc *)operation->body; reallocp->oldAddress = event->arg2; reallocp->size = (uint32_t)event->arg3; reallocp->newAddress = event->arg4; entry_size = sizeof(compressed_operation) + sizeof(struct compressed_realloc); break; } case TRACE_free: { s_totalLibMallocEvents++; s_totalFreeEvents++; struct compressed_free *freep = (struct compressed_free *)operation->body; freep->address = event->arg2; entry_size = sizeof(compressed_operation) + sizeof(struct compressed_free); break; } } if (entry_size) { next_ptr = (char *)next_ptr + entry_size; space_left -= entry_size; } })); ktrace_set_completion_handler(s, ^{ dispatch_group_leave(g); }); dispatch_group_enter(g); if (!ktrace_start(s, q)) { dispatch_group_wait(g, DISPATCH_TIME_FOREVER); } else { dispatch_group_leave(g); } dispatch_release(g); dispatch_release(q); // Write out any remaining data if (space_left < chunk_buffer_size) { ktrace_file_append_data(output_file, events_chunk, buffer, chunk_buffer_size - space_left); blockBytesWritten += chunk_buffer_size - space_left; } free(buffer); if (ktrace_file_append_finish(output_file, events_chunk)) { printf("Failed to write events to %s\n", outputFile); } ktrace_file_close(output_file); ktrace_session_destroy(s); // //Dump out data about how many events we saw. // printf("TotalMalloc: %16llu\n" "TotalCalloc: %16llu\n" "TotalRealloc: %16llu\n" "TotalMalign: %16llu\n" "TotalFree: %16llu\n" "\n" "TotalEvents: %16llu\n" "TotalLibMalloc: %16llu\n" "\n" "TotalBytesWritten: %16u\n", s_totalMallocEvents, s_totalCallocEvents, s_totalReallocEvents, s_totalMalignEvents, s_totalFreeEvents, s_totalEvents, s_totalLibMallocEvents, blockBytesWritten ); return true; } //////////////////////////////////////////////////////////////////////////////// // // dirty_memory - Writes the minimum number of bytes to dirty a range of memory. // //////////////////////////////////////////////////////////////////////////////// static void dirty_memory(uint8_t* memory, size_t size) { *memory = 0xFF; uint8_t* current = (uint8_t*)round_page_kernel((uint64_t)memory); size_t good_size = malloc_good_size(size); while (current < (memory + good_size)) { *current = 0xFF; current += vm_kernel_page_size; } } //////////////////////////////////////////////////////////////////////////////// // // run_event - Decodes an operation into its actual event type and then calls the // proper libmalloc function. Returns the size of the event type so // so the caller can move to the next compressed_operation. // //////////////////////////////////////////////////////////////////////////////// static size_t run_event(const struct compressed_operation* currentOperation, size_t remainingMapping, replay_config_t config) { void* event = (void *)currentOperation->body; size_t bytesRead = sizeof(compressed_operation); remainingMapping -= sizeof(compressed_operation); if (s_funcMagSetThreadIndex){ s_funcMagSetThreadIndex(currentOperation->core); } uint64_t preICount = 0; uint64_t postICount = 0; uint32_t reqAllocSize = 0; //printf("EVENT : %llx\n", event); switch (currentOperation->opcode) { case op_malloc: { if (remainingMapping < sizeof(struct compressed_alloc)) { return 0; } struct compressed_alloc* alloc = (struct compressed_alloc*)event; reqAllocSize = alloc->size; capture_thread_counters(preICount, config); uint64_t* allocation = (uint64_t*)malloc(alloc->size); capture_thread_counters(postICount, config); os_assert(allocation); dirty_memory((uint8_t*)allocation, alloc->size); s_addressMap.insert(std::make_pair(alloc->address, (uint64_t)allocation)); s_totalMallocEvents++; bytesRead += sizeof(struct compressed_alloc); break; } case op_calloc: { if (remainingMapping < sizeof(struct compressed_calloc)) { return 0; } struct compressed_calloc* alloc = (struct compressed_calloc*)event; reqAllocSize = alloc->size * alloc->count; capture_thread_counters(preICount, config); uint64_t allocation = (uint64_t)calloc(alloc->count, alloc->size); capture_thread_counters(postICount, config); os_assert(allocation); dirty_memory((uint8_t*)allocation, alloc->size * alloc->count); s_addressMap.insert(std::make_pair(alloc->address, allocation)); s_totalCallocEvents++; bytesRead += sizeof(struct compressed_calloc); break; } case op_memalign: { if (remainingMapping < sizeof(struct compressed_memalign)) { return 0; } struct compressed_memalign* alloc = (struct compressed_memalign*)event; reqAllocSize = alloc->size; uint64_t allocation = 0; capture_thread_counters(preICount, config); posix_memalign((void**)&allocation, alloc->alignment, alloc->size); capture_thread_counters(postICount, config); os_assert(allocation); dirty_memory((uint8_t*)allocation, alloc->size); s_addressMap.insert(std::make_pair(alloc->address, allocation)); s_totalMalignEvents++; bytesRead += sizeof(struct compressed_memalign); break; } case op_valloc: { if (remainingMapping < sizeof(struct compressed_alloc)) { return 0; } struct compressed_alloc* alloc = (struct compressed_alloc*)event; reqAllocSize = alloc->size; capture_thread_counters(preICount, config); uint64_t allocation = (uint64_t)valloc(alloc->size); capture_thread_counters(postICount, config); os_assert(allocation); dirty_memory((uint8_t*)allocation, alloc->size); s_addressMap.insert(std::make_pair(alloc->address, allocation)); s_totalVallocEvents++; bytesRead += sizeof(struct compressed_alloc); break; } case op_free: { if (remainingMapping < sizeof(struct compressed_free)) { return 0; } bytesRead += sizeof(struct compressed_free); struct compressed_free* freed = (struct compressed_free*)event; auto iter = s_addressMap.find(freed->address); if (iter == s_addressMap.end()) { s_totalFailedFreeEvents++; break; } capture_thread_counters(preICount, config); free((void*)iter->second); capture_thread_counters(postICount, config); s_addressMap.erase(iter); s_totalFreeEvents++; break; } case op_realloc: { if (remainingMapping < sizeof(struct compressed_realloc)) { return 0; } bytesRead += sizeof(struct compressed_realloc); struct compressed_realloc* alloc = (struct compressed_realloc*)event; reqAllocSize = alloc->size; auto iter = s_addressMap.find(alloc->oldAddress); if (iter == s_addressMap.end()) { s_totalFailedReallocEvents++; break; } uint64_t oldAddress = iter->second; capture_thread_counters(preICount, config); uint64_t newAddress = (uint64_t)realloc((void*)oldAddress, alloc->size); capture_thread_counters(postICount, config); os_assert(newAddress); dirty_memory((uint8_t*)newAddress, alloc->size); s_addressMap.erase(iter); s_addressMap.insert(std::make_pair(alloc->newAddress, newAddress)); s_totalReallocEvents++; break; } default: __builtin_trap(); break; }; if (config & (CONFIG_REC_COUNTERS | CONFIG_REC_STATS)) { uint64_t diff = postICount - preICount; uint16_t instrCount = diff <= UINT16_MAX ? diff : UINT16_MAX; if (config & CONFIG_REC_STATS) { call_ins_retired[currentOperation->opcode - 1] += instrCount; call_count[currentOperation->opcode - 1]++; } if ((config & CONFIG_REC_COUNTERS) && reqAllocSize > 0) { auto lookup = CallSizePair(currentOperation->opcode, reqAllocSize); auto iter = s_counterDistributions.find(lookup); if (iter != s_counterDistributions.end()) { iter->second.push_back(instrCount); } else { s_counterDistributions.insert({lookup, ReplayVector(1, instrCount)}); } } } return bytesRead; } //////////////////////////////////////////////////////////////////////////////// // // setup_private_malloc_zone - Creates a malloc zone for use during actual replay. // We need to do so in order to keep the bookkeeping // separate from the replayed data. This zone is not // counted when figuring out fragmentation. // //////////////////////////////////////////////////////////////////////////////// static bool setup_private_malloc_zone() { s_zone = malloc_create_zone(0, 0); if (!s_zone) { printf("Couldn't create zone\n"); return false; } malloc_set_zone_name(s_zone, "IGNORE_THIS_ZONE"); return true; } //////////////////////////////////////////////////////////////////////////////// // // memory_reader - Read from ourselves, instead of a remote process like vmmap // does. // //////////////////////////////////////////////////////////////////////////////// static kern_return_t memory_reader(task_t remote_task, vm_address_t remote_address, vm_size_t size, void **local_memory) { if (local_memory) { *local_memory = (void*)remote_address; return KERN_SUCCESS; } return KERN_FAILURE; } //////////////////////////////////////////////////////////////////////////////// // // vm_range_recorder - Enumerate all the malloc vm ranges, looking at each page // to figure out if it is resident or not, and dirty or not. // Used to calculate fragmentation. // //////////////////////////////////////////////////////////////////////////////// static void vm_range_recorder(task_t task, void* context, unsigned type, vm_range_t *ranges, unsigned count) { for (unsigned currentRange = 0; currentRange < count; currentRange++ ) { replay_malloc_magazine magazine = { .baseAddress = ranges[currentRange].address, .extent = ranges[currentRange].address + ranges[currentRange].size }; for (uint64_t i = magazine.baseAddress; i < magazine.extent; i += vm_kernel_page_size) { kern_return_t err = 0; integer_t disposition = 0; integer_t refCount = 0; err = mach_vm_page_query(mach_task_self(), i, &disposition, &refCount); if (!err) { if (disposition & VM_PAGE_QUERY_PAGE_PRESENT) { if (disposition & (VM_PAGE_QUERY_PAGE_COPIED|VM_PAGE_QUERY_PAGE_DIRTY)) { magazine.pages_dirty++; } magazine.pages_resident++; } else if (disposition & VM_PAGE_QUERY_PAGE_PAGED_OUT){ magazine.pages_dirty++; magazine.pages_resident++; } } } ((replay_malloc_zone_t)context)->magazines.push_back(magazine); } } //////////////////////////////////////////////////////////////////////////////// // // run_malloc_replay - Replay a compressed malloc trace. The idea here is to replay // the recorded events while forcing a specific CPU. By doing // so libmalloc will target a specific magazine. This way we // can see how the current allocator would pack an old allocation // stream. // //////////////////////////////////////////////////////////////////////////////// static bool run_malloc_replay(const char* fileName, pdwriter_t perfDataWriter, replay_config_t config) { if (!setup_private_malloc_zone()) { return false; } ktrace_session_t s = ktrace_session_create(); if (ktrace_set_file(s, fileName)) { printf("Couldn't open file: %s\n", fileName); ktrace_session_destroy(s); return false; } configure_ktrace_session(s); dispatch_group_t g = dispatch_group_create(); dispatch_queue_t q = dispatch_queue_create("Read Malloc Trace File", DISPATCH_QUEUE_SERIAL); ktrace_chunks(s, MALLOC_EVENTS_TAG, ^(ktrace_chunk_t c) { if (ktrace_chunk_version_major(c) != MALLOC_EVENTS_V_MAJOR || ktrace_chunk_version_minor(c) != MALLOC_EVENTS_V_MINOR) { printf("Invalid replay file: %s\n", fileName); exit(1); } size_t size = (size_t)ktrace_chunk_size(c); off_t offset = 0; while (size > sizeof(compressed_operation)) { void *ptr; size_t mapped_size = size; #ifdef MAX_REPLAY_FILE_CHUNK_SIZE mapped_size = MIN(mapped_size, MAX_REPLAY_FILE_CHUNK_SIZE); #endif // MAX_REPLAY_FILE_CHUNK_SIZE // Map as much of the chunk as we can. If we can't map everything, // keep halving the requested size until we get to something that // works. If nothing works, bail. do { ptr = ktrace_chunk_map_data(c, offset, mapped_size); if (!ptr) { mapped_size /= 2; } } while (!ptr && mapped_size); if (!mapped_size) { perror("Could not map replay file chunk"); exit(1); } struct compressed_operation* event = (struct compressed_operation*)ptr; size_t size_left = mapped_size; do { size_t read = run_event(event, size_left, config); if (read == 0) { break; } s_totalLibMallocEvents++; size_left -= read; size -= read; offset += read; event = (struct compressed_operation*)((char *)event + read); } while (size_left > sizeof(compressed_operation)); ktrace_chunk_unmap_data(c, ptr, mapped_size); } }); ktrace_set_completion_handler(s, ^{ dispatch_group_leave(g); }); dispatch_group_enter(g); ktrace_events_all(s, ^(ktrace_event_t event) { }); if (!ktrace_start(s, q)) { dispatch_group_wait(g, DISPATCH_TIME_FOREVER); } else { dispatch_group_leave(g); } dispatch_release(g); dispatch_release(q); s_addressMap.clear(); ktrace_session_destroy(s); // //If passed a writer, output performance data. // if (perfDataWriter) { pdwriter_new_value(perfDataWriter, "TotalMalloc", PDUNIT_CUSTOM(totalmalloc), s_totalMallocEvents); pdwriter_new_value(perfDataWriter, "TotalCalloc", PDUNIT_CUSTOM(totalcalloc), s_totalCallocEvents); pdwriter_new_value(perfDataWriter, "TotalRealloc", PDUNIT_CUSTOM(totalrealloc), s_totalReallocEvents); pdwriter_new_value(perfDataWriter, "TotalValloc", PDUNIT_CUSTOM(totalvalloc), s_totalVallocEvents); pdwriter_new_value(perfDataWriter, "TotalMalign", PDUNIT_CUSTOM(totalmalign), s_totalMalignEvents); pdwriter_new_value(perfDataWriter, "TotalFree", PDUNIT_CUSTOM(totalfree), s_totalFreeEvents); pdwriter_new_value(perfDataWriter, "FailedRealloc", PDUNIT_CUSTOM(failedrealloc), s_totalFailedReallocEvents); pdwriter_new_value(perfDataWriter, "FailedFree", PDUNIT_CUSTOM(failedfree), s_totalFailedFreeEvents); } printf("TotalMalloc: %16llu\n" "TotalCalloc: %16llu\n" "TotalRealloc: %16llu\n" "TotalValloc: %16llu\n" "TotalMalign: %16llu\n" "TotalFree: %16llu\n" "\n" "FailedRealloc: %16llu\n" "FailedFree: %16llu\n", s_totalMallocEvents, s_totalCallocEvents, s_totalReallocEvents, s_totalVallocEvents, s_totalMalignEvents, s_totalFreeEvents, s_totalFailedReallocEvents, s_totalFailedFreeEvents ); // //Now lets go over the data and find how fragmented we are. // vm_address_t* addresses = NULL; unsigned count = 0; printf("\n\n\n"); printf("Zone: BytesDirty BytesInUse %%Frag\n"); printf("===========================================================\n"); double defaultFrag = 0; malloc_get_all_zones(mach_task_self(), memory_reader, &addresses, &count); for (unsigned i = 0; i < count; i++) { malloc_zone_t* zone = (malloc_zone_t*)addresses[i]; replay_malloc_zone zoneInfo = { 0 }; if (strcmp(zone->zone_name, "IGNORE_THIS_ZONE") != 0) { malloc_statistics_t stats = {0}; zone->introspect->enumerator(mach_task_self(), &zoneInfo, MALLOC_PTR_REGION_RANGE_TYPE, (vm_address_t)zone, memory_reader, vm_range_recorder); zone->introspect->statistics(zone, &stats); uint64_t bytesDirty = 0; for (const auto& magazine : zoneInfo.magazines) { bytesDirty += magazine.pages_dirty * vm_kernel_page_size; //printf("%llx %llx %d\n", magazine.baseAddress, magazine.extent, magazine.pages_dirty); } double frag = (bytesDirty && (stats.size_in_use < bytesDirty)) ? 100 - (100.0 * stats.size_in_use)/bytesDirty : 0; printf("%20s %14llu %14lu %6.2f\n", zone->zone_name, bytesDirty, stats.size_in_use, frag); if (perfDataWriter) { pdwriter_new_value(perfDataWriter, "BytesDirty", pdunit_bytes, bytesDirty); pdwriter_record_variable_str(perfDataWriter, "ZoneName", zone->zone_name); pdwriter_new_value(perfDataWriter, "BytesInUse", pdunit_bytes, stats.size_in_use); pdwriter_record_variable_str(perfDataWriter, "ZoneName", zone->zone_name); pdwriter_new_value(perfDataWriter, "Fragmentation", PDUNIT_CUSTOM(FragmentedPercent), frag); pdwriter_record_variable_str(perfDataWriter, "ZoneName", zone->zone_name); if (strcmp(zone->zone_name, _DefaultNanoZone) == 0) { defaultFrag = frag; } } } } if (perfDataWriter) { // //Write out the fragmentation in DefaultMallocZone as a primary metric. // pdwriter_new_value(perfDataWriter, _DefaultFragMetricName, PDUNIT_CUSTOM(FragmentedPercent), defaultFrag); pdwriter_record_variable(perfDataWriter, kPCFailureThresholdPctVar, 10); } else if (config & CONFIG_REC_STATS){ printf("\n\n\n"); printf("Call Cycles (mean)\n"); printf("=====================\n"); } // //If we were asked to gather instruction counts, iterate through them and //either output the mean for the call or the raw counts for each //call:requested-size pair. // if (config & (CONFIG_REC_COUNTERS | CONFIG_REC_STATS)) { json_t jsonW = NULL; if (perfDataWriter && (config & CONFIG_REC_COUNTERS)) { // //Write out the instruction count data. We record into an extension //since there's typically a large numbers of counts. // jsonW = pdwriter_start_extension(perfDataWriter, "libmalloc.instruction_counts"); if (jsonW) { for (auto const &mCallDistribution : s_counterDistributions) { // //If requested, write the i-counts out to the perfdata. // char description[16]; snprintf(description, sizeof(description), "%d:%u", mCallDistribution.first.first, mCallDistribution.first.second); json_member_start_object(jsonW, description); json_member_int(jsonW, "call", mCallDistribution.first.first); json_member_int(jsonW, "size", mCallDistribution.first.second); json_member_uint(jsonW, "count", (unsigned int)mCallDistribution.second.size()); json_member_start_array(jsonW, "values"); for (uint64_t val : mCallDistribution.second) { json_value_uint(jsonW, (unsigned int)val); } json_end_array(jsonW); // Inner counts json_end_object(jsonW); } } } // //Output the mean number of instructions retired. // if (config & CONFIG_REC_STATS) { for (int i = 0; i < operation_count; i++) { if (call_ins_retired[i] > 0 && call_count[i] > 0) { uint64_t mean = call_ins_retired[i] / call_count[i]; if (perfDataWriter) { char full_name[16]; // operation enum is indexed from 1, adjust index for mcall_to_name. snprintf(full_name, sizeof(full_name), "%s-mean", mcall_to_name(i + 1)); pdwriter_new_value(perfDataWriter, full_name, pdunit_instructions, mean); pdwriter_record_variable(perfDataWriter, kPCFailureThresholdPctVar, 100); } else { printf("%9s %6llu\n", mcall_to_name(i + 1), mean); } } } } if (jsonW) { pdwriter_end_extension(perfDataWriter, jsonW); } } return true; } //////////////////////////////////////////////////////////////////////////////// // // usage - Output help. // //////////////////////////////////////////////////////////////////////////////// static void usage() { printf("libmalloc_replay -r [-p] [-j filename] [-t testname] [-c | -s]\n"); printf("libmalloc_replay [-i ] -o [-p]\n"); printf("\t-p Pause the replay process before exit\n"); printf("\t-j \toutput perfdata V2 formatted file\n"); printf("\t-t \tset the test name for the perfdata V2 formatted output file\n"); printf("\t-c capture and output instruction counts along with the performance data.\n"); printf("\t-s capture and output instruction count statistics along with the performance data.\n"); } //////////////////////////////////////////////////////////////////////////////// // // main - Yep. // //////////////////////////////////////////////////////////////////////////////// int main(int argc, char** argv) { char * inputMTrace = NULL; char * inputKtrace = NULL; char * outputMTrace = NULL; char * outputPerfData = NULL; char * outputTestName = NULL; replay_config_t config = 0; int c = 0; if (argc < 2) { usage(); return -1; } while ((c = getopt(argc, (char* const*)argv, "phr:i:o:j:t:cs")) != -1) { switch (c) { case 'r': inputMTrace = strdup(optarg); config |= CONFIG_RUN_REPLAY; break; case 'i': inputKtrace = strdup(optarg); if (inputKtrace && outputMTrace) { config |= CONFIG_CONVERT_FILE; } break; case 'o': outputMTrace = strdup(optarg); if (inputKtrace && outputMTrace) { config |= CONFIG_CONVERT_FILE; } break; case 'p': config |= CONFIG_PAUSE; break; case 'j': outputPerfData = strdup(optarg); break; case 't': outputTestName = strdup(optarg); break; case 'c': config |= CONFIG_REC_COUNTERS; break; case 's': config |= CONFIG_REC_STATS; break; case 'h': default: usage(); return EX_USAGE; } } if ((config & CONFIG_REC_COUNTERS) && (config & CONFIG_REC_STATS)) { printf("Invalid usage: -c and -s\n"); usage(); return EX_USAGE; } timespec beginTime = {0}; pdwriter_t writer = NULL; if (outputPerfData) { char dataPath[MAXPATHLEN]; // //Ensure the filename is prepended with libmalloc // const char *prepend = "libmalloc"; auto outputFilePath = std::string(outputPerfData); const auto namePos = outputFilePath.find_last_of('/') + 1; if (outputFilePath.find(prepend, namePos) != namePos) { outputFilePath.insert(namePos, prepend); } int ret = snprintf(dataPath, sizeof(dataPath), "%s.%d.%llx." PD_FILE_EXT, outputFilePath.c_str(), getpid(), mach_absolute_time()); if (ret < 0) { return errno; } auto perfdataName = std::string("libmalloc.replay."); perfdataName += outputTestName ? outputTestName : dataPath; writer = pdwriter_open(dataPath, perfdataName.c_str(), 0, 0); if (!writer) { printf("\n****Couldn't open writer for performance data file. Error: %s\n", strerror(errno)); } else { pdwriter_set_primary_metric(writer, _DefaultFragMetricName); } } if (config & CONFIG_RUN_REPLAY) { void *libmalloc = dlopen("/usr/lib/system/libsystem_malloc.dylib", RTLD_NOW); if (libmalloc) { s_funcMagSetThreadIndex = (void (*)(unsigned int))dlsym(libmalloc, "mag_set_thread_index"); } if (!s_funcMagSetThreadIndex) { printf("\n****Couldn't load mag_set_thread_index, replay won't honor core****\n\n"); } clock_gettime(CLOCK_MONOTONIC_RAW, &beginTime); if (!run_malloc_replay(inputMTrace, writer, config)) { return -1; } } else if (config & CONFIG_CONVERT_FILE) { clock_gettime(CLOCK_MONOTONIC_RAW, &beginTime); if (!run_ktrace(inputKtrace, outputMTrace)) { printf("\n****Couldn't record mtrace file.\n"); } } else if (outputMTrace) { clock_gettime(CLOCK_MONOTONIC_RAW, &beginTime); if (!run_ktrace(NULL, outputMTrace)) { printf("\n****Couldn't record mtrace file.\n"); } } if (beginTime.tv_sec) { timespec endTime; clock_gettime(CLOCK_MONOTONIC_RAW, &endTime); printf("\n\nRuntime: %ld ms\n", ((endTime.tv_sec - beginTime.tv_sec) * 1000) + (endTime.tv_nsec - beginTime.tv_nsec)/1000000); } if (writer) { pdwriter_close(writer); } if (config & CONFIG_PAUSE) { printf("\n\nProcess paused, hit Crtl+C to exit\n"); pause(); } return 0; } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tools/malloc_replay.h ================================================ /* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __MALLOC_REPLAY_H #define __MALLOC_REPLAY_H #include "trace.h" #include // //Our file format // // Definitions for the event chunk. #define MALLOC_EVENTS_TAG (uint32_t)0xe001e001 #define MALLOC_EVENTS_V_MAJOR 1 #define MALLOC_EVENTS_V_MINOR 1 enum operation { op_malloc = 0x01, op_free = 0x02, op_realloc = 0x03, op_memalign = 0x04, op_calloc = 0x05, op_valloc = 0x06, }; static const int operation_count = op_valloc; static const char *mcall_names[] = {"malloc", "free", "realloc", "memalign", "calloc", "valloc"}; static inline const char * mcall_to_name(int call_num) { if (call_num > 0 && call_num <= operation_count) { return mcall_names[call_num - 1]; } return NULL; } enum flags { flag_stacks = 0x00000001, flag_timestamps = 0x00000002 }; struct compressed_header { uint16_t version; uint64_t flags; } __attribute__((packed)); struct compressed_operation { uint8_t opcode; uint8_t core; uint32_t body[]; }__attribute__((packed)); struct compressed_alloc { uint64_t address; uint32_t size; } __attribute__((packed)); struct compressed_calloc { uint64_t address; uint32_t count; uint32_t size; } __attribute__((packed)); struct compressed_memalign { uint64_t address; uint32_t alignment; uint32_t size; } __attribute__((packed)); struct compressed_free { uint64_t address; } __attribute__((packed)); struct compressed_realloc { uint64_t oldAddress; uint64_t newAddress; uint32_t size; } __attribute__((packed)); struct compressed_stack_key { uint64_t stackKey; } __attribute__((packed)); struct compressed_time { uint64_t timestamp; } __attribute__((packed)); // //Our allocator to allocate from a specific zone. // extern malloc_zone_t* s_zone; template class ReplayAllocator { public: // type definitions typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; // rebind allocator to type U template struct rebind { typedef ReplayAllocator other; }; // return address of values pointer address (reference value) const { return &value; } const_pointer address (const_reference value) const { return &value; } /* constructors and destructor * - nothing to do because the allocator has no state */ ReplayAllocator() throw() { } ReplayAllocator(const ReplayAllocator&) throw() { } template ReplayAllocator (const ReplayAllocator&) throw() { } ~ReplayAllocator() throw() { } // return maximum number of elements that can be allocated size_type max_size () const throw() { return std::numeric_limits::max() / sizeof(T); } // allocate but don't initialize num elements of type T pointer allocate (size_type num, const void* = 0) { return (pointer)malloc_zone_malloc(s_zone, num * sizeof(T)); } // initialize elements of allocated storage p with value value void construct (pointer p, const T& value) { // initialize memory with placement new new((void*)p)T(value); } // destroy elements of initialized storage p void destroy (pointer p) { // destroy objects by calling their destructor p->~T(); } // deallocate storage p of deleted elements void deallocate (pointer p, size_type num) { malloc_zone_free(s_zone, p); } }; template bool operator== (const ReplayAllocator&, const ReplayAllocator&) throw() { return true; } template bool operator!= (const ReplayAllocator&, const ReplayAllocator&) throw() { return false; } typedef struct replay_malloc_magazine { uint64_t baseAddress; uint64_t extent; uint32_t pages_resident; uint32_t pages_dirty; } *replay_malloc_magazine_t; typedef struct replay_malloc_zone { const char* name; std::vector > magazines; } *replay_malloc_zone_t; #endif // __MALLOC_REPLAY_H ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tools/malloc_replay_plotter.py ================================================ #!/usr/bin/env python from __future__ import absolute_import from __future__ import unicode_literals from __future__ import division from __future__ import print_function import sys import os import re import argparse import logging import json from pprint import pprint import numpy as np import matplotlib.pyplot as plt class ReportConfiguration(object): def __init__(self, report_type, call, nano_malloc_cutoff, xfilter, num_bins, merge_calloc, fileV1, fileV2): self.report_type = report_type self.call = call self.nano_malloc_cutoff = nano_malloc_cutoff self.xfilter = xfilter self.num_bins = num_bins self.merge_calloc = merge_calloc self.fileV1 = fileV1 self.fileV2 = fileV2 def plotter_class(self): if self.report_type == "scatter": return ScatterPlotter if self.report_type == "instructions": return InstructionsPlotter if self.report_type == "request_sizes": return RequestSizePlotter if self.report_type == "nano_request_bins": return RequestSizePlotter if self.report_type == "nano_request_bins_ysize": return RequestSizePlotter def call_identifier(self): return self.call_identifier_for_name(self.call) @classmethod def call_identifier_for_name(cls, name): mapping = {'malloc': 1, 'realloc': 3, 'memalign': 4, 'calloc': 5, 'valloc': 6} return mapping[name] @classmethod def configuration_for_arguments(cls, args): return cls(args.report_type, args.call, args.nano_malloc_cutoff, args.xfilter, args.num_bins, args.merge_calloc, args.fileV1, args.fileV2) class ReportData(object): def __init__(self, fileV1, fileV2): self.fileV1 = fileV1 self.fileV2 = fileV2 self.all_data = [] self.frag = [] self.paths = [fileV1, fileV2] self.parse_input_files() def parse_input_files(self): with open(self.fileV1) as f: self.all_data.append(json.load(f)) if self.fileV2: with open(self.fileV2) as f: self.all_data.append(json.load(f)) self.calculate_fragmentation() def enumerate(self): for i, data in enumerate(self.all_data): yield i, data, self.frag[i], self.paths[i] def fileV1_data(self): return self.all_data[0] def num_plots(self): return 2 if self.fileV1 and self.fileV2 else 1 def calculate_fragmentation(self): for data in self.all_data: total_frag = 0 data = data['data'] for obj in data: for i in obj: if 'variables' in i: if i['metric'] == 'Fragmentation': total_frag += i['value'] self.frag.append(total_frag) class Plotter(object): def __init__(self, report_configuration): self.configuration = report_configuration def plot(self, report_data): pass # Returns a list of sizes requested and the frequency at which this request # was made. def size_freq_for_data(self, data, call_identifier): size_filter = self.configuration.nano_malloc_cutoff if not size_filter: size_filter = size.maxint size_freq = [] for ext, counts in data['extensions']['libmalloc.instruction_counts'].items(): if counts['call'] == call_identifier and int(counts['size']) <= size_filter: size_freq.append([counts['size'], counts['count']]) return size_freq # Returns a list of lists of ([size, [instruction counts]]). Where size is the # requested size and instruction counts are the number of CPU instructions it took (as # recorded by libmalloc_replay. If coalesce is set, this instead returns a # coalesced list of instruction counts ([instruction counts]), flattened across all # request sizes. def times_for_data(self, data, call_identifier, coalesce): size_filter = self.configuration.nano_malloc_cutoff if not size_filter: size_filter = size.maxint times = [] for ext, counts in data['extensions']['libmalloc.instruction_counts'].items(): if counts['call'] == call_identifier and int(counts['size']) <= size_filter: if coalesce: times += counts['values'] else: times.append([counts['size'], counts['values']]) return times def show(self): plt.show() def write_to_path(self, path): plt.savefig(path) class ScatterPlotter(Plotter): def plot(self, report_data): plt.figure(figsize=(20,10)) labels = ["V1", "V2"] colours = ['r', 'b'] for i, data, _, _ in report_data.enumerate(): logging.debug("Building data") sizecounts = self.times_for_data(data, self.configuration.call_identifier(), False) sizes = [] counts = [] for pair in sizecounts: rsize = pair[0] for icount in pair[1]: sizes.append(rsize) counts.append(icount) colmark = colours[i] + 'x' logging.debug("Plotting scatter") scatter, = plt.plot(sizes, counts, colmark) scatter.set_label(labels[i]) plt.xlabel("Requested Size (Bytes)") plt.ylabel("Instruction Count") plt.legend() class InstructionsPlotter(Plotter): def plot(self, report_data): num_plots = report_data.num_plots() fig = plt.figure(figsize=(20, num_plots * 5)) subplot_config = 221 if num_plots == 2 else 121 for i, data, fragmentation, path in report_data.enumerate(): all_times = self.times_for_data(data, self.configuration.call_identifier(), True) # We may want to just filter for a certain range (0, xfilter) if self.configuration.xfilter: filtered = [t for t in all_times if t < self.configuration.xfilter] else: filtered = all_times logging.debug("Plotting: Histogram") # Histogram h_ax = plt.subplot(subplot_config) subplot_config += 1 self.hist_data(filtered, False, 1) if self.configuration.xfilter > 0: h_ax.set_xlim([0, self.configuration.xfilter.xfilter]) plt.suptitle('{}: {}'.format(path, self.configuration.call)) logging.debug("Plotting: CDF") # CDF ax = plt.subplot(subplot_config) subplot_config += 1 self.hist_data(all_times, True, 0) # Table logging.debug("Producing table") per50 = np.percentile(all_times, 50) per75 = np.percentile(all_times, 75) per95 = np.percentile(all_times, 95) tblstr = 'Fragmentation: {}%\n50th: {}\n75th: {}\n95th: {}'.format(fragmentation, per50, per75, per95) ax.text(0, 0.1, tblstr, bbox=dict(facecolor='white'), horizontalalignment='right', verticalalignment='top') def hist_data(self, data, cumulative, width): norm = 1 if cumulative else 0 plt.hist(data, bins=self.configuration.num_bins, log=False, cumulative=cumulative, linewidth=width, normed=norm) plt.xlabel("Instruction Counts") if cumulative: plt.title("Cumulative") class RequestSizePlotter(Plotter): def sort_split_and_fill_size_freqs(self, size_freq): # Sort by the size. Then split into two lists. size_freq.sort(key=lambda x: x[0]) sizes = [i[0] for i in size_freq] counts = [i[1] for i in size_freq] # Fill out the arrays where we didn't see events. This helps when we # bin the data later. sparse_sizes = [] sparse_counts = [] i = 0 j = 0 while i < max(sizes): if sizes[j] == (i + 1): sparse_sizes.append(sizes[j]) sparse_counts.append(counts[j]) j = j + 1 else: sparse_sizes.append(i+1) sparse_counts.append(0) i = i + 1 return sparse_sizes, sparse_counts def merge_size_counts(self, sizes, counts, sizes_c, counts_c): # Merge the calloc data with malloc. N.b. The lists can be different lengths; merge into sizes. if len(sizes_c) > len(sizes): tmpS = sizes tmpC = counts sizes = sizes_c counts = counts_c sizes_c = tmpS counts_c = tmpC for i in range(len(sizes)): if i >= len(sizes_c): break assert(sizes[i] == sizes_c[i]) counts[i] = counts[i] + counts_c[i] return sizes, counts def plot(self, report_data): plt.figure(figsize=(50, 10)) plt_config = 211 calls = ['malloc'] if not self.configuration.merge_calloc: plt_config = 311 calls.append('calloc') calls.append('realloc') for call_name in calls: logging.debug('Plotting: %s' % call_name) call_identifier = self.configuration.call_identifier_for_name(call_name) data = report_data.fileV1_data() size_freq = self.size_freq_for_data(data, call_identifier) sizes, counts = self.sort_split_and_fill_size_freqs(size_freq) # calloc merging if self.configuration.merge_calloc and call_name == 'malloc': size_freq_calloc = self.size_freq_for_data(data, 5) sizes_c, counts_c = self.sort_split_and_fill_size_freqs(size_freq_calloc) sizes, counts = self.merge_size_counts(sizes, counts, sizes_c, counts_c) # Bin the data num_bins = 16 binned_counts = [0] * 16 curr_bin = 0 bin_num = 0 for i in range(max(sizes))[1:]: if i % num_bins == 0 and i != 0: logging.debug('Bin end: %d' % i) binned_counts[bin_num] = curr_bin # Extra logging. Enable this if you want to output the # counts in each bin to the console. #logging.debug(' Count: %d' % curr_bin) bin_num = bin_num + 1 curr_bin = 0 if self.configuration.report_type == "nano_request_bins_ysize": curr_bin = curr_bin + (counts[i-1] * sizes[i-1]) else: curr_bin = curr_bin + counts[i-1] # Draw the plot ax = plt.subplot(plt_config) plt_config += 1 if self.configuration.report_type == "nano_request_bins" or self.configuration.report_type == "nano_request_bins_ysize": ax.bar(range(num_bins), binned_counts) ax.set_xticks(range(num_bins)) x_labels = range(1, 256, 16) x_labels.append("0") ax.set_xticklabels(x_labels) ax.set_xlabel("Request size (bytes)", fontsize=12) if self.configuration.report_type == "nano_request_bins_ysize": ax.set_ylabel("Total Requested (Bytes)") else: ax.set_ylabel("Frequency") ax.set_title(call_name) else: plt.bar(sizes, counts) plt.suptitle(self.configuration.fileV1) plt.subplots_adjust(hspace=0.5) class Tool(object): def __init__(self, args): self.args = args def run(self): logging.debug('Loading JSON') configuration = ReportConfiguration.configuration_for_arguments(self.args) plotter_class = configuration.plotter_class() plotter = plotter_class(configuration) report_data = ReportData(self.args.fileV1, self.args.fileV2) plotter.plot(report_data) if self.args.show_plot: plotter.show() else: plotter.write_to_path(self.args.output) @classmethod def main(cls): parser = argparse.ArgumentParser(description='Analyze libmalloc_replay perfdata output. This takes as input a .pdj file containing request sizes and instruction counts and outputs various plots.') parser.add_argument('fileV1', help='Path to nano V1 data JSON file') parser.add_argument('fileV2', nargs='?', help='Optional path to nano V2 data JSON file') parser.add_argument('--report', dest='report_type', choices=['instructions', 'scatter', 'request_sizes', 'nano_request_bins', 'nano_request_bins_ysize'], default='instructions', help='The report type to produce (default: %(default)s)') parser.add_argument('--call', dest='call', default='malloc', choices=['malloc', 'calloc', 'realloc', 'memalign', 'valloc'], help="The call to analyze (default: %(default)s)") parser.add_argument('-f', '--xfilter', type=int, default=0, help="Filter the histogram to a range from 0 to <%(dest)s)>") parser.add_argument('-b', '--num_bins', type=int, default=10000, help="The number of bins to use for histogrammed data (default: %(default)s)") parser.add_argument('-n', '--nano_malloc_cutoff', type=int, default=256, help="The cutoff size to filter for (default: %(default)s bytes)") parser.add_argument('--merge_calloc', action='store_true', default=False, help='Merge calloc calls with malloc. For use with the nano_request_bins, nano_request_bins_ysize and request_sizes reports.') parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose debug logging') output_group = parser.add_mutually_exclusive_group(required=True) output_group.add_argument('-s', '--show_plot', action='store_true') output_group.add_argument('-o', '--output', default='fig.pdf', help="The output file path, including type extension (default: %(default)s)") args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.DEBUG) cls(args).run() if __name__ == "__main__": Tool.main() ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tools/radix_tree_main.m ================================================ /* * Copyright (c) 2017 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #import #include #include #include #include "../src/radix_tree_internal.h" void usage() { printf ("usage: radix-tree [-l 0xADDRESS | 0xSTART-0xEND] FILENAME\n"); printf ("\n"); printf ("This is a debugging tool for the radix-tree sidetable used to track VM allocations\n"); printf ("under MallocStackLogging=lite.\n"); printf ("\n"); printf (" radix-tree FILE # print out radix tree as text\n"); printf (" radix-tree -l 0xf00 FILE # lookup address in radix tree\n"); printf (" radix-tree -l 0xf00-0xba FILE # lookup address range in radix tree\n"); printf ("\n"); exit(0); } uint64_t minsize = 4096; int main(int argc, char **argv) { int ch; uint64_t start = 0, end = 0; while ((ch = getopt(argc, argv, "l:")) != -1) { switch (ch) { case 'l': { char *p = strchr(optarg, '-'); if (p) { *p = 0; end = strtoull(p+1, NULL, 16); start = strtoull(optarg, NULL, 16); } else { start = strtoull(optarg, NULL, 16); } if (start%minsize || end%minsize) { usage(); } } break; default: case '?': usage(); } } argc -= optind; argv += optind; if (argc != 1) usage(); NSData *data; if (0==strcmp(argv[0], "-")) { data = [[NSFileHandle fileHandleWithStandardInput] readDataToEndOfFile]; } else { data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:argv[0]]]; } if (!data) { fprintf(stderr, "failed to read data\n"); return 1; } struct radix_tree *tree = (void*) [data bytes]; radix_tree_fsck(tree); if (start != 0 && end != 0) { uint64_t last_stackid = -1; uint64_t last_start = 0; for (uint64_t a = start; a <= end; a += minsize) { uint64_t stackid = a == end ? -1 : radix_tree_lookup(tree, a); if (last_stackid != -1) { if (stackid != last_stackid) { printf ("[%llx-%llx] -> %llx\n", last_start, a, last_stackid); last_stackid = stackid; last_start = a; } } else { if (stackid != -1) { last_stackid = stackid; last_start = a; } } } } else if (start != 0) { printf ("%llx -> %llx\n", start, radix_tree_lookup(tree, start)); } else { radix_tree_print(tree); } } ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/tools/read-radix-tree ================================================ #!/usr/bin/perl # read the radix tree out of a process and write it to stdout my $proc = shift; my @vmmap_out = `vmmap -vw -interleaved -noCoalesce $proc`; my $pid; for (@vmmap_out) { if (/^Process:.*\[(\d+)\]\s*$/) { $pid = $1; } if (/^Performance tool data \s*([0-9a-fA-F]+)-([0-9a-fA-F]+)/) { my $addr = $1; my $end = $2; if (`memread $pid 0x$addr 7` eq "radixv2") { exec sprintf("memread $pid 0x$addr 0x%x", hex($end) - hex($addr)); } } } printf STDERR "not found\n"; exit 1; ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodeconfig/interposable.list ================================================ _realloc ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodeconfig/libmalloc.dirty ================================================ _max_magazines _malloc_entropy _first_block_offset_by_size_class _last_block_offset_by_size_class _ptr_offset_to_size_class _nano_common_max_magazines _phys_ncpus _logical_ncpus _entropic_address _entropic_limit _nanov2_config_predicate _pFRZCounterLive _pFRZCounterDrain __malloc_engaged_nano _malloc_num_zones _malloc_num_zones_allocated _malloc_zones _malloc_debug_flags __malloc_no_asl_log _stack_logging_dontcompact _stack_logging_finished_init __malloc_entropy_initialized _malloc_default_purgeable_zone.dpz __malloc_initialize_pred _initial_default_zone ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodeconfig/libmalloc.xcconfig ================================================ #include "/Makefiles/CoreOS/Xcode/BSD.xcconfig" SDKROOT = macosx.internal SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator BUILD_VARIANTS = normal debug PRODUCT_NAME = libsystem_malloc INSTALL_PATH = /usr/lib/system PUBLIC_HEADERS_FOLDER_PATH = /usr/include/malloc PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include SYSTEM_FRAMEWORK_HEADERS = $(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders HEADER_SEARCH_PATHS = $(DERIVED_FILES_DIR)/dtrace $(SRCROOT)/include $(SYSTEM_FRAMEWORK_HEADERS) $(SDKROOT)/usr/local/include $(inherited) GCC_PREPROCESSOR_DEFINITIONS = _FORTIFY_SOURCE=0 NDEBUG $(OSATOMIC_PREPROCESSOR_DEFINITIONS) $(PLATFORM_PREPROCESSOR_DEFINITIONS) OSATOMIC_PREPROCESSOR_DEFINITIONS = OSATOMIC_USE_INLINED=1 OS_UNFAIR_LOCK_INLINE=1 OSATOMIC_PREPROCESSOR_DEFINITIONS_NOINLINE = OSATOMIC_DEPRECATED=0 OSATOMIC_USE_INLINED=0 OS_UNFAIR_LOCK_INLINE=0 GCC_NO_COMMON_BLOCKS = YES ENABLE_STRICT_OBJC_MSGSEND = YES // TODO: Add -fno-stack-protector when uplink to Libc is removed OTHER_CFLAGS = $(PLATFORM_CFLAGS) OTHER_CFLAGS_normal = -momit-leaf-frame-pointer OTHER_CFLAGS_debug = -fstack-protector -fno-inline -O0 -DDEBUG=1 -UNDEBUG GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES //GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES GCC_WARN_UNUSED_FUNCTION = YES GCC_WARN_UNUSED_LABEL = YES //GCC_WARN_UNUSED_PARAMETER = YES GCC_WARN_UNUSED_VALUE = YES GCC_WARN_UNUSED_VARIABLE = YES GCC_WARN_64_TO_32_BIT_CONVERSION = YES GCC_WARN_ABOUT_RETURN_TYPE = YES GCC_WARN_UNDECLARED_SELECTOR = YES GCC_WARN_UNINITIALIZED_AUTOS = YES CLANG_WARN_BOOL_CONVERSION = YES CLANG_WARN_CONSTANT_CONVERSION = YES CLANG_WARN_EMPTY_BODY = YES CLANG_WARN_ENUM_CONVERSION = YES CLANG_WARN_INFINITE_RECURSION = YES CLANG_WARN_INT_CONVERSION = YES CLANG_WARN_SUSPICIOUS_MOVE = YES CLANG_WARN_UNREACHABLE_CODE = YES CLANG_WARN__DUPLICATE_METHOD_MATCH = YES // clang doesn't understand the simple_printf %y specifier WARNING_CFLAGS = -Wno-format-invalid-specifier -Wno-format-extra-args LLVM_LTO = LLVM_LTO_$(CURRENT_VARIANT) LLVM_LTO_normal = YES LLVM_LTO_debug = NO DEAD_CODE_STRIPPING = NO IS_ZIPPERED = YES SIM_SUFFIX[sdk=*simulator*] = _sim LINK_WITH_STANDARD_LIBRARIES = NO OTHER_LDFLAGS = -all_load -L/usr/lib/system -umbrella System $(CR_LDFLAGS) $(LIBCOMPILER_RT_LDFLAGS) $(LIBDYLD_LDFLAGS) $(LIBSYSCALL_LDFLAGS) $(LIBPLATFORM_LDFLAGS) $(PLATFORM_LDFLAGS) $(UPLINK_LDFLAGS) $(INTERPOSE_LDFLAGS) $(DIRTY_LDFLAGS) LIBCOMPILER_RT_LDFLAGS = -lcompiler_rt LIBPLATFORM_LDFLAGS = -lsystem$(SIM_SUFFIX)_platform LIBSYSCALL_LDFLAGS = -lsystem$(SIM_SUFFIX)_kernel LIBDYLD_LDFLAGS = -ldyld // TODO: Eliminate the crosslink between libmalloc and Libc (13046853) UPLINK_LDFLAGS = -Wl,-upward-lsystem_c INTERPOSE_LDFLAGS = -Wl,-interposable_list,$(SRCROOT)/xcodeconfig/interposable.list ORDER_FILE = $(SDKROOT)/$(APPLE_INTERNAL_DIR)/OrderFiles/$(PRODUCT_NAME).order ORDER_FILE[sdk=*simulator*] = DIRTY_LDFLAGS = -Wl,-dirty_data_list,$(SRCROOT)/xcodeconfig/libmalloc.dirty DIRTY_LDFLAGS[sdk=macosx*] = DYLIB_CURRENT_VERSION = $(CURRENT_PROJECT_VERSION) ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodeconfig/libmalloc_eos.xcconfig ================================================ #include "libmalloc.xcconfig" BUILD_VARIANTS = normal EXECUTABLE_PREFIX = lib GENERATE_MASTER_OBJECT_FILE = YES INSTALL_PATH = /usr/local/lib/eOS PRODUCT_NAME = malloc_eOS SKIP_INSTALL = YES SKIP_INSTALL[sdk=iphoneos*] = NO STRIP_INSTALLED_PRODUCT = NO VERSIONING_SYSTEM = apple-generic MACH_O_TYPE = staticlib OTHER_LDFLAGS = ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodeconfig/libmalloc_resolved.xcconfig ================================================ #include "libmalloc.xcconfig" SUPPORTED_PLATFORMS = iphoneos appletvos watchos PRODUCT_NAME = malloc_$(RESOLVED_VARIANT) OTHER_LDFLAGS = SKIP_INSTALL = YES VERSIONING_SYSTEM = EXCLUDED_SOURCE_FILE_NAMES = * ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodeconfig/libmalloc_resolver.xcconfig ================================================ #include "libmalloc.xcconfig" ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodeconfig/libmalloc_static.xcconfig ================================================ #include "libmalloc.xcconfig" BUILD_VARIANTS = normal debug EXECUTABLE_PREFIX = lib GENERATE_MASTER_OBJECT_FILE = YES INSTALL_PATH = /usr/local/lib/system PRODUCT_NAME = malloc STRIP_INSTALLED_PRODUCT = NO VERSIONING_SYSTEM = apple-generic MACH_O_TYPE = staticlib OTHER_LDFLAGS = ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodescripts/install-codes.sh ================================================ #!/bin/bash -e # install kdebug trace files based on the input file INPUT=${SCRIPT_INPUT_FILE_0} OUTPUT=${SCRIPT_OUTPUT_FILE_0} # pre-process the source and pass through perl it xcrun cc -E -I${SDKROOT}/System/Library/Frameworks/System.framework/PrivateHeaders -D_MALLOC_BUILDING_CODES_ "${INPUT}" | perl > "${OUTPUT}" ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodescripts/manpages.sh ================================================ #!/bin/bash -e if [ "$ACTION" = installhdrs ]; then exit 0; fi if [[ "$PLATFORM_NAME" != "macosx" ]]; then exit 0; fi UNIFDEF_FLAGS="" MANPAGES_LIST="${SRCROOT}/man/manpages.lst" FILES=$(find -E ${SRCROOT} -regex '.*/[^.]+\.[0-9]' -type f) cat ${MANPAGES_LIST} | grep -v -E '(^#|^\s*$)' | while read first solid rest; do SOURCE=$(grep -E "/${first}$"<&2 exit 1 fi echo "Using ${CLANGFORMAT} to reindent, using concurrency of ${CPUS}" find -x . \! \( \( -name BUILD -o -name EXTERNAL_HEADERS -o -name libMicro -o -name zlib -o -name .svn -o -name .git -o -name cscope.\* -o -name \*~ \) -prune \) -type f \( -name \*.c -o -name \*.cpp \) -print0 | \ xargs -0 -P "${CPUS}" -n 10 "${CLANGFORMAT}" -style=file -i ret=$? if [ $ret -ne 0 ]; then echo "reindent failed: $ret" 1>&2 exit 1 fi exit 0 ================================================ FILE: InterView-obj-isa-class/libmalloc-166.220.1/xcodescripts/sanitise_headers.sh ================================================ #!/bin/bash -e # # Copyright (c) 2010-2011 Apple Inc. All rights reserved. # # @APPLE_APACHE_LICENSE_HEADER_START@ # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # @APPLE_APACHE_LICENSE_HEADER_END@ # ================================================ FILE: InterView-obj-isa-class/objc4-750/APPLE_LICENSE ================================================ APPLE PUBLIC SOURCE LICENSE Version 2.0 - August 6, 2003 Please read this License carefully before downloading this software. By downloading or using this software, you are agreeing to be bound by the terms of this License. If you do not or cannot agree to the terms of this License, please do not download or use the software. 1. General; Definitions. This License applies to any program or other work which Apple Computer, Inc. ("Apple") makes publicly available and which contains a notice placed by Apple identifying such program or work as "Original Code" and stating that it is subject to the terms of this Apple Public Source License version 2.0 ("License"). As used in this License: 1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is the grantor of rights, (i) claims of patents that are now or hereafter acquired, owned by or assigned to Apple and (ii) that cover subject matter contained in the Original Code, but only to the extent necessary to use, reproduce and/or distribute the Original Code without infringement; and (b) in the case where You are the grantor of rights, (i) claims of patents that are now or hereafter acquired, owned by or assigned to You and (ii) that cover subject matter in Your Modifications, taken alone or in combination with Original Code. 1.2 "Contributor" means any person or entity that creates or contributes to the creation of Modifications. 1.3 "Covered Code" means the Original Code, Modifications, the combination of Original Code and any Modifications, and/or any respective portions thereof. 1.4 "Externally Deploy" means: (a) to sublicense, distribute or otherwise make Covered Code available, directly or indirectly, to anyone other than You; and/or (b) to use Covered Code, alone or as part of a Larger Work, in any way to provide a service, including but not limited to delivery of content, through electronic communication with a client other than You. 1.5 "Larger Work" means a work which combines Covered Code or portions thereof with code not governed by the terms of this License. 1.6 "Modifications" mean any addition to, deletion from, and/or change to, the substance and/or structure of the Original Code, any previous Modifications, the combination of Original Code and any previous Modifications, and/or any respective portions thereof. When code is released as a series of files, a Modification is: (a) any addition to or deletion from the contents of a file containing Covered Code; and/or (b) any new file or other representation of computer program statements that contains any part of Covered Code. 1.7 "Original Code" means (a) the Source Code of a program or other work as originally made available by Apple under this License, including the Source Code of any updates or upgrades to such programs or works made available by Apple under this License, and that has been expressly identified by Apple as such in the header file(s) of such work; and (b) the object code compiled from such Source Code and originally made available by Apple under this License. 1.8 "Source Code" means the human readable form of a program or other work that is suitable for making modifications to it, including all modules it contains, plus any associated interface definition files, scripts used to control compilation and installation of an executable (object code). 1.9 "You" or "Your" means an individual or a legal entity exercising rights under this License. For legal entities, "You" or "Your" includes any entity which controls, is controlled by, or is under common control with, You, where "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of fifty percent (50%) or more of the outstanding shares or beneficial ownership of such entity. 2. Permitted Uses; Conditions & Restrictions. Subject to the terms and conditions of this License, Apple hereby grants You, effective on the date You accept this License and download the Original Code, a world-wide, royalty-free, non-exclusive license, to the extent of Apple's Applicable Patent Rights and copyrights covering the Original Code, to do the following: 2.1 Unmodified Code. You may use, reproduce, display, perform, internally distribute within Your organization, and Externally Deploy verbatim, unmodified copies of the Original Code, for commercial or non-commercial purposes, provided that in each instance: (a) You must retain and reproduce in all copies of Original Code the copyright and other proprietary notices and disclaimers of Apple as they appear in the Original Code, and keep intact all notices in the Original Code that refer to this License; and (b) You must include a copy of this License with every copy of Source Code of Covered Code and documentation You distribute or Externally Deploy, and You may not offer or impose any terms on such Source Code that alter or restrict this License or the recipients' rights hereunder, except as permitted under Section 6. 2.2 Modified Code. You may modify Covered Code and use, reproduce, display, perform, internally distribute within Your organization, and Externally Deploy Your Modifications and Covered Code, for commercial or non-commercial purposes, provided that in each instance You also meet all of these conditions: (a) You must satisfy all the conditions of Section 2.1 with respect to the Source Code of the Covered Code; (b) You must duplicate, to the extent it does not already exist, the notice in Exhibit A in each file of the Source Code of all Your Modifications, and cause the modified files to carry prominent notices stating that You changed the files and the date of any change; and (c) If You Externally Deploy Your Modifications, You must make Source Code of all Your Externally Deployed Modifications either available to those to whom You have Externally Deployed Your Modifications, or publicly available. Source Code of Your Externally Deployed Modifications must be released under the terms set forth in this License, including the license grants set forth in Section 3 below, for as long as you Externally Deploy the Covered Code or twelve (12) months from the date of initial External Deployment, whichever is longer. You should preferably distribute the Source Code of Your Externally Deployed Modifications electronically (e.g. download from a web site). 2.3 Distribution of Executable Versions. In addition, if You Externally Deploy Covered Code (Original Code and/or Modifications) in object code, executable form only, You must include a prominent notice, in the code itself as well as in related documentation, stating that Source Code of the Covered Code is available under the terms of this License with information on how and where to obtain such Source Code. 2.4 Third Party Rights. You expressly acknowledge and agree that although Apple and each Contributor grants the licenses to their respective portions of the Covered Code set forth herein, no assurances are provided by Apple or any Contributor that the Covered Code does not infringe the patent or other intellectual property rights of any other entity. Apple and each Contributor disclaim any liability to You for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, You hereby assume sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow You to distribute the Covered Code, it is Your responsibility to acquire that license before distributing the Covered Code. 3. Your Grants. In consideration of, and as a condition to, the licenses granted to You under this License, You hereby grant to any person or entity receiving or distributing Covered Code under this License a non-exclusive, royalty-free, perpetual, irrevocable license, under Your Applicable Patent Rights and other intellectual property rights (other than patent) owned or controlled by You, to use, reproduce, display, perform, modify, sublicense, distribute and Externally Deploy Your Modifications of the same scope and extent as Apple's licenses under Sections 2.1 and 2.2 above. 4. Larger Works. You may create a Larger Work by combining Covered Code with other code not governed by the terms of this License and distribute the Larger Work as a single product. In each such instance, You must make sure the requirements of this License are fulfilled for the Covered Code or any portion thereof. 5. Limitations on Patent License. Except as expressly stated in Section 2, no other patent rights, express or implied, are granted by Apple herein. Modifications and/or Larger Works may require additional patent licenses from Apple which Apple may grant in its sole discretion. 6. Additional Terms. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations and/or other rights consistent with the scope of the license granted herein ("Additional Terms") to one or more recipients of Covered Code. However, You may do so only on Your own behalf and as Your sole responsibility, and not on behalf of Apple or any Contributor. You must obtain the recipient's agreement that any such Additional Terms are offered by You alone, and You hereby agree to indemnify, defend and hold Apple and every Contributor harmless for any liability incurred by or claims asserted against Apple or such Contributor by reason of any such Additional Terms. 7. Versions of the License. Apple may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Once Original Code has been published under a particular version of this License, You may continue to use it under the terms of that version. You may also choose to use such Original Code under the terms of any subsequent version of this License published by Apple. No one other than Apple has the right to modify the terms applicable to Covered Code created under this License. 8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in part pre-release, untested, or not fully tested works. The Covered Code may contain errors that could cause failures or loss of data, and may be incomplete or contain inaccuracies. You expressly acknowledge and agree that use of the Covered Code, or any portion thereof, is at Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS, THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY. You acknowledge that the Covered Code is not intended for use in the operation of nuclear facilities, aircraft navigation, communication systems, or air traffic control machines in which case the failure of the Covered Code could lead to death, personal injury, or severe physical or environmental damage. 9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY, TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU. In no event shall Apple's total liability to You for all damages (other than as may be required by applicable law) under this License exceed the amount of fifty dollars ($50.00). 10. Trademarks. This License does not grant any rights to use the trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS", "QuickTime", "QuickTime Streaming Server" or any other trademarks, service marks, logos or trade names belonging to Apple (collectively "Apple Marks") or to any trademark, service mark, logo or trade name belonging to any Contributor. You agree not to use any Apple Marks in or as part of the name of products derived from the Original Code or to endorse or promote products derived from the Original Code other than as expressly permitted by and in strict compliance at all times with Apple's third party trademark usage guidelines which are posted at http://www.apple.com/legal/guidelinesfor3rdparties.html. 11. Ownership. Subject to the licenses granted under this License, each Contributor retains all rights, title and interest in and to any Modifications made by such Contributor. Apple retains all rights, title and interest in and to the Original Code and any Modifications made by or on behalf of Apple ("Apple Modifications"), and such Apple Modifications will not be automatically subject to this License. Apple may, at its sole discretion, choose to license such Apple Modifications under this License, or on different terms from those contained in this License or may choose not to license them at all. 12. Termination. 12.1 Termination. This License and the rights granted hereunder will terminate: (a) automatically without notice from Apple if You fail to comply with any term(s) of this License and fail to cure such breach within 30 days of becoming aware of such breach; (b) immediately in the event of the circumstances described in Section 13.5(b); or (c) automatically without notice from Apple if You, at any time during the term of this License, commence an action for patent infringement against Apple; provided that Apple did not first commence an action for patent infringement against You in that instance. 12.2 Effect of Termination. Upon termination, You agree to immediately stop any further use, reproduction, modification, sublicensing and distribution of the Covered Code. All sublicenses to the Covered Code which have been properly granted prior to termination shall survive any termination of this License. Provisions which, by their nature, should remain in effect beyond the termination of this License shall survive, including but not limited to Sections 3, 5, 8, 9, 10, 11, 12.2 and 13. No party will be liable to any other for compensation, indemnity or damages of any sort solely as a result of terminating this License in accordance with its terms, and termination of this License will be without prejudice to any other right or remedy of any party. 13. Miscellaneous. 13.1 Government End Users. The Covered Code is a "commercial item" as defined in FAR 2.101. Government software and technical data rights in the Covered Code include only those rights customarily provided to the public as defined in this License. This customary commercial license in technical data and software is provided in accordance with FAR 12.211 (Technical Data) and 12.212 (Computer Software) and, for Department of Defense purchases, DFAR 252.227-7015 (Technical Data -- Commercial Items) and 227.7202-3 (Rights in Commercial Computer Software or Computer Software Documentation). Accordingly, all U.S. Government End Users acquire Covered Code with only those rights set forth herein. 13.2 Relationship of Parties. This License will not be construed as creating an agency, partnership, joint venture or any other form of legal association between or among You, Apple or any Contributor, and You will not represent to the contrary, whether expressly, by implication, appearance or otherwise. 13.3 Independent Development. Nothing in this License will impair Apple's right to acquire, license, develop, have others develop for it, market and/or distribute technology or products that perform the same or similar functions as, or otherwise compete with, Modifications, Larger Works, technology or products that You may develop, produce, market or distribute. 13.4 Waiver; Construction. Failure by Apple or any Contributor to enforce any provision of this License will not be deemed a waiver of future enforcement of that or any other provision. Any law or regulation which provides that the language of a contract shall be construed against the drafter will not apply to this License. 13.5 Severability. (a) If for any reason a court of competent jurisdiction finds any provision of this License, or portion thereof, to be unenforceable, that provision of the License will be enforced to the maximum extent permissible so as to effect the economic benefits and intent of the parties, and the remainder of this License will continue in full force and effect. (b) Notwithstanding the foregoing, if applicable law prohibits or restricts You from fully and/or specifically complying with Sections 2 and/or 3 or prevents the enforceability of either of those Sections, this License will immediately terminate and You must immediately discontinue any use of the Covered Code and destroy all copies of it that are in your possession or control. 13.6 Dispute Resolution. Any litigation or other dispute resolution between You and Apple relating to this License shall take place in the Northern District of California, and You and Apple hereby consent to the personal jurisdiction of, and venue in, the state and federal courts within that District with respect to this License. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. 13.7 Entire Agreement; Governing Law. This License constitutes the entire agreement between the parties with respect to the subject matter hereof. This License shall be governed by the laws of the United States and the State of California, except that body of California law concerning conflicts of law. Where You are located in the province of Quebec, Canada, the following clause applies: The parties hereby confirm that they have requested that this License and all related documents be drafted in English. Les parties ont exige que le present contrat et tous les documents connexes soient rediges en anglais. EXHIBIT A. "Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. This file contains Original Code and/or Modifications of Original Code as defined in and that are subject to the Apple Public Source License Version 2.0 (the 'License'). You may not use this file except in compliance with the License. Please obtain a copy of the License at http://www.opensource.apple.com/apsl/ and read it before using this file. The Original Code and all software distributed under the License are distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the specific language governing rights and limitations under the License." ================================================ FILE: InterView-obj-isa-class/objc4-750/ReleaseNotes.rtf ================================================ {\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf420 {\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;\f1\fswiss\fcharset77 Helvetica;\f2\fnil\fcharset77 Monaco; } {\colortbl;\red255\green255\blue255;\red70\green130\blue100;} \vieww11200\viewh14360\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b\fs30 \cf0 Objective-C Release Notes\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0\fs24 \cf0 \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b\fs30 \cf0 Mac OS X 10.5 Leopard \f1\b0\fs24 \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 Contents \f1\b0 \ \'a5 Garbage Collection\ \'a5\'caProperties\ \'a5\'caLoading and Unloading Bundles\ \'a5 Method and Class Attributes\ \'a5\'ca@package Instance Variables\ \'a5\'caRuntime API changes\ \'a5\'ca64-bit ABI\ \'a5\'ca64-bit Class and Instance Variable Access Control\ \'a5\'ca64-bit Non-Fragile Instance Variables\ \'a5\'ca64-bit Zero-Cost C++-Compatible Exceptions\ \ \ \f0\b Garbage Collection\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0 \cf0 \ The Objective-C runtime examines on startup the execution image to determine whether to run with garbage collection or not. Each object file has an info section and they must all agree for execution to proceed. Standard compilation results in an info section that indicates that no GC capability is present. Compiling with -fobjc-gc indicates that both GC and retain/release logic is present. Compiling with -fobjc-gc-only indicates that only GC logic is present. A non-GC executable that attempts to load a gc-only framework will fail, as will a GC capable executable that attemps to load a GC incapable framework (or bundle).\ \ The collector initially runs only on the main thread when requested via objc_collect_if_needed(1), which is called automatically from the autoreleasepool -drain method. The AppKit arranges to call objc_start_collector_thread() after launch and subsequently collections run on a dedicated thread and are responsive to pure allocation demand. The objc_set_collection_threshold and objc_set_collection_ratio calls are used to establish the "need" for a collection. Once every ratio times a full (complete) collection will occur; otherwise a generational collection will be done if allocations have exceeded the threshold.\ \ The garbage collector minimally pauses those threads which have been registered to it while collecting. Registration occurs during establishment of an NSThread, not simply a pthread.\ \ A critical assumption that the collector makes is that one thread never gains access to an object (or more generally any block of garbage collected memory) by way of a pointer to another thread's stack memory. In other words, the collector does not make provision for cross thread stack references. This enables the collector to avoid pausing all threads at the same time while it examines recursively all of their references.\ \ The compiler uses three "helper" functions for assignments of strong pointers to garbage collected memory into global memory ( \f2\fs20 objc_assign_global \f1\fs24 ), garbage collected heap memory ( \f2\fs20 objc_assign_ivar \f1\fs24 ), or into unknown memory ( \f2\fs20 objc_assign_strongCast \f1\fs24 ). For assignments of weak pointers it uses objc_assign_weak and for reads it uses objc_read_weak.\ \ When copying memory in bulk into a garbage collected block one must use the API \f2\fs20 objc_memmove_collectable(void *dst, const void *src, size_t size) \f1\fs24 .\ \ Garbage Collection Errors\ \ The collector itself is found in \f2\fs20 /usr/lib/libauto.dylib \f1\fs24 . Its error messages are printed using \f2\fs20 malloc_printf \f1\fs24 . The ObjC runtime is found in \f2\fs20 /usr/lib/libobjc.dylib \f1\fs24 . Its errors are printed using \f2\fs20 _objc_inform \f1\fs24 . Currently we note resurrection and reference count underflow errors by calling the following routines:\ \ \pard\tx960\pardeftab960\ql\qnatural\pardirnatural \f2\fs20 \cf2 \CocoaLigature0 objc_assign_global_error\ \pard\tx960\pardeftab960\ql\qnatural\pardirnatural \cf0 objc_assign_ivar_error\ \pard\tx960\pardeftab960\ql\qnatural\pardirnatural \cf2 objc_exception_during_finalize_error\ auto_zone_resurrection_error\cf0 \ \cf2 auto_refcount_underflow_error \f1\fs24 \cf0 \CocoaLigature1 \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \cf0 \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 Properties \f1\b0 \ \ The syntax for Objective-C properties has been overhauled since WWDC 2006. See the property documentation for details.\ \ In summary, @property(attributes) type name introduces an implicit declaration of a "getter" and a "setter" method (unless a read-only property is requested) for the "variable" named. The setter= and getter= attributes allow one to specify the names of the methods, otherwise a "name" method and a "setName:" method are implicitly declared. They may also be explicitly named.\ \ By default, properties are assigned when set. For objects under non-GC this is often incorrect and a warning is issued unless the assignment semantic is explicitly named. There are three choices - assign, for non-retained object references, copy, for objects that are copied and implicitly retained, and simply retain, for objects that require being retained when set.\ \ Access to properties is atomic by default. This is trivial under GC for almost everything and also trivial under non-GC for everything but objects and structures. In particular atomic access to retained objects under non-GC conditions can be expensive. As such, a nonatomic property attribute is available.\ \ Pointers may be held strongly under GC by declaring them __strong, and they can be zeroing weak by declaring them __weak.\ \ The implementations for properties can be provided by the compiler and runtime through the use of the @synthesize statement in the @implementation section of the class (or class extension). The compiler expects an instance variable of the same name as the property. If one wishes a different name it can be supplied to the @synthesize statement.\ \ In particular the compiler and runtime will implement accessors to retained objects by using atomic compare and swap instructions. It is extremely dangerous to directly access an atomic object property through its instance variable since another thread might change its value unpredictably. As such the compiler will warn you about such unprotected accesses. The runtime, in fact, will temporarily use the least significant bit of the instance variable as a temporary lock while retaining the new value and releasing the old. Direct use of an atomic instance variable under non-GC is strongly discouraged.\ \ \ \f0\b Loading and Unloading Bundles\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0 \cf0 \ Since Mac OS X Version 10.4 it has been possible to unload bundles containing Objective-C. No attempt is made to prevent this if objects are still present for classes that are unloaded. Subclasses of classes loaded in bundles are particularly vulnerable.\ \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 Method and Class Attributes\ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0 \cf0 Objective-C now supports some gcc attributes for Objective-C methods. Syntactically, attributes for a method follow the method's declaration, and attributes for a method parameter sit between the parameter type and the parameter name. Supported attributes include:\ \ Deprecation and availability, including AvailabilityMacros.h\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f2\fs20 \cf0 - (void)method:(id)param __attribute__((deprecated));\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\fs24 \cf0 \ Unused parameters\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f2\fs20 \cf0 - (void)method:(id) __attribute__((unused)) param;\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\fs24 \cf0 \ Sentinel parameters, including \f2\fs20 NS_REQUIRES_NIL_TERMINATION \f1\fs24 \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f2\fs20 \cf0 - (void)methodWithObjects:(id)obj, ... NS_REQUIRES_NIL_TERMINATION;\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\fs24 \cf0 \ Objective-C also supports some gcc attributes for Objective-C classes. Syntactically, attributes for a class precede the class's \f2\fs20 @interface \f1\fs24 declaration. Supported attributes include:\ \ Deprecation and availability, including AvailabilityMacros.h\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f2\fs20 \cf0 __attribute__((deprecated))\ @interface MyDeprecatedClass : SomeSuperclass\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\fs24 \cf0 \ Visibility\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f2\fs20 \cf0 __attribute__((visibility("hidden")))\ @interface MyPrivateClass : SomeSuperclass \f1\fs24 \ \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 @package Instance Variables \f1\b0 \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f2\fs20 \cf0 @package \f1\fs24 is a new ivar protection class, like \f2\fs20 @public \f1\fs24 and \f2\fs20 @protected \f1\fs24 . \f2\fs20 @package \f1\fs24 ivars behave as follows:\ \'a5\'ca \f2\fs20 @public \f1\fs24 in 32-bit; \ \'a5\'ca \f2\fs20 @public \f1\fs24 in 64-bit, inside the framework that defined the class; \ \'a5\'ca \f2\fs20 @private \f1\fs24 in 64-bit, outside the framework that defined the class.\ \ In 64-bit, the ivar symbol for an \f2\fs20 @package \f1\fs24 ivar is not exported, so any attempt to use the ivar from outside the framework that defined the class will fail with a link error. See "64-bit Class and Instance Variable Access Control" for more about ivar symbols.\ \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 Runtime API changes\ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0 \cf0 The C interface to the Objective-C runtime (in \f2\fs20 \f1\fs24 ) has changed significantly. Highlights include:\ \'a5\'caAlmost all structures are deprecated, including \f2\fs20 struct objc_class \f1\fs24 . Functional replacements for most of these are provided.\ \'a5\'ca \f2\fs20 class_poseAs \f1\fs24 is deprecated. Use method list manipulation functions instead.\ \'a5\'ca \f2\fs20 class_nextMethodList \f1\fs24 is deprecated. Use \f2\fs20 class_copyMethodList \f1\fs24 instead.\ \'a5\'ca \f2\fs20 class_addMethods \f1\fs24 is deprecated. Use \f2\fs20 class_addMethod \f1\fs24 instead.\ \'a5\'ca \f2\fs20 objc_addClass \f1\fs24 is deprecated. Use \f2\fs20 objc_allocateClassPair \f1\fs24 and \f2\fs20 objc_registerClassPair \f1\fs24 instead.\ \'a5\'caIn general, all deprecated declarations are absent in 64-bit.\ \'a5\'caThe API in objc/objc-runtime.h and objc/objc-class.h is now in objc/runtime.h and objc/message.h. The old header files simply #include the new ones.\ \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 64-bit ABI\ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0 \cf0 The 64-bit Objective-C ABI is generally unlike the 32-bit ABI. The new ABI provides new features, better performance, and improved future adaptability. All aspects of the 64-bit ABI are private and subject to future change. Forthcoming documentation will describe the ABI for the use of compilers and developer tools only.\ \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 64-bit Class and Instance Variable Access Control\ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0 \cf0 In 64-bit Objective-C, access control for classes and each class and instance variable has a symbol associated with it. All uses of a class or instance variable reference this symbol. These symbols are subject to access control by the linker.\ \ The upshot is that access to private classes and ivars is more strictly enforced. Illegal use of a private ivar may fail with a link error. Frameworks that provide classes and ivars must correctly export their symbols. In particular, frameworks built with \f2\fs20 -fvisibility=hidden \f1\fs24 or a linker export list may need to be changed.\ \ Class symbols have names of the form \f2\fs20 _OBJC_CLASS_$_ClassName \f1\fs24 and \f2\fs20 _OBJC_METACLASS_$_ClassName \f1\fs24 . The class symbol is used by clients who send messages to the class (i.e. \f2\fs20 [ClassName someMessage] \f1\fs24 ). The metaclass symbol is used by clients who subclass the class.\ \ By default, class symbols are exported. They are affected by gcc's symbol visibility flags, so \f2\fs20 -fvisibility=hidden \f1\fs24 will make the class symbols non-exported. The linker recognizes the old symbol name \f2\fs20 .objc_class_name_ClassName \f1\fs24 in linker export lists and translates it to these symbols. \ \ Visibility of a single class can be changed using an attribute.\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f2\fs20 \cf0 __attribute__((visibility("hidden")))\ @interface ClassName : SomeSuperclass\ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\fs24 \cf0 For classes with \f2\fs20 "default" \f1\fs24 visibility, the class symbols are exported, and the ivar symbols are handled as described below. For classes with \f2\fs20 "hidden" \f1\fs24 visibility, the class symbols and ivar symbols are all not exported.\ \ Ivar symbols have the form \f2\fs20 _OBJC_IVAR_$_ClassName.IvarName \f1\fs24 . The ivar symbol is used by clients who read or write the ivar.\ \ By default, ivar symbols for \f2\fs20 @private \f1\fs24 and \f2\fs20 @package \f1\fs24 ivars are not exported, and ivar symbols for \f2\fs20 @public \f1\fs24 and \f2\fs20 @protected \f1\fs24 ivars are exported. This can be changed by export lists, \f2\fs20 -fvisibility \f1\fs24 , or a visibility attribute on the class. Visibility attributes on individual ivars are currently not supported.\ \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 64-bit Non-Fragile Instance Variables\ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0 \cf0 All instance variables in 64-bit Objective-C are non-fragile. That is, existing compiled code that uses a class's ivars will not break when the class or a superclass changes its own ivar layout. In particular, framework classes may add new ivars without breaking subclasses compiled against a previous version of the framework.\ \ Ivars may be added or reordered freely; existing users of a reordered ivar will adapt transparently. Other ivar changes are safe except that they will break any existing users of the ivar: deleting an ivar, renaming an ivar, moving an ivar to a different class, and changing the type of an ivar. \ \ Do not use \f2\fs20 @defs \f1\fs24 . The ivar layout it presents cannot adapt to superclass changes.\ \ Do not use \f2\fs20 sizeof(SomeClass) \f1\fs24 . Use \f2\fs20 class_getInstanceSize([SomeClass class]) \f1\fs24 instead.\ \ Do not use \f2\fs20 offsetof(SomeClass, SomeIvar) \f1\fs24 . Use \f2\fs20 ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar")) \f1\fs24 instead.\ \ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\b \cf0 64-bit Zero-Cost C++-Compatible Exceptions\ \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f1\b0 \cf0 In 64-bit, the implementation of Objective-C exceptions has been rewritten. The new system provides "zero-cost" try blocks and interoperability with C++. \ \ "Zero-cost" try blocks incur no time penalty when entering an \f2\fs20 @try \f1\fs24 block, unlike 32-bit which must call \f2\fs20 setjmp() \f1\fs24 and other additional bookkeeping. On the other hand, actually throwing an exception is much more expensive. For best performance in 64-bit, exceptions should be thrown only in exceptional cases.\ \ The Cocoa frameworks require that all exceptions be instances of NSException or its subclasses. Do not throw objects of other types.\ \ The Cocoa frameworks are generally not exception-safe. Their general pattern is that exceptions are reserved for programmer error only, and the program should quit soon after catching such an exception. Be careful when throwing exceptions across the Cocoa frameworks.\ \ In 64-bit, C++ exceptions and Objective-C exceptions are interoperable. In particular, C++ destructors and Objective-C \f2\fs20 @finally \f1\fs24 blocks are honored when unwinding any exception, and default catch clauses - \f2\fs20 catch (...) \f1\fs24 and \f2\fs20 @catch (...) \f1\fs24 - are able to catch and re-throw any exception.\ \ Objective-C \f2\fs20 @catch (id e) \f1\fs24 catches any Objective-C exception, but no C++ exceptions. Use \f2\fs20 @catch (...) \f1\fs24 to catch everything, and \f2\fs20 @throw; \f1\fs24 to re-throw caught exceptions. \f2\fs20 @catch (...) \f1\fs24 is allowed in 32-bit, and has the same effect there as \f2\fs20 @catch (id e) \f1\fs24 . \ } ================================================ FILE: InterView-obj-isa-class/objc4-750/interposable.txt ================================================ _objc_release ================================================ FILE: InterView-obj-isa-class/objc4-750/libobjc.order ================================================ __objc_init _environ_init _tls_init _lock_init _recursive_mutex_init _exception_init _map_images _map_images_nolock __getObjcImageInfo __hasObjcContents __objc_appendHeader _verify_gc_readiness _gc_init __objc_inform_on_crash __objc_crashlog _rtp_init _gc_fixup_barrier_stubs __objc_update_stubs_in_mach_header _sel_init ___sel_registerName __objc_search_builtins __ZNK8objc_opt13objc_selopt_t3getEPKc __ZNK8objc_opt13objc_selopt_t4hashEPKc _sel_registerName _arr_init __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE4initEj __read_images __Z11initVtablesv __Z17appendTrampolinesP22objc_trampoline_header _gdb_objc_trampolines_changed _crashlog_header_name __getObjc2ClassList _NXCreateMapTableFromZone _NXCreateHashTable _NXCreateHashTableFromZone _NXHashGet _NXHashInsert __NXHashRehashToCapacity _NXNextHashState _freeBuckets _NXNoEffectFree _hashPrototype _NXPtrHash _isEqualPrototype __Z13futureClassesv _NXCountMapTable __Z13addNamedClassP7class_tPKc _NXMapGet __mapStrHash _NXMapInsert __mapPtrHash __Z10remapClassP7class_t __Z15remappedClassesa _NXMapMember __NXMapMember __mapStrIsEqual __mapPtrIsEqual __getObjc2ClassRefs __getObjc2SuperRefs _sel_preoptimizationValid __getObjc2SelectorRefs _sel_registerNameNoLock ___objc_sel_set_create ___objc_sel_set_add ___objc_sel_set_findBuckets ___objc_sel_set_get __Z9protocolsv __getObjc2ProtocolList _NXMapKeyCopyingInsert __NXMapRehash __getObjc2ProtocolRefs __Z13remapProtocolm __getObjc2NonlazyClassList __Z12realizeClassP7class_t __Z11addSubclassP7class_tS0_ __Z17attachMethodListsP7class_tPP13method_list_tiaPa __Z15fixupMethodListP13method_list_ta _memdup __ZNSt3__113__stable_sortIRN8method_t16SortBySELAddressEN13method_list_t15method_iteratorEEEvT0_S6_T_NS_15iterator_traitsIS6_E1 __Z9addMethodP7class_tP13objc_selectorPFP11objc_objectS3_S1_zEPKca __Z23getMethodNoSuper_nolockP7class_tP13objc_selector __ZN7class_t14setHasCustomRREv __Z20unattachedCategoriesv _NXMapRemove __Z21attachCategoryMethodsP7class_tP13category_listPa _objc_addRegisteredClass _layout_bitmap_create _set_bits _layout_bitmap_free __ZNSt3__116__insertion_sortIRN8method_t16SortBySELAddressEN13method_list_t15method_iteratorEEEvT0_S6_T_ __Z17buildProtocolListP13category_listPK15protocol_list_tPS3_ __Z17buildPropertyListPK15property_list_tP13category_lista __ZNSt3__120get_temporary_bufferI8method_tEENS_4pairIPT_lEEl __ZNSt3__118__stable_sort_moveIRN8method_t16SortBySELAddressEN13method_list_t15method_iteratorEEEvT0_S6_T_NS_15iterator_traitsI __ZNSt3__122__merge_move_constructIRN8method_t16SortBySELAddressEN13method_list_t15method_iteratorES5_EEvT0_S6_T1_S7_PNS_15iter __ZNSt3__119__merge_move_assignIRN8method_t16SortBySELAddressEPS1_S4_N13method_list_t15method_iteratorEEEvT0_S7_T1_S8_T2_T_ _NXPtrIsEqual __getObjc2CategoryList __Z29addUnattachedCategoryForClassP10category_tP7class_tP12_header_info __Z16remethodizeClassP7class_t __Z11flushCachesP7class_t _flush_cache __class_getCache _load_images _load_images_nolock _prepare_load_methods __Z19schedule_class_loadP7class_t _add_class_to_loadable_list __class_getLoadMethod __getObjc2NonlazyCategoryList _call_load_methods +[Protocol load] _objc_lookUpClass _look_up_class _object_getClass _protocol_copyMethodDescriptionList _class_getClassMethod __class_getMeta _look_up_method __cache_getMethod __class_getMethod _method_getTypeEncoding _method_getImplementation _method_getName _class_addMethod _class_getInstanceMethod __Z12flushVtablesP7class_t __Z12updateVtableP7class_ta _class_replaceMethod __Z25_method_setImplementationP7class_tP8method_tPFP11objc_objectS4_P13objc_selectorzE _class_addProtocol _class_conformsToProtocol _objc_setExceptionPreprocessor _objc_setExceptionMatcher _objc_setUncaughtExceptionHandler _objc_setForwardHandler _objc_setEnumerationMutationHandler _objc_collectingEnabled _objc_getFutureClass _objc_assign_strongCast_non_gc _objc_getClass __objc_insert_tagged_isa _objc_msgSend_fixup __objc_fixupMessageRef _objc_msgSend __class_lookupMethodAndLoadCache3 _lookUpMethod _prepareForMethodLookup __class_initialize __class_getNonMetaClass __Z15getNonMetaClassP7class_t __class_getSuperclass __class_isInitialized __class_isInitializing __class_setInitializing __fetchInitializingClassList __objc_fetch_pthread_data _lockForMethodLookup __cache_getImp __class_getMethodNoSuper_nolock _log_and_fill_cache __cache_fill _unlockForMethodLookup _objc_assign_global_non_gc _class_setSuperclass _class_setVersion _objc_msgSend_vtable1 __objc_rootAlloc _class_getInstanceSize __class_getInstanceSize _class_createInstance _object_getClassName __class_getName _object_getIndexedIvars _objc_msgSend_vtable0 __objc_rootAllocWithZone __objc_rootInit _objc_msgSend_vtable3 _objc_assign_ivar_non_gc __objc_rootRetain __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE16FindAndConstructERKS2_ __ZNK4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE15LookupBucketForERKS2_RPNSt3__14pairIS2_mEE __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE16InsertIntoBucketERKS2_RKmPNSt3__14pairIS2_mEE __objc_rootRelease __objc_rootReleaseWasZero __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE4findERKS2_ __finishInitializing __class_setInitialized _NXFreeMapTable _NXResetMapTable __cache_malloc __class_setCache __class_setGrowCache _objc_initializeClassPair __Z33objc_initializeClassPair_internalP10objc_classPKcS0_S0_ _objc_registerClassPair _add_category_to_loadable_list __category_getLoadMethod __category_getClass __class_isLoadable _objc_msgSendSuper2 __objc_autoreleasePoolPush _objc_autoreleasePoolPush __ZN12_GLOBAL__N_119AutoreleasePoolPageC1EPS0_ __ZN12_GLOBAL__N_119AutoreleasePoolPage9fastcheckEb _objc_destructInstance _objc_clear_deallocating __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE5eraseERKS2_ _objc_msgSend_vtable9 __class_shouldGrowCache __cache_collect_free __cache_collect _class_getSuperclass _objc_msgSend_vtable2 _objc_msgSend_vtable13 _objc_msgSend_vtable14 _objc_memmove_collectable _class_respondsToSelector __class_resolveMethod __class_isMetaClass __cache_addForwardEntry __objc_rootDealloc _object_dispose _objc_msgSend_fixedup _class_getName _objc_atomicCompareAndSwapPtrBarrier _objc_msgSend_vtable7 __objc_rootAutorelease __Z22_objc_rootAutorelease2P11objc_object _objc_msgSend_vtable12 _objc_msgSend_vtable11 _objc_msgSend_vtable8 _objc_msgSend_vtable15 __objc_autoreleasePoolPop __ZN12_GLOBAL__N_119AutoreleasePoolPage3popEPv _objc_msgSend_vtable4 _objc_msgSend_vtable10 _objc_retain _objc_atomicCompareAndSwapInstanceVariableBarrier _objc_msgSendSuper2_fixup _objc_msgSendSuper2_fixedup __collecting_in_critical __cache_free_block _class_getVersion _objc_finalizeOnMainThread _class_getImageName __objc_rootZone __Z35_protocol_conformsToProtocol_nolockP10protocol_tS0_ _objc_msgSend_vtable5 _objc_sync_enter _id2data _fetch_cache _objc_sync_exit _gc_enforcer _cache_region_calloc _class_getMethodImplementation _objc_msgSend_stret __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE4growEj __objc_rootHash _objc_assign_weak_non_gc _objc_read_weak_non_gc _sel_getName _method_getArgumentType _encoding_getArgumentType _encoding_getArgumentInfo _SkipFirstType _class_isMetaClass _objc_allocateClassPair __calloc_class _class_getInstanceVariable __class_getVariable __Z7getIvarP7class_tPKc _object_setClass __class_instancesHaveAssociatedObjects _method_getNumberOfArguments _encoding_getNumberOfArguments _method_copyReturnType _encoding_copyReturnType _method_copyArgumentType _encoding_copyArgumentType __objc_rootRetainCount _objc_getAssociatedObject_non_gc __object_get_associative_reference __ZN19AssociationsManagerC2Ev __ZN19AssociationsManager12associationsEv __ZNK23objc_references_support15ObjcPointerHashclEPv _objc_release _objc_removeAssociatedObjects _objc_setProperty_non_gc _objc_getProperty_non_gc _objc_autoreleaseReturnValue _objc_setAssociatedObject_non_gc __object_set_associative_reference __ZN9__gnu_cxx8hash_mapIPvPN23objc_references_support20ObjectAssociationMapENS2_15ObjcPointerHashENS2_16ObjcPointerEqualENS2_13 __ZNSt3__13mapIPvN23objc_references_support15ObjcAssociationENS2_17ObjectPointerLessENS2_13ObjcAllocatorINS_4pairIKS1_S3_EEEEEi __ZNSt3__13mapIPvN23objc_references_support15ObjcAssociationENS2_17ObjectPointerLessENS2_13ObjcAllocatorINS_4pairIKS1_S3_EEEEE1 __ZNSt3__127__tree_balance_after_insertIPNS_16__tree_node_baseIPvEEEEvT_S5_ __class_setInstancesHaveAssociatedObjects _ivar_getTypeEncoding _object_getIvar _ivar_getOffset __class_usesAutomaticRetainRelease __objc_msgForward_internal __objc_msgForward _class_copyProtocolList _protocol_getMethodDescription __protocol_getMethod __Z26_protocol_getMethod_nolockP10protocol_tP13objc_selectoraa _method_getDescription _ivar_getName _objc_addExceptionHandler _read_address _read_sleb _fetch_handler_list _objc_removeExceptionHandler _SubtypeUntil _objc_collecting_enabled _objc_msgSend_vtable6 _objc_is_finalized _class_copyPropertyList _property_getName _property_getAttributes _objc_msgSendSuper2_stret _object_setInstanceVariable _object_setIvar _objc_assign_ivar __ZN12_GLOBAL__N_119AutoreleasePoolPage15autoreleaseSlowEP11objc_object _objc_atomicCompareAndSwapPtr _objc_atomicCompareAndSwapGlobalBarrier _sel_getUid __ZN12_GLOBAL__N_119AutoreleasePoolPage11tls_deallocEPv __ZN12_GLOBAL__N_119AutoreleasePoolPage4killEv __objc_constructOrFree _object_cxxConstruct _object_cxxConstructFromClass __class_hasCxxStructors _lookupMethodInClassAndLoadCache __class_getMethodNoSuper _object_cxxDestruct _object_cxxDestructFromClass _class_copyIvarList __objc_rootRetain_slow __objc_rootReleaseWasZero_slow _object_copy __Z20_object_copyFromZoneP11objc_objectmPv __objc_pthread_destroyspecific __destroyInitializingClassList __destroySyncCache __destroyAltHandlerList __object_remove_assocations __ZNSt3__114__split_bufferIN23objc_references_support15ObjcAssociationERNS1_13ObjcAllocatorIS2_EEE9push_backERKS2_ __ZNSt3__16vectorIN23objc_references_support15ObjcAssociationENS1_13ObjcAllocatorIS2_EEE26__swap_out_circular_bufferERNS_14__sp __ZNSt3__112__hash_tableINS_4pairIPvPN23objc_references_support20ObjectAssociationMapEEEN9__gnu_cxx17__hash_map_hasherIS6_NS3_1 __ZNSt3__114__split_bufferIN23objc_references_support15ObjcAssociationERNS1_13ObjcAllocatorIS2_EEE10push_frontERKS2_ __ZNSt3__16__treeINS_4pairIPvN23objc_references_support15ObjcAssociationEEENS_19__map_value_compareIS2_S4_NS3_17ObjectPointerLe __ZNSt3__113__tree_removeIPNS_16__tree_node_baseIPvEEEEvT_S5_ ================================================ FILE: InterView-obj-isa-class/objc4-750/markgc.cpp ================================================ /* * Copyright (c) 2007-2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Some OS X SDKs don't define these. #ifndef CPU_TYPE_ARM #define CPU_TYPE_ARM ((cpu_type_t) 12) #endif #ifndef CPU_ARCH_ABI64 #define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */ #endif #ifndef CPU_TYPE_ARM64 #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) #endif // File abstraction taken from ld64/FileAbstraction.hpp // and ld64/MachOFileAbstraction.hpp. #ifdef __OPTIMIZE__ #define INLINE __attribute__((always_inline)) #else #define INLINE #endif // // This abstraction layer is for use with file formats that have 64-bit/32-bit and Big-Endian/Little-Endian variants // // For example: to make a utility that handles 32-bit little enidan files use: Pointer32 // // // get16() read a 16-bit number from an E endian struct // set16() write a 16-bit number to an E endian struct // get32() read a 32-bit number from an E endian struct // set32() write a 32-bit number to an E endian struct // get64() read a 64-bit number from an E endian struct // set64() write a 64-bit number to an E endian struct // // getBits() read a bit field from an E endian struct (bitCount=number of bits in field, firstBit=bit index of field) // setBits() write a bit field to an E endian struct (bitCount=number of bits in field, firstBit=bit index of field) // // getBitsRaw() read a bit field from a struct with native endianness // setBitsRaw() write a bit field from a struct with native endianness // class BigEndian { public: static uint16_t get16(const uint16_t& from) INLINE { return OSReadBigInt16(&from, 0); } static void set16(uint16_t& into, uint16_t value) INLINE { OSWriteBigInt16(&into, 0, value); } static uint32_t get32(const uint32_t& from) INLINE { return OSReadBigInt32(&from, 0); } static void set32(uint32_t& into, uint32_t value) INLINE { OSWriteBigInt32(&into, 0, value); } static uint64_t get64(const uint64_t& from) INLINE { return OSReadBigInt64(&from, 0); } static void set64(uint64_t& into, uint64_t value) INLINE { OSWriteBigInt64(&into, 0, value); } static uint32_t getBits(const uint32_t& from, uint8_t firstBit, uint8_t bitCount) INLINE { return getBitsRaw(get32(from), firstBit, bitCount); } static void setBits(uint32_t& into, uint32_t value, uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = get32(into); setBitsRaw(temp, value, firstBit, bitCount); set32(into, temp); } static uint32_t getBitsRaw(const uint32_t& from, uint8_t firstBit, uint8_t bitCount) INLINE { return ((from >> (32-firstBit-bitCount)) & ((1<> firstBit) & ((1< class Pointer32 { public: typedef uint32_t uint_t; typedef int32_t sint_t; typedef _E E; static uint64_t getP(const uint_t& from) INLINE { return _E::get32(from); } static void setP(uint_t& into, uint64_t value) INLINE { _E::set32(into, value); } }; template class Pointer64 { public: typedef uint64_t uint_t; typedef int64_t sint_t; typedef _E E; static uint64_t getP(const uint_t& from) INLINE { return _E::get64(from); } static void setP(uint_t& into, uint64_t value) INLINE { _E::set64(into, value); } }; // // mach-o file header // template struct macho_header_content {}; template <> struct macho_header_content > { mach_header fields; }; template <> struct macho_header_content > { mach_header_64 fields; }; template <> struct macho_header_content > { mach_header fields; }; template <> struct macho_header_content > { mach_header_64 fields; }; template class macho_header { public: uint32_t magic() const INLINE { return E::get32(header.fields.magic); } void set_magic(uint32_t value) INLINE { E::set32(header.fields.magic, value); } uint32_t cputype() const INLINE { return E::get32(header.fields.cputype); } void set_cputype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cputype, value); } uint32_t cpusubtype() const INLINE { return E::get32(header.fields.cpusubtype); } void set_cpusubtype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cpusubtype, value); } uint32_t filetype() const INLINE { return E::get32(header.fields.filetype); } void set_filetype(uint32_t value) INLINE { E::set32(header.fields.filetype, value); } uint32_t ncmds() const INLINE { return E::get32(header.fields.ncmds); } void set_ncmds(uint32_t value) INLINE { E::set32(header.fields.ncmds, value); } uint32_t sizeofcmds() const INLINE { return E::get32(header.fields.sizeofcmds); } void set_sizeofcmds(uint32_t value) INLINE { E::set32(header.fields.sizeofcmds, value); } uint32_t flags() const INLINE { return E::get32(header.fields.flags); } void set_flags(uint32_t value) INLINE { E::set32(header.fields.flags, value); } uint32_t reserved() const INLINE { return E::get32(header.fields.reserved); } void set_reserved(uint32_t value) INLINE { E::set32(header.fields.reserved, value); } typedef typename P::E E; private: macho_header_content

header; }; // // mach-o load command // template class macho_load_command { public: uint32_t cmd() const INLINE { return E::get32(command.cmd); } void set_cmd(uint32_t value) INLINE { E::set32(command.cmd, value); } uint32_t cmdsize() const INLINE { return E::get32(command.cmdsize); } void set_cmdsize(uint32_t value) INLINE { E::set32(command.cmdsize, value); } typedef typename P::E E; private: load_command command; }; // // mach-o segment load command // template struct macho_segment_content {}; template <> struct macho_segment_content > { segment_command fields; enum { CMD = LC_SEGMENT }; }; template <> struct macho_segment_content > { segment_command_64 fields; enum { CMD = LC_SEGMENT_64 }; }; template <> struct macho_segment_content > { segment_command fields; enum { CMD = LC_SEGMENT }; }; template <> struct macho_segment_content > { segment_command_64 fields; enum { CMD = LC_SEGMENT_64 }; }; template class macho_segment_command { public: uint32_t cmd() const INLINE { return E::get32(segment.fields.cmd); } void set_cmd(uint32_t value) INLINE { E::set32(segment.fields.cmd, value); } uint32_t cmdsize() const INLINE { return E::get32(segment.fields.cmdsize); } void set_cmdsize(uint32_t value) INLINE { E::set32(segment.fields.cmdsize, value); } const char* segname() const INLINE { return segment.fields.segname; } void set_segname(const char* value) INLINE { strncpy(segment.fields.segname, value, 16); } uint64_t vmaddr() const INLINE { return P::getP(segment.fields.vmaddr); } void set_vmaddr(uint64_t value) INLINE { P::setP(segment.fields.vmaddr, value); } uint64_t vmsize() const INLINE { return P::getP(segment.fields.vmsize); } void set_vmsize(uint64_t value) INLINE { P::setP(segment.fields.vmsize, value); } uint64_t fileoff() const INLINE { return P::getP(segment.fields.fileoff); } void set_fileoff(uint64_t value) INLINE { P::setP(segment.fields.fileoff, value); } uint64_t filesize() const INLINE { return P::getP(segment.fields.filesize); } void set_filesize(uint64_t value) INLINE { P::setP(segment.fields.filesize, value); } uint32_t maxprot() const INLINE { return E::get32(segment.fields.maxprot); } void set_maxprot(uint32_t value) INLINE { E::set32((uint32_t&)segment.fields.maxprot, value); } uint32_t initprot() const INLINE { return E::get32(segment.fields.initprot); } void set_initprot(uint32_t value) INLINE { E::set32((uint32_t&)segment.fields.initprot, value); } uint32_t nsects() const INLINE { return E::get32(segment.fields.nsects); } void set_nsects(uint32_t value) INLINE { E::set32(segment.fields.nsects, value); } uint32_t flags() const INLINE { return E::get32(segment.fields.flags); } void set_flags(uint32_t value) INLINE { E::set32(segment.fields.flags, value); } enum { CMD = macho_segment_content

::CMD }; typedef typename P::E E; private: macho_segment_content

segment; }; // // mach-o section // template struct macho_section_content {}; template <> struct macho_section_content > { section fields; }; template <> struct macho_section_content > { section_64 fields; }; template <> struct macho_section_content > { section fields; }; template <> struct macho_section_content > { section_64 fields; }; template class macho_section { public: const char* sectname() const INLINE { return section.fields.sectname; } void set_sectname(const char* value) INLINE { strncpy(section.fields.sectname, value, 16); } const char* segname() const INLINE { return section.fields.segname; } void set_segname(const char* value) INLINE { strncpy(section.fields.segname, value, 16); } uint64_t addr() const INLINE { return P::getP(section.fields.addr); } void set_addr(uint64_t value) INLINE { P::setP(section.fields.addr, value); } uint64_t size() const INLINE { return P::getP(section.fields.size); } void set_size(uint64_t value) INLINE { P::setP(section.fields.size, value); } uint32_t offset() const INLINE { return E::get32(section.fields.offset); } void set_offset(uint32_t value) INLINE { E::set32(section.fields.offset, value); } uint32_t align() const INLINE { return E::get32(section.fields.align); } void set_align(uint32_t value) INLINE { E::set32(section.fields.align, value); } uint32_t reloff() const INLINE { return E::get32(section.fields.reloff); } void set_reloff(uint32_t value) INLINE { E::set32(section.fields.reloff, value); } uint32_t nreloc() const INLINE { return E::get32(section.fields.nreloc); } void set_nreloc(uint32_t value) INLINE { E::set32(section.fields.nreloc, value); } uint32_t flags() const INLINE { return E::get32(section.fields.flags); } void set_flags(uint32_t value) INLINE { E::set32(section.fields.flags, value); } uint32_t reserved1() const INLINE { return E::get32(section.fields.reserved1); } void set_reserved1(uint32_t value) INLINE { E::set32(section.fields.reserved1, value); } uint32_t reserved2() const INLINE { return E::get32(section.fields.reserved2); } void set_reserved2(uint32_t value) INLINE { E::set32(section.fields.reserved2, value); } typedef typename P::E E; private: macho_section_content

section; }; static bool debug = true; bool processFile(const char *filename); int main(int argc, const char *argv[]) { for (int i = 1; i < argc; ++i) { if (!processFile(argv[i])) return 1; } return 0; } struct imageinfo { uint32_t version; uint32_t flags; }; // Segment and section names are 16 bytes and may be un-terminated. bool segnameEquals(const char *lhs, const char *rhs) { return 0 == strncmp(lhs, rhs, 16); } bool segnameStartsWith(const char *segname, const char *prefix) { return 0 == strncmp(segname, prefix, strlen(prefix)); } bool sectnameEquals(const char *lhs, const char *rhs) { return segnameEquals(lhs, rhs); } template void dosect(uint8_t *start, macho_section

*sect) { if (debug) printf("section %.16s from segment %.16s\n", sect->sectname(), sect->segname()); // Strip S_MOD_INIT/TERM_FUNC_POINTERS. We don't want dyld to call // our init funcs because it is too late, and we don't want anyone to // call our term funcs ever. if (segnameStartsWith(sect->segname(), "__DATA") && sectnameEquals(sect->sectname(), "__mod_init_func")) { // section type 0 is S_REGULAR sect->set_flags(sect->flags() & ~SECTION_TYPE); sect->set_sectname("__objc_init_func"); if (debug) printf("disabled __mod_init_func section\n"); } if (segnameStartsWith(sect->segname(), "__DATA") && sectnameEquals(sect->sectname(), "__mod_term_func")) { // section type 0 is S_REGULAR sect->set_flags(sect->flags() & ~SECTION_TYPE); sect->set_sectname("__objc_term_func"); if (debug) printf("disabled __mod_term_func section\n"); } } template void doseg(uint8_t *start, macho_segment_command

*seg) { if (debug) printf("segment name: %.16s, nsects %u\n", seg->segname(), seg->nsects()); macho_section

*sect = (macho_section

*)(seg + 1); for (uint32_t i = 0; i < seg->nsects(); ++i) { dosect(start, §[i]); } } template bool parse_macho(uint8_t *buffer) { macho_header

* mh = (macho_header

*)buffer; uint8_t *cmds = (uint8_t *)(mh + 1); for (uint32_t c = 0; c < mh->ncmds(); c++) { macho_load_command

* cmd = (macho_load_command

*)cmds; cmds += cmd->cmdsize(); if (cmd->cmd() == LC_SEGMENT || cmd->cmd() == LC_SEGMENT_64) { doseg(buffer, (macho_segment_command

*)cmd); } } return true; } bool parse_macho(uint8_t *buffer) { uint32_t magic = *(uint32_t *)buffer; switch (magic) { case MH_MAGIC_64: return parse_macho>(buffer); case MH_MAGIC: return parse_macho>(buffer); case MH_CIGAM_64: return parse_macho>(buffer); case MH_CIGAM: return parse_macho>(buffer); default: printf("file is not mach-o (magic %x)\n", magic); return false; } } bool parse_fat(uint8_t *buffer, size_t size) { uint32_t magic; if (size < sizeof(magic)) { printf("file is too small\n"); return false; } magic = *(uint32_t *)buffer; if (magic != FAT_MAGIC && magic != FAT_CIGAM) { /* Not a fat file */ return parse_macho(buffer); } else { struct fat_header *fh; uint32_t fat_magic, fat_nfat_arch; struct fat_arch *archs; if (size < sizeof(struct fat_header)) { printf("file is too small\n"); return false; } fh = (struct fat_header *)buffer; fat_magic = OSSwapBigToHostInt32(fh->magic); fat_nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch); size_t fat_arch_size; // fat_nfat_arch * sizeof(struct fat_arch) + sizeof(struct fat_header) if (os_mul_and_add_overflow(fat_nfat_arch, sizeof(struct fat_arch), sizeof(struct fat_header), &fat_arch_size)) { printf("too many fat archs\n"); return false; } if (size < fat_arch_size) { printf("file is too small\n"); return false; } archs = (struct fat_arch *)(buffer + sizeof(struct fat_header)); /* Special case hidden CPU_TYPE_ARM64 */ size_t fat_arch_plus_one_size; if (os_add_overflow(fat_arch_size, sizeof(struct fat_arch), &fat_arch_plus_one_size)) { printf("too many fat archs\n"); return false; } if (size >= fat_arch_plus_one_size) { if (fat_nfat_arch > 0 && OSSwapBigToHostInt32(archs[fat_nfat_arch].cputype) == CPU_TYPE_ARM64) { fat_nfat_arch++; } } /* End special case hidden CPU_TYPE_ARM64 */ if (debug) printf("%d fat architectures\n", fat_nfat_arch); for (uint32_t i = 0; i < fat_nfat_arch; i++) { uint32_t arch_cputype = OSSwapBigToHostInt32(archs[i].cputype); uint32_t arch_cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype); uint32_t arch_offset = OSSwapBigToHostInt32(archs[i].offset); uint32_t arch_size = OSSwapBigToHostInt32(archs[i].size); if (debug) printf("cputype %d cpusubtype %d\n", arch_cputype, arch_cpusubtype); /* Check that slice data is after all fat headers and archs */ if (arch_offset < fat_arch_size) { printf("file is badly formed\n"); return false; } /* Check that the slice ends before the file does */ if (arch_offset > size) { printf("file is badly formed\n"); return false; } if (arch_size > size) { printf("file is badly formed\n"); return false; } if (arch_offset > (size - arch_size)) { printf("file is badly formed\n"); return false; } bool ok = parse_macho(buffer + arch_offset); if (!ok) return false; } return true; } } bool processFile(const char *filename) { if (debug) printf("file %s\n", filename); int fd = open(filename, O_RDWR); if (fd < 0) { printf("open %s: %s\n", filename, strerror(errno)); return false; } struct stat st; if (fstat(fd, &st) < 0) { printf("fstat %s: %s\n", filename, strerror(errno)); return false; } void *buffer = mmap(NULL, (size_t)st.st_size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0); if (buffer == MAP_FAILED) { printf("mmap %s: %s\n", filename, strerror(errno)); return false; } bool result = parse_fat((uint8_t *)buffer, (size_t)st.st_size); munmap(buffer, (size_t)st.st_size); close(fd); return result; } ================================================ FILE: InterView-obj-isa-class/objc4-750/objc.sln ================================================  Microsoft Visual Studio Solution File, Format Version 9.00 # Visual C++ Express 2005 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "objc", "objc.vcproj", "{B3408263-0CF1-47BE-83CC-56070EFC9BC1}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "objcrt", "objcrt\objcrt.vcproj", "{E38C1996-8B3D-4050-A4B2-DC85957B047D}" ProjectSection(ProjectDependencies) = postProject {B3408263-0CF1-47BE-83CC-56070EFC9BC1} = {B3408263-0CF1-47BE-83CC-56070EFC9BC1} EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 DebugDLL|Win32 = DebugDLL|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B3408263-0CF1-47BE-83CC-56070EFC9BC1}.Debug|Win32.ActiveCfg = Debug|Win32 {B3408263-0CF1-47BE-83CC-56070EFC9BC1}.Debug|Win32.Build.0 = Debug|Win32 {B3408263-0CF1-47BE-83CC-56070EFC9BC1}.DebugDLL|Win32.ActiveCfg = DebugDLL|Win32 {B3408263-0CF1-47BE-83CC-56070EFC9BC1}.DebugDLL|Win32.Build.0 = DebugDLL|Win32 {B3408263-0CF1-47BE-83CC-56070EFC9BC1}.Release|Win32.ActiveCfg = Release|Win32 {B3408263-0CF1-47BE-83CC-56070EFC9BC1}.Release|Win32.Build.0 = Release|Win32 {E38C1996-8B3D-4050-A4B2-DC85957B047D}.Debug|Win32.ActiveCfg = Debug|Win32 {E38C1996-8B3D-4050-A4B2-DC85957B047D}.Debug|Win32.Build.0 = Debug|Win32 {E38C1996-8B3D-4050-A4B2-DC85957B047D}.DebugDLL|Win32.ActiveCfg = Debug|Win32 {E38C1996-8B3D-4050-A4B2-DC85957B047D}.DebugDLL|Win32.Build.0 = Debug|Win32 {E38C1996-8B3D-4050-A4B2-DC85957B047D}.Release|Win32.ActiveCfg = Release|Win32 {E38C1996-8B3D-4050-A4B2-DC85957B047D}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: InterView-obj-isa-class/objc4-750/objc.vcproj ================================================ ================================================ FILE: InterView-obj-isa-class/objc4-750/objc.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXAggregateTarget section */ 837F67A81A771F63004D34FA /* objc-simulator */ = { isa = PBXAggregateTarget; buildConfigurationList = 837F67A91A771F63004D34FA /* Build configuration list for PBXAggregateTarget "objc-simulator" */; buildPhases = ( ); dependencies = ( 837F67AD1A771F6E004D34FA /* PBXTargetDependency */, ); name = "objc-simulator"; productName = objc_simulator; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 393CEAC00DC69E3E000B69DE /* objc-references.mm in Sources */ = {isa = PBXBuildFile; fileRef = 393CEABF0DC69E3E000B69DE /* objc-references.mm */; }; 393CEAC60DC69E67000B69DE /* objc-references.h in Headers */ = {isa = PBXBuildFile; fileRef = 393CEAC50DC69E67000B69DE /* objc-references.h */; }; 39ABD72312F0B61800D1054C /* objc-weak.h in Headers */ = {isa = PBXBuildFile; fileRef = 39ABD71F12F0B61800D1054C /* objc-weak.h */; }; 39ABD72412F0B61800D1054C /* objc-weak.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39ABD72012F0B61800D1054C /* objc-weak.mm */; }; 7593EC58202248E50046AB96 /* objc-object.h in Headers */ = {isa = PBXBuildFile; fileRef = 7593EC57202248DF0046AB96 /* objc-object.h */; }; 75A9504F202BAA0600D7D56F /* objc-locks-new.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A9504E202BAA0300D7D56F /* objc-locks-new.h */; }; 75A95051202BAA9A00D7D56F /* objc-locks.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A95050202BAA9A00D7D56F /* objc-locks.h */; }; 75A95053202BAC4100D7D56F /* objc-lockdebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A95052202BAC4100D7D56F /* objc-lockdebug.h */; }; 8306440920D24A5D00E356D2 /* objc-block-trampolines.h in Headers */ = {isa = PBXBuildFile; fileRef = 8306440620D24A3E00E356D2 /* objc-block-trampolines.h */; settings = {ATTRIBUTES = (Private, ); }; }; 830F2A740D737FB800392440 /* objc-msg-arm.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A690D737FB800392440 /* objc-msg-arm.s */; }; 830F2A750D737FB900392440 /* objc-msg-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A6A0D737FB800392440 /* objc-msg-i386.s */; }; 830F2A7D0D737FBB00392440 /* objc-msg-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A720D737FB800392440 /* objc-msg-x86_64.s */; }; 830F2A950D73876100392440 /* objc-accessors.mm in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A930D73876100392440 /* objc-accessors.mm */; }; 830F2A980D738DC200392440 /* hashtable.h in Headers */ = {isa = PBXBuildFile; fileRef = 830F2A970D738DC200392440 /* hashtable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83112ED40F00599600A5FBAF /* objc-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 83112ED30F00599600A5FBAF /* objc-internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 831C85D50E10CF850066E64C /* objc-os.h in Headers */ = {isa = PBXBuildFile; fileRef = 831C85D30E10CF850066E64C /* objc-os.h */; }; 831C85D60E10CF850066E64C /* objc-os.mm in Sources */ = {isa = PBXBuildFile; fileRef = 831C85D40E10CF850066E64C /* objc-os.mm */; }; 834266D80E665A8B002E4DA2 /* objc-gdb.h in Headers */ = {isa = PBXBuildFile; fileRef = 834266D70E665A8B002E4DA2 /* objc-gdb.h */; settings = {ATTRIBUTES = (Private, ); }; }; 834DF8B715993EE1002F2BC9 /* objc-sel-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 834DF8B615993EE1002F2BC9 /* objc-sel-old.mm */; }; 834EC0A411614167009B2563 /* objc-abi.h in Headers */ = {isa = PBXBuildFile; fileRef = 834EC0A311614167009B2563 /* objc-abi.h */; settings = {ATTRIBUTES = (Private, ); }; }; 83725F4A14CA5BFA0014370E /* objc-opt.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83725F4914CA5BFA0014370E /* objc-opt.mm */; }; 838485BF0D6D687300CEA253 /* hashtable2.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485B70D6D687300CEA253 /* hashtable2.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838485C00D6D687300CEA253 /* hashtable2.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485B80D6D687300CEA253 /* hashtable2.mm */; }; 838485C30D6D687300CEA253 /* maptable.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485BB0D6D687300CEA253 /* maptable.h */; settings = {ATTRIBUTES = (Private, ); }; }; 838485C40D6D687300CEA253 /* maptable.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485BC0D6D687300CEA253 /* maptable.mm */; }; 838485EF0D6D68A200CEA253 /* objc-api.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485C80D6D68A200CEA253 /* objc-api.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838485F00D6D68A200CEA253 /* objc-auto.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485C90D6D68A200CEA253 /* objc-auto.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838485F10D6D68A200CEA253 /* objc-auto.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485CA0D6D68A200CEA253 /* objc-auto.mm */; settings = {COMPILER_FLAGS = "-fexceptions"; }; }; 838485F20D6D68A200CEA253 /* objc-cache.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485CB0D6D68A200CEA253 /* objc-cache.mm */; }; 838485F30D6D68A200CEA253 /* objc-class-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485CC0D6D68A200CEA253 /* objc-class-old.mm */; }; 838485F40D6D68A200CEA253 /* objc-class.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485CD0D6D68A200CEA253 /* objc-class.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838485F50D6D68A200CEA253 /* objc-class.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485CE0D6D68A200CEA253 /* objc-class.mm */; }; 838485F60D6D68A200CEA253 /* objc-config.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485CF0D6D68A200CEA253 /* objc-config.h */; }; 838485F70D6D68A200CEA253 /* objc-errors.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485D00D6D68A200CEA253 /* objc-errors.mm */; }; 838485F80D6D68A200CEA253 /* objc-exception.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485D10D6D68A200CEA253 /* objc-exception.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838485F90D6D68A200CEA253 /* objc-exception.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485D20D6D68A200CEA253 /* objc-exception.mm */; settings = {COMPILER_FLAGS = "-fexceptions"; }; }; 838485FA0D6D68A200CEA253 /* objc-file.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485D30D6D68A200CEA253 /* objc-file.mm */; }; 838485FB0D6D68A200CEA253 /* objc-initialize.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485D40D6D68A200CEA253 /* objc-initialize.h */; }; 838485FC0D6D68A200CEA253 /* objc-initialize.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485D50D6D68A200CEA253 /* objc-initialize.mm */; }; 838485FD0D6D68A200CEA253 /* objc-layout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485D60D6D68A200CEA253 /* objc-layout.mm */; }; 838485FE0D6D68A200CEA253 /* objc-load.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485D70D6D68A200CEA253 /* objc-load.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838485FF0D6D68A200CEA253 /* objc-load.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485D80D6D68A200CEA253 /* objc-load.mm */; }; 838486000D6D68A200CEA253 /* objc-loadmethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485D90D6D68A200CEA253 /* objc-loadmethod.h */; }; 838486010D6D68A200CEA253 /* objc-loadmethod.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485DA0D6D68A200CEA253 /* objc-loadmethod.mm */; }; 838486020D6D68A200CEA253 /* objc-lockdebug.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485DB0D6D68A200CEA253 /* objc-lockdebug.mm */; settings = {COMPILER_FLAGS = "-Os"; }; }; 838486030D6D68A200CEA253 /* objc-private.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485DC0D6D68A200CEA253 /* objc-private.h */; }; 838486070D6D68A200CEA253 /* objc-runtime-new.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485E00D6D68A200CEA253 /* objc-runtime-new.h */; }; 838486080D6D68A200CEA253 /* objc-runtime-new.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485E10D6D68A200CEA253 /* objc-runtime-new.mm */; }; 838486090D6D68A200CEA253 /* objc-runtime-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485E20D6D68A200CEA253 /* objc-runtime-old.mm */; }; 8384860A0D6D68A200CEA253 /* objc-runtime.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485E30D6D68A200CEA253 /* objc-runtime.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8384860B0D6D68A200CEA253 /* objc-runtime.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485E40D6D68A200CEA253 /* objc-runtime.mm */; }; 8384860C0D6D68A200CEA253 /* objc-sel-set.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485E50D6D68A200CEA253 /* objc-sel-set.h */; }; 8384860D0D6D68A200CEA253 /* objc-sel-set.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485E60D6D68A200CEA253 /* objc-sel-set.mm */; }; 8384860F0D6D68A200CEA253 /* objc-sel.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485E80D6D68A200CEA253 /* objc-sel.mm */; }; 838486100D6D68A200CEA253 /* objc-sync.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485E90D6D68A200CEA253 /* objc-sync.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838486110D6D68A200CEA253 /* objc-sync.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485EA0D6D68A200CEA253 /* objc-sync.mm */; }; 838486120D6D68A200CEA253 /* objc-typeencoding.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485EB0D6D68A200CEA253 /* objc-typeencoding.mm */; }; 838486130D6D68A200CEA253 /* objc.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485EC0D6D68A200CEA253 /* objc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838486140D6D68A200CEA253 /* Object.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485ED0D6D68A200CEA253 /* Object.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838486150D6D68A200CEA253 /* Object.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485EE0D6D68A200CEA253 /* Object.mm */; }; 8384861E0D6D68A800CEA253 /* Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 838486180D6D68A800CEA253 /* Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8384861F0D6D68A800CEA253 /* Protocol.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838486190D6D68A800CEA253 /* Protocol.mm */; }; 838486200D6D68A800CEA253 /* runtime.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384861A0D6D68A800CEA253 /* runtime.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838486250D6D68F000CEA253 /* List.m in Sources */ = {isa = PBXBuildFile; fileRef = 838486230D6D68F000CEA253 /* List.m */; }; 838486260D6D68F000CEA253 /* List.h in Headers */ = {isa = PBXBuildFile; fileRef = 838486240D6D68F000CEA253 /* List.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838486280D6D6A2400CEA253 /* message.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485BD0D6D687300CEA253 /* message.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83A4AEDC1EA0840800ACADDE /* module.modulemap in Headers */ = {isa = PBXBuildFile; fileRef = 83A4AED71EA06D9D00ACADDE /* module.modulemap */; settings = {ATTRIBUTES = (Public, ); }; }; 83A4AEDE1EA08C7200ACADDE /* ObjectiveC.apinotes in Headers */ = {isa = PBXBuildFile; fileRef = 83A4AEDD1EA08C5700ACADDE /* ObjectiveC.apinotes */; settings = {ATTRIBUTES = (Public, ); }; }; 83B1A8BE0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = 83B1A8BC0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s */; }; 83BE02E40FCCB23400661494 /* objc-file-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83BE02E30FCCB23400661494 /* objc-file-old.mm */; }; 83BE02E80FCCB24D00661494 /* objc-file-old.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BE02E50FCCB24D00661494 /* objc-file-old.h */; }; 83BE02E90FCCB24D00661494 /* objc-file.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BE02E60FCCB24D00661494 /* objc-file.h */; }; 83BE02EA0FCCB24D00661494 /* objc-runtime-old.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BE02E70FCCB24D00661494 /* objc-runtime-old.h */; }; 83C9C3391668B50E00F4E544 /* objc-msg-simulator-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = 83C9C3381668B50E00F4E544 /* objc-msg-simulator-x86_64.s */; }; 83D49E4F13C7C84F0057F1DD /* objc-msg-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 83D49E4E13C7C84F0057F1DD /* objc-msg-arm64.s */; }; 83EB007B121C9EC200B92C16 /* objc-sel-table.s in Sources */ = {isa = PBXBuildFile; fileRef = 83EB007A121C9EC200B92C16 /* objc-sel-table.s */; }; 83EF5E9820D2298400F486A4 /* objc-blocktramps-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */; }; 83EF5E9920D2298400F486A4 /* objc-blocktramps-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */; }; 83EF5E9C20D2299E00F486A4 /* objc-blocktramps-arm.s in Sources */ = {isa = PBXBuildFile; fileRef = 8383A3A1122600E9009290B8 /* objc-blocktramps-arm.s */; }; 83F4B52815E843B100E0926F /* NSObjCRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F4B52615E843B100E0926F /* NSObjCRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83F4B52915E843B100E0926F /* NSObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F4B52715E843B100E0926F /* NSObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83F550DF155E030800E95D3B /* objc-cache-old.mm */; }; 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = 87BB4E900EC39633005D08E1 /* objc-probes.d */; }; 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9672F7ED14D5F488007CEC96 /* NSObject.mm */; }; E8923DA5116AB2820071B552 /* objc-block-trampolines.mm in Sources */ = {isa = PBXBuildFile; fileRef = E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */; }; F9BCC71B205C68E800DD9AFC /* objc-blocktramps-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 837F67AC1A771F6E004D34FA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; proxyType = 1; remoteGlobalIDString = D2AAC0620554660B00DB518D; remoteInfo = objc; }; F9BCC728205C6A0900DD9AFC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; proxyType = 1; remoteGlobalIDString = F9BCC6CA205C68E800DD9AFC; remoteInfo = "objc-trampolines"; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 393CEABF0DC69E3E000B69DE /* objc-references.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-references.mm"; path = "runtime/objc-references.mm"; sourceTree = ""; }; 393CEAC50DC69E67000B69DE /* objc-references.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-references.h"; path = "runtime/objc-references.h"; sourceTree = ""; }; 39ABD71F12F0B61800D1054C /* objc-weak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-weak.h"; path = "runtime/objc-weak.h"; sourceTree = ""; }; 39ABD72012F0B61800D1054C /* objc-weak.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-weak.mm"; path = "runtime/objc-weak.mm"; sourceTree = ""; }; 7593EC57202248DF0046AB96 /* objc-object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-object.h"; path = "runtime/objc-object.h"; sourceTree = ""; }; 75A9504E202BAA0300D7D56F /* objc-locks-new.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-locks-new.h"; path = "runtime/objc-locks-new.h"; sourceTree = ""; }; 75A95050202BAA9A00D7D56F /* objc-locks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-locks.h"; path = "runtime/objc-locks.h"; sourceTree = ""; }; 75A95052202BAC4100D7D56F /* objc-lockdebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-lockdebug.h"; path = "runtime/objc-lockdebug.h"; sourceTree = ""; }; 8306440620D24A3E00E356D2 /* objc-block-trampolines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-block-trampolines.h"; path = "runtime/objc-block-trampolines.h"; sourceTree = ""; }; 830F2A690D737FB800392440 /* objc-msg-arm.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-arm.s"; path = "runtime/Messengers.subproj/objc-msg-arm.s"; sourceTree = ""; }; 830F2A6A0D737FB800392440 /* objc-msg-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-i386.s"; path = "runtime/Messengers.subproj/objc-msg-i386.s"; sourceTree = ""; }; 830F2A720D737FB800392440 /* objc-msg-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-x86_64.s"; path = "runtime/Messengers.subproj/objc-msg-x86_64.s"; sourceTree = ""; tabWidth = 8; usesTabs = 1; }; 830F2A930D73876100392440 /* objc-accessors.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-accessors.mm"; path = "runtime/objc-accessors.mm"; sourceTree = ""; }; 830F2A970D738DC200392440 /* hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = runtime/hashtable.h; sourceTree = ""; }; 830F2AA50D7394C200392440 /* markgc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = markgc.cpp; sourceTree = ""; }; 83112ED30F00599600A5FBAF /* objc-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-internal.h"; path = "runtime/objc-internal.h"; sourceTree = ""; }; 831C85D30E10CF850066E64C /* objc-os.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-os.h"; path = "runtime/objc-os.h"; sourceTree = ""; }; 831C85D40E10CF850066E64C /* objc-os.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-os.mm"; path = "runtime/objc-os.mm"; sourceTree = ""; }; 834266D70E665A8B002E4DA2 /* objc-gdb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-gdb.h"; path = "runtime/objc-gdb.h"; sourceTree = ""; }; 834DF8B615993EE1002F2BC9 /* objc-sel-old.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-sel-old.mm"; path = "runtime/objc-sel-old.mm"; sourceTree = ""; }; 834EC0A311614167009B2563 /* objc-abi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-abi.h"; path = "runtime/objc-abi.h"; sourceTree = ""; }; 83725F4914CA5BFA0014370E /* objc-opt.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-opt.mm"; path = "runtime/objc-opt.mm"; sourceTree = ""; }; 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-arm64.s"; path = "runtime/objc-blocktramps-arm64.s"; sourceTree = ""; }; 8383A3A1122600E9009290B8 /* objc-blocktramps-arm.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-arm.s"; path = "runtime/objc-blocktramps-arm.s"; sourceTree = ""; }; 838485B30D6D682B00CEA253 /* libobjc.order */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = libobjc.order; sourceTree = ""; }; 838485B40D6D683300CEA253 /* APPLE_LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = APPLE_LICENSE; sourceTree = ""; }; 838485B50D6D683300CEA253 /* ReleaseNotes.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = ReleaseNotes.rtf; sourceTree = ""; }; 838485B70D6D687300CEA253 /* hashtable2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hashtable2.h; path = runtime/hashtable2.h; sourceTree = ""; }; 838485B80D6D687300CEA253 /* hashtable2.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = hashtable2.mm; path = runtime/hashtable2.mm; sourceTree = ""; }; 838485BB0D6D687300CEA253 /* maptable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = maptable.h; path = runtime/maptable.h; sourceTree = ""; }; 838485BC0D6D687300CEA253 /* maptable.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = maptable.mm; path = runtime/maptable.mm; sourceTree = ""; }; 838485BD0D6D687300CEA253 /* message.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = message.h; path = runtime/message.h; sourceTree = ""; }; 838485C80D6D68A200CEA253 /* objc-api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-api.h"; path = "runtime/objc-api.h"; sourceTree = ""; }; 838485C90D6D68A200CEA253 /* objc-auto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-auto.h"; path = "runtime/objc-auto.h"; sourceTree = ""; }; 838485CA0D6D68A200CEA253 /* objc-auto.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-auto.mm"; path = "runtime/objc-auto.mm"; sourceTree = ""; }; 838485CB0D6D68A200CEA253 /* objc-cache.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-cache.mm"; path = "runtime/objc-cache.mm"; sourceTree = ""; }; 838485CC0D6D68A200CEA253 /* objc-class-old.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-class-old.mm"; path = "runtime/objc-class-old.mm"; sourceTree = ""; }; 838485CD0D6D68A200CEA253 /* objc-class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-class.h"; path = "runtime/objc-class.h"; sourceTree = ""; }; 838485CE0D6D68A200CEA253 /* objc-class.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-class.mm"; path = "runtime/objc-class.mm"; sourceTree = ""; }; 838485CF0D6D68A200CEA253 /* objc-config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-config.h"; path = "runtime/objc-config.h"; sourceTree = ""; }; 838485D00D6D68A200CEA253 /* objc-errors.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-errors.mm"; path = "runtime/objc-errors.mm"; sourceTree = ""; }; 838485D10D6D68A200CEA253 /* objc-exception.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-exception.h"; path = "runtime/objc-exception.h"; sourceTree = ""; }; 838485D20D6D68A200CEA253 /* objc-exception.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-exception.mm"; path = "runtime/objc-exception.mm"; sourceTree = ""; }; 838485D30D6D68A200CEA253 /* objc-file.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-file.mm"; path = "runtime/objc-file.mm"; sourceTree = ""; }; 838485D40D6D68A200CEA253 /* objc-initialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-initialize.h"; path = "runtime/objc-initialize.h"; sourceTree = ""; }; 838485D50D6D68A200CEA253 /* objc-initialize.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-initialize.mm"; path = "runtime/objc-initialize.mm"; sourceTree = ""; }; 838485D60D6D68A200CEA253 /* objc-layout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-layout.mm"; path = "runtime/objc-layout.mm"; sourceTree = ""; }; 838485D70D6D68A200CEA253 /* objc-load.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-load.h"; path = "runtime/objc-load.h"; sourceTree = ""; }; 838485D80D6D68A200CEA253 /* objc-load.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-load.mm"; path = "runtime/objc-load.mm"; sourceTree = ""; }; 838485D90D6D68A200CEA253 /* objc-loadmethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-loadmethod.h"; path = "runtime/objc-loadmethod.h"; sourceTree = ""; }; 838485DA0D6D68A200CEA253 /* objc-loadmethod.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-loadmethod.mm"; path = "runtime/objc-loadmethod.mm"; sourceTree = ""; }; 838485DB0D6D68A200CEA253 /* objc-lockdebug.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-lockdebug.mm"; path = "runtime/objc-lockdebug.mm"; sourceTree = ""; }; 838485DC0D6D68A200CEA253 /* objc-private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-private.h"; path = "runtime/objc-private.h"; sourceTree = ""; }; 838485E00D6D68A200CEA253 /* objc-runtime-new.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-runtime-new.h"; path = "runtime/objc-runtime-new.h"; sourceTree = ""; }; 838485E10D6D68A200CEA253 /* objc-runtime-new.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-runtime-new.mm"; path = "runtime/objc-runtime-new.mm"; sourceTree = ""; }; 838485E20D6D68A200CEA253 /* objc-runtime-old.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-runtime-old.mm"; path = "runtime/objc-runtime-old.mm"; sourceTree = ""; }; 838485E30D6D68A200CEA253 /* objc-runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-runtime.h"; path = "runtime/objc-runtime.h"; sourceTree = ""; }; 838485E40D6D68A200CEA253 /* objc-runtime.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-runtime.mm"; path = "runtime/objc-runtime.mm"; sourceTree = ""; }; 838485E50D6D68A200CEA253 /* objc-sel-set.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-sel-set.h"; path = "runtime/objc-sel-set.h"; sourceTree = ""; }; 838485E60D6D68A200CEA253 /* objc-sel-set.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-sel-set.mm"; path = "runtime/objc-sel-set.mm"; sourceTree = ""; }; 838485E80D6D68A200CEA253 /* objc-sel.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-sel.mm"; path = "runtime/objc-sel.mm"; sourceTree = ""; }; 838485E90D6D68A200CEA253 /* objc-sync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-sync.h"; path = "runtime/objc-sync.h"; sourceTree = ""; }; 838485EA0D6D68A200CEA253 /* objc-sync.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-sync.mm"; path = "runtime/objc-sync.mm"; sourceTree = ""; }; 838485EB0D6D68A200CEA253 /* objc-typeencoding.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-typeencoding.mm"; path = "runtime/objc-typeencoding.mm"; sourceTree = ""; }; 838485EC0D6D68A200CEA253 /* objc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = objc.h; path = runtime/objc.h; sourceTree = ""; }; 838485ED0D6D68A200CEA253 /* Object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Object.h; path = runtime/Object.h; sourceTree = ""; }; 838485EE0D6D68A200CEA253 /* Object.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = Object.mm; path = runtime/Object.mm; sourceTree = ""; }; 838486180D6D68A800CEA253 /* Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Protocol.h; path = runtime/Protocol.h; sourceTree = ""; }; 838486190D6D68A800CEA253 /* Protocol.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = Protocol.mm; path = runtime/Protocol.mm; sourceTree = ""; }; 8384861A0D6D68A800CEA253 /* runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = runtime.h; path = runtime/runtime.h; sourceTree = ""; }; 838486230D6D68F000CEA253 /* List.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = List.m; path = runtime/OldClasses.subproj/List.m; sourceTree = ""; }; 838486240D6D68F000CEA253 /* List.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = List.h; path = runtime/OldClasses.subproj/List.h; sourceTree = ""; }; 83A4AED71EA06D9D00ACADDE /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = runtime/Module/module.modulemap; sourceTree = ""; }; 83A4AEDD1EA08C5700ACADDE /* ObjectiveC.apinotes */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = ObjectiveC.apinotes; path = runtime/Module/ObjectiveC.apinotes; sourceTree = ""; }; 83B1A8BC0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-simulator-i386.s"; path = "runtime/Messengers.subproj/objc-msg-simulator-i386.s"; sourceTree = ""; }; 83BE02E30FCCB23400661494 /* objc-file-old.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-file-old.mm"; path = "runtime/objc-file-old.mm"; sourceTree = ""; }; 83BE02E50FCCB24D00661494 /* objc-file-old.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-file-old.h"; path = "runtime/objc-file-old.h"; sourceTree = ""; }; 83BE02E60FCCB24D00661494 /* objc-file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-file.h"; path = "runtime/objc-file.h"; sourceTree = ""; }; 83BE02E70FCCB24D00661494 /* objc-runtime-old.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-runtime-old.h"; path = "runtime/objc-runtime-old.h"; sourceTree = ""; }; 83C9C3381668B50E00F4E544 /* objc-msg-simulator-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-simulator-x86_64.s"; path = "runtime/Messengers.subproj/objc-msg-simulator-x86_64.s"; sourceTree = ""; }; 83CE671D1E6E76B60095A33E /* interposable.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = interposable.txt; sourceTree = ""; }; 83D49E4E13C7C84F0057F1DD /* objc-msg-arm64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-arm64.s"; path = "runtime/Messengers.subproj/objc-msg-arm64.s"; sourceTree = ""; }; 83EB007A121C9EC200B92C16 /* objc-sel-table.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-sel-table.s"; path = "runtime/objc-sel-table.s"; sourceTree = ""; }; 83F4B52615E843B100E0926F /* NSObjCRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSObjCRuntime.h; path = runtime/NSObjCRuntime.h; sourceTree = ""; }; 83F4B52715E843B100E0926F /* NSObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSObject.h; path = runtime/NSObject.h; sourceTree = ""; }; 83F550DF155E030800E95D3B /* objc-cache-old.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-cache-old.mm"; path = "runtime/objc-cache-old.mm"; sourceTree = ""; }; 87BB4E900EC39633005D08E1 /* objc-probes.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; name = "objc-probes.d"; path = "runtime/objc-probes.d"; sourceTree = ""; }; 9672F7ED14D5F488007CEC96 /* NSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NSObject.mm; path = runtime/NSObject.mm; sourceTree = ""; }; BC8B5D1212D3D48100C78A5B /* libauto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauto.dylib; path = /usr/lib/libauto.dylib; sourceTree = ""; }; D2AAC0630554660B00DB518D /* libobjc.A.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libobjc.A.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-i386.s"; path = "runtime/objc-blocktramps-i386.s"; sourceTree = ""; }; E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-x86_64.s"; path = "runtime/objc-blocktramps-x86_64.s"; sourceTree = ""; }; E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-block-trampolines.mm"; path = "runtime/objc-block-trampolines.mm"; sourceTree = ""; }; F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libobjc-trampolines.dylib"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ D289988505E68E00004EDB86 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F9BCC721205C68E800DD9AFC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 08FB7794FE84155DC02AAC07 /* objc */ = { isa = PBXGroup; children = ( BC8B5D1212D3D48100C78A5B /* libauto.dylib */, 838485C60D6D687700CEA253 /* Public Headers */, 838485C70D6D688200CEA253 /* Private Headers */, 8384862A0D6D6ABC00CEA253 /* Project Headers */, 838486220D6D68E300CEA253 /* Obsolete Headers */, 838486270D6D690F00CEA253 /* Obsolete Source */, 08FB7795FE84155DC02AAC07 /* Source */, 838485B20D6D67F900CEA253 /* Other */, 1AB674ADFE9D54B511CA2CBB /* Products */, F9BCC72A205C6A1600DD9AFC /* Frameworks */, ); name = objc; sourceTree = ""; }; 08FB7795FE84155DC02AAC07 /* Source */ = { isa = PBXGroup; children = ( 838485B80D6D687300CEA253 /* hashtable2.mm */, 838485BC0D6D687300CEA253 /* maptable.mm */, 9672F7ED14D5F488007CEC96 /* NSObject.mm */, 838486190D6D68A800CEA253 /* Protocol.mm */, 830F2A930D73876100392440 /* objc-accessors.mm */, 838485CA0D6D68A200CEA253 /* objc-auto.mm */, 39ABD72012F0B61800D1054C /* objc-weak.mm */, E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */, 838485CB0D6D68A200CEA253 /* objc-cache.mm */, 83F550DF155E030800E95D3B /* objc-cache-old.mm */, 838485CC0D6D68A200CEA253 /* objc-class-old.mm */, 838485CE0D6D68A200CEA253 /* objc-class.mm */, 838485D00D6D68A200CEA253 /* objc-errors.mm */, 838485D20D6D68A200CEA253 /* objc-exception.mm */, 838485D30D6D68A200CEA253 /* objc-file.mm */, 83BE02E30FCCB23400661494 /* objc-file-old.mm */, 838485D50D6D68A200CEA253 /* objc-initialize.mm */, 838485D60D6D68A200CEA253 /* objc-layout.mm */, 838485D80D6D68A200CEA253 /* objc-load.mm */, 838485DA0D6D68A200CEA253 /* objc-loadmethod.mm */, 838485DB0D6D68A200CEA253 /* objc-lockdebug.mm */, 83725F4914CA5BFA0014370E /* objc-opt.mm */, 831C85D40E10CF850066E64C /* objc-os.mm */, 393CEABF0DC69E3E000B69DE /* objc-references.mm */, 838485E10D6D68A200CEA253 /* objc-runtime-new.mm */, 838485E20D6D68A200CEA253 /* objc-runtime-old.mm */, 838485E40D6D68A200CEA253 /* objc-runtime.mm */, 838485E60D6D68A200CEA253 /* objc-sel-set.mm */, 83EB007A121C9EC200B92C16 /* objc-sel-table.s */, 838485E80D6D68A200CEA253 /* objc-sel.mm */, 834DF8B615993EE1002F2BC9 /* objc-sel-old.mm */, 838485EA0D6D68A200CEA253 /* objc-sync.mm */, 838485EB0D6D68A200CEA253 /* objc-typeencoding.mm */, 8383A3A1122600E9009290B8 /* objc-blocktramps-arm.s */, 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */, E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */, E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */, 830F2A690D737FB800392440 /* objc-msg-arm.s */, 83D49E4E13C7C84F0057F1DD /* objc-msg-arm64.s */, 830F2A6A0D737FB800392440 /* objc-msg-i386.s */, 83B1A8BC0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s */, 83C9C3381668B50E00F4E544 /* objc-msg-simulator-x86_64.s */, 830F2A720D737FB800392440 /* objc-msg-x86_64.s */, 87BB4E900EC39633005D08E1 /* objc-probes.d */, ); name = Source; sourceTree = ""; }; 1AB674ADFE9D54B511CA2CBB /* Products */ = { isa = PBXGroup; children = ( D2AAC0630554660B00DB518D /* libobjc.A.dylib */, F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */, ); name = Products; sourceTree = ""; }; 838485B20D6D67F900CEA253 /* Other */ = { isa = PBXGroup; children = ( 830F2AA50D7394C200392440 /* markgc.cpp */, 838485B40D6D683300CEA253 /* APPLE_LICENSE */, 838485B50D6D683300CEA253 /* ReleaseNotes.rtf */, 83CE671D1E6E76B60095A33E /* interposable.txt */, 838485B30D6D682B00CEA253 /* libobjc.order */, ); name = Other; sourceTree = ""; }; 838485C60D6D687700CEA253 /* Public Headers */ = { isa = PBXGroup; children = ( 83A4AEDD1EA08C5700ACADDE /* ObjectiveC.apinotes */, 83A4AED71EA06D9D00ACADDE /* module.modulemap */, 83F4B52615E843B100E0926F /* NSObjCRuntime.h */, 83F4B52715E843B100E0926F /* NSObject.h */, 838485BD0D6D687300CEA253 /* message.h */, 838485C80D6D68A200CEA253 /* objc-api.h */, 838485C90D6D68A200CEA253 /* objc-auto.h */, 838485D10D6D68A200CEA253 /* objc-exception.h */, 838485E90D6D68A200CEA253 /* objc-sync.h */, 838485EC0D6D68A200CEA253 /* objc.h */, 8384861A0D6D68A800CEA253 /* runtime.h */, ); name = "Public Headers"; sourceTree = ""; }; 838485C70D6D688200CEA253 /* Private Headers */ = { isa = PBXGroup; children = ( 83112ED30F00599600A5FBAF /* objc-internal.h */, 834EC0A311614167009B2563 /* objc-abi.h */, 838485BB0D6D687300CEA253 /* maptable.h */, 834266D70E665A8B002E4DA2 /* objc-gdb.h */, 8306440620D24A3E00E356D2 /* objc-block-trampolines.h */, ); name = "Private Headers"; sourceTree = ""; }; 838486220D6D68E300CEA253 /* Obsolete Headers */ = { isa = PBXGroup; children = ( 830F2A970D738DC200392440 /* hashtable.h */, 838485B70D6D687300CEA253 /* hashtable2.h */, 838485CD0D6D68A200CEA253 /* objc-class.h */, 838485D70D6D68A200CEA253 /* objc-load.h */, 838485E30D6D68A200CEA253 /* objc-runtime.h */, 838486240D6D68F000CEA253 /* List.h */, 838485ED0D6D68A200CEA253 /* Object.h */, 838486180D6D68A800CEA253 /* Protocol.h */, ); name = "Obsolete Headers"; sourceTree = ""; }; 838486270D6D690F00CEA253 /* Obsolete Source */ = { isa = PBXGroup; children = ( 838486230D6D68F000CEA253 /* List.m */, 838485EE0D6D68A200CEA253 /* Object.mm */, ); name = "Obsolete Source"; sourceTree = ""; }; 8384862A0D6D6ABC00CEA253 /* Project Headers */ = { isa = PBXGroup; children = ( 838485CF0D6D68A200CEA253 /* objc-config.h */, 83BE02E50FCCB24D00661494 /* objc-file-old.h */, 83BE02E60FCCB24D00661494 /* objc-file.h */, 838485D40D6D68A200CEA253 /* objc-initialize.h */, 838485D90D6D68A200CEA253 /* objc-loadmethod.h */, 75A9504E202BAA0300D7D56F /* objc-locks-new.h */, 75A95052202BAC4100D7D56F /* objc-lockdebug.h */, 75A95050202BAA9A00D7D56F /* objc-locks.h */, 7593EC57202248DF0046AB96 /* objc-object.h */, 831C85D30E10CF850066E64C /* objc-os.h */, 838485DC0D6D68A200CEA253 /* objc-private.h */, 393CEAC50DC69E67000B69DE /* objc-references.h */, 838485E00D6D68A200CEA253 /* objc-runtime-new.h */, 83BE02E70FCCB24D00661494 /* objc-runtime-old.h */, 838485E50D6D68A200CEA253 /* objc-sel-set.h */, 39ABD71F12F0B61800D1054C /* objc-weak.h */, ); name = "Project Headers"; sourceTree = ""; }; F9BCC72A205C6A1600DD9AFC /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 8306440820D24A5300E356D2 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 8306440920D24A5D00E356D2 /* objc-block-trampolines.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; D2AAC0600554660B00DB518D /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 83A4AEDE1EA08C7200ACADDE /* ObjectiveC.apinotes in Headers */, 75A95051202BAA9A00D7D56F /* objc-locks.h in Headers */, 83A4AEDC1EA0840800ACADDE /* module.modulemap in Headers */, 830F2A980D738DC200392440 /* hashtable.h in Headers */, 838485BF0D6D687300CEA253 /* hashtable2.h in Headers */, 838486260D6D68F000CEA253 /* List.h in Headers */, 838485C30D6D687300CEA253 /* maptable.h in Headers */, 838486280D6D6A2400CEA253 /* message.h in Headers */, 834EC0A411614167009B2563 /* objc-abi.h in Headers */, 838485EF0D6D68A200CEA253 /* objc-api.h in Headers */, 838485F00D6D68A200CEA253 /* objc-auto.h in Headers */, 838485F40D6D68A200CEA253 /* objc-class.h in Headers */, 838485F60D6D68A200CEA253 /* objc-config.h in Headers */, 838485F80D6D68A200CEA253 /* objc-exception.h in Headers */, 83BE02E80FCCB24D00661494 /* objc-file-old.h in Headers */, 83BE02E90FCCB24D00661494 /* objc-file.h in Headers */, 75A9504F202BAA0600D7D56F /* objc-locks-new.h in Headers */, 834266D80E665A8B002E4DA2 /* objc-gdb.h in Headers */, 838485FB0D6D68A200CEA253 /* objc-initialize.h in Headers */, 7593EC58202248E50046AB96 /* objc-object.h in Headers */, 83112ED40F00599600A5FBAF /* objc-internal.h in Headers */, 838485FE0D6D68A200CEA253 /* objc-load.h in Headers */, 838486000D6D68A200CEA253 /* objc-loadmethod.h in Headers */, 75A95053202BAC4100D7D56F /* objc-lockdebug.h in Headers */, 831C85D50E10CF850066E64C /* objc-os.h in Headers */, 838486030D6D68A200CEA253 /* objc-private.h in Headers */, 393CEAC60DC69E67000B69DE /* objc-references.h in Headers */, 838486070D6D68A200CEA253 /* objc-runtime-new.h in Headers */, 83BE02EA0FCCB24D00661494 /* objc-runtime-old.h in Headers */, 8384860A0D6D68A200CEA253 /* objc-runtime.h in Headers */, 8384860C0D6D68A200CEA253 /* objc-sel-set.h in Headers */, 838486100D6D68A200CEA253 /* objc-sync.h in Headers */, 838486130D6D68A200CEA253 /* objc.h in Headers */, 838486140D6D68A200CEA253 /* Object.h in Headers */, 8384861E0D6D68A800CEA253 /* Protocol.h in Headers */, 838486200D6D68A800CEA253 /* runtime.h in Headers */, 39ABD72312F0B61800D1054C /* objc-weak.h in Headers */, 83F4B52815E843B100E0926F /* NSObjCRuntime.h in Headers */, 83F4B52915E843B100E0926F /* NSObject.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ D2AAC0620554660B00DB518D /* objc */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB914A08733D8E0010E9CD /* Build configuration list for PBXNativeTarget "objc" */; buildPhases = ( D2AAC0600554660B00DB518D /* Headers */, D2AAC0610554660B00DB518D /* Sources */, D289988505E68E00004EDB86 /* Frameworks */, 830F2AB60D739AB600392440 /* Run Script (markgc) */, 830F2AFA0D73BC5800392440 /* Run Script (symlink) */, ); buildRules = ( ); dependencies = ( F9BCC729205C6A0900DD9AFC /* PBXTargetDependency */, ); name = objc; productName = objc; productReference = D2AAC0630554660B00DB518D /* libobjc.A.dylib */; productType = "com.apple.product-type.library.dynamic"; }; F9BCC6CA205C68E800DD9AFC /* objc-trampolines */ = { isa = PBXNativeTarget; buildConfigurationList = F9BCC724205C68E800DD9AFC /* Build configuration list for PBXNativeTarget "objc-trampolines" */; buildPhases = ( 8306440820D24A5300E356D2 /* Headers */, F9BCC6EF205C68E800DD9AFC /* Sources */, F9BCC721205C68E800DD9AFC /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = "objc-trampolines"; productName = objc; productReference = F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */; productType = "com.apple.product-type.library.dynamic"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = NO; LastUpgradeCheck = 0440; TargetAttributes = { 837F67A81A771F63004D34FA = { CreatedOnToolsVersion = 6.3; }; }; }; buildConfigurationList = 1DEB914E08733D8E0010E9CD /* Build configuration list for PBXProject "objc" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 1; knownRegions = ( English, Japanese, French, German, ); mainGroup = 08FB7794FE84155DC02AAC07 /* objc */; projectDirPath = ""; projectRoot = ""; targets = ( D2AAC0620554660B00DB518D /* objc */, 837F67A81A771F63004D34FA /* objc-simulator */, F9BCC6CA205C68E800DD9AFC /* objc-trampolines */, ); }; /* End PBXProject section */ /* Begin PBXShellScriptBuildPhase section */ 830F2AB60D739AB600392440 /* Run Script (markgc) */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; comments = "Modify the built dylib (mod_init_funcs and mod_term_funcs)."; files = ( ); inputPaths = ( ); name = "Run Script (markgc)"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "set -x\n/usr/bin/xcrun -sdk macosx.internal clang++ -Wall -mmacosx-version-min=10.12 -arch x86_64 -std=c++11 \"${SRCROOT}/markgc.cpp\" -o \"${BUILT_PRODUCTS_DIR}/markgc\"\n\"${BUILT_PRODUCTS_DIR}/markgc\" \"${BUILT_PRODUCTS_DIR}/libobjc.A.dylib\""; }; 830F2AFA0D73BC5800392440 /* Run Script (symlink) */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( ); inputPaths = ( ); name = "Run Script (symlink)"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = "cd \"${INSTALL_DIR}\"\n/bin/ln -s libobjc.A.dylib libobjc.dylib\n\nTBD_UPPER=`echo ${GENERATE_TEXT_BASED_STUBS} | tr a-z A-Z`\n\nif [ ${TBD_UPPER} = \"YES\" ] || [ ${TBD_UPPER} = \"TRUE\" ] || [ ${TBD_UPPER} = \"1\" ]; then\nGENERATE_TBD=1\nfi\n\nif [ ${GENERATE_TBD} ]; then\n /bin/ln -s libobjc.A.tbd libobjc.tbd\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ D2AAC0610554660B00DB518D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 838485C00D6D687300CEA253 /* hashtable2.mm in Sources */, 838485C40D6D687300CEA253 /* maptable.mm in Sources */, 838485F10D6D68A200CEA253 /* objc-auto.mm in Sources */, 838485F20D6D68A200CEA253 /* objc-cache.mm in Sources */, 838485F30D6D68A200CEA253 /* objc-class-old.mm in Sources */, 838485F50D6D68A200CEA253 /* objc-class.mm in Sources */, 838485F70D6D68A200CEA253 /* objc-errors.mm in Sources */, 838485F90D6D68A200CEA253 /* objc-exception.mm in Sources */, 838485FA0D6D68A200CEA253 /* objc-file.mm in Sources */, 838485FC0D6D68A200CEA253 /* objc-initialize.mm in Sources */, 838485FD0D6D68A200CEA253 /* objc-layout.mm in Sources */, 838485FF0D6D68A200CEA253 /* objc-load.mm in Sources */, 838486010D6D68A200CEA253 /* objc-loadmethod.mm in Sources */, 838486020D6D68A200CEA253 /* objc-lockdebug.mm in Sources */, 838486080D6D68A200CEA253 /* objc-runtime-new.mm in Sources */, 838486090D6D68A200CEA253 /* objc-runtime-old.mm in Sources */, 8384860B0D6D68A200CEA253 /* objc-runtime.mm in Sources */, 8384860D0D6D68A200CEA253 /* objc-sel-set.mm in Sources */, 8384860F0D6D68A200CEA253 /* objc-sel.mm in Sources */, 838486110D6D68A200CEA253 /* objc-sync.mm in Sources */, 838486120D6D68A200CEA253 /* objc-typeencoding.mm in Sources */, 838486150D6D68A200CEA253 /* Object.mm in Sources */, 8384861F0D6D68A800CEA253 /* Protocol.mm in Sources */, 838486250D6D68F000CEA253 /* List.m in Sources */, 830F2A740D737FB800392440 /* objc-msg-arm.s in Sources */, 830F2A750D737FB900392440 /* objc-msg-i386.s in Sources */, 830F2A7D0D737FBB00392440 /* objc-msg-x86_64.s in Sources */, 830F2A950D73876100392440 /* objc-accessors.mm in Sources */, 393CEAC00DC69E3E000B69DE /* objc-references.mm in Sources */, 831C85D60E10CF850066E64C /* objc-os.mm in Sources */, 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */, 83BE02E40FCCB23400661494 /* objc-file-old.mm in Sources */, E8923DA5116AB2820071B552 /* objc-block-trampolines.mm in Sources */, 83B1A8BE0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s in Sources */, 83EB007B121C9EC200B92C16 /* objc-sel-table.s in Sources */, 39ABD72412F0B61800D1054C /* objc-weak.mm in Sources */, 83D49E4F13C7C84F0057F1DD /* objc-msg-arm64.s in Sources */, 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */, 83725F4A14CA5BFA0014370E /* objc-opt.mm in Sources */, 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */, 834DF8B715993EE1002F2BC9 /* objc-sel-old.mm in Sources */, 83C9C3391668B50E00F4E544 /* objc-msg-simulator-x86_64.s in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F9BCC6EF205C68E800DD9AFC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 83EF5E9C20D2299E00F486A4 /* objc-blocktramps-arm.s in Sources */, 83EF5E9820D2298400F486A4 /* objc-blocktramps-i386.s in Sources */, 83EF5E9920D2298400F486A4 /* objc-blocktramps-x86_64.s in Sources */, F9BCC71B205C68E800DD9AFC /* objc-blocktramps-arm64.s in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 837F67AD1A771F6E004D34FA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D2AAC0620554660B00DB518D /* objc */; targetProxy = 837F67AC1A771F6E004D34FA /* PBXContainerItemProxy */; }; F9BCC729205C6A0900DD9AFC /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F9BCC6CA205C68E800DD9AFC /* objc-trampolines */; targetProxy = F9BCC728205C6A0900DD9AFC /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 1DEB914B08733D8E0010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; COPY_PHASE_STRIP = NO; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; HEADER_SEARCH_PATHS = ( "$(DSTROOT)/usr/include/**", "$(DSTROOT)/usr/local/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", /System/Library/Frameworks/System.framework/PrivateHeaders, ); INSTALL_PATH = /usr/lib; IS_ZIPPERED = YES; ORDER_FILE = "$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order"; "ORDER_FILE[sdk=iphonesimulator*]" = ""; OTHER_CFLAGS = ( "-fdollars-in-identifiers", "$(OTHER_CFLAGS)", ); "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( "-lc++abi", "-Wl,-segalign,0x4000", "-Xlinker", "-sectalign", "-Xlinker", __DATA, "-Xlinker", __objc_data, "-Xlinker", 0x1000, "-Xlinker", "-interposable_list", "-Xlinker", interposable.txt, ); "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = ( "-lc++abi", "-Xlinker", "-interposable_list", "-Xlinker", interposable.txt, ); "OTHER_LDFLAGS[sdk=macosx*]" = ( "-lCrashReporterClient", "-lc++abi", "-Xlinker", "-sectalign", "-Xlinker", __DATA, "-Xlinker", __objc_data, "-Xlinker", 0x1000, "-Xlinker", "-interposable_list", "-Xlinker", interposable.txt, ); OTHER_TAPI_FLAGS = "-exclude-public-header $(DSTROOT)/usr/include/objc/ObjectiveC.apinotes -exclude-public-header $(DSTROOT)/usr/include/objc/module.modulemap -Xparser -Wno-deprecated-declarations -Xparser -Wno-unavailable-declarations -Xparser -D_OBJC_PRIVATE_H_=1 -DOBJC_DECLARE_SYMBOLS=1"; PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = objc.A; PUBLIC_HEADERS_FOLDER_PATH = /usr/include/objc; SUPPORTS_TEXT_BASED_API = YES; TAPI_VERIFY_MODE = Pedantic; UNEXPORTED_SYMBOLS_FILE = unexported_symbols; }; name = Debug; }; 1DEB914C08733D8E0010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; HEADER_SEARCH_PATHS = ( "$(DSTROOT)/usr/include/**", "$(DSTROOT)/usr/local/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", /System/Library/Frameworks/System.framework/PrivateHeaders, ); INSTALL_PATH = /usr/lib; IS_ZIPPERED = YES; ORDER_FILE = "$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order"; "ORDER_FILE[sdk=iphonesimulator*]" = ""; OTHER_CFLAGS = ( "-fdollars-in-identifiers", "$(OTHER_CFLAGS)", ); "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( "-lc++abi", "-Wl,-segalign,0x4000", "-Xlinker", "-sectalign", "-Xlinker", __DATA, "-Xlinker", __objc_data, "-Xlinker", 0x1000, "-Xlinker", "-interposable_list", "-Xlinker", interposable.txt, ); "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = ( "-lc++abi", "-Xlinker", "-interposable_list", "-Xlinker", interposable.txt, ); "OTHER_LDFLAGS[sdk=macosx*]" = ( "-lCrashReporterClient", "-lc++abi", "-Xlinker", "-sectalign", "-Xlinker", __DATA, "-Xlinker", __objc_data, "-Xlinker", 0x1000, "-Xlinker", "-interposable_list", "-Xlinker", interposable.txt, ); OTHER_TAPI_FLAGS = "-exclude-public-header $(DSTROOT)/usr/include/objc/ObjectiveC.apinotes -exclude-public-header $(DSTROOT)/usr/include/objc/module.modulemap -Xparser -Wno-deprecated-declarations -Xparser -Wno-unavailable-declarations -Xparser -D_OBJC_PRIVATE_H_=1 -DOBJC_DECLARE_SYMBOLS=1"; PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = objc.A; PUBLIC_HEADERS_FOLDER_PATH = /usr/include/objc; SUPPORTS_TEXT_BASED_API = YES; TAPI_VERIFY_MODE = Pedantic; UNEXPORTED_SYMBOLS_FILE = unexported_symbols; WARNING_CFLAGS = ( "$(inherited)", "-Wglobal-constructors", ); }; name = Release; }; 1DEB914F08733D8E0010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; DEBUG_INFORMATION_FORMAT = dwarf; EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS = "$(inherited) test"; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = "OS_OBJECT_USE_OBJC=0"; GCC_STRICT_ALIASING = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_SHADOW = YES; GCC_WARN_UNUSED_VARIABLE = YES; OTHER_CFLAGS = ""; "OTHER_CFLAGS[arch=x86_64]" = "-fobjc-legacy-dispatch"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-D_LIBCPP_VISIBLE=\"\"", ); WARNING_CFLAGS = ( "-Wall", "-Wextra", "-Wstrict-aliasing=2", "-Wstrict-overflow=4", "-Wno-unused-parameter", "-Wno-deprecated-objc-isa-usage", "-Wno-cast-of-sel-type", ); }; name = Debug; }; 1DEB915008733D8E0010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS = "$(inherited) test"; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "OS_OBJECT_USE_OBJC=0", "NDEBUG=1", ); GCC_STRICT_ALIASING = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_SHADOW = YES; GCC_WARN_UNUSED_VARIABLE = YES; "OTHER_CFLAGS[arch=i386]" = "-momit-leaf-frame-pointer"; "OTHER_CFLAGS[arch=x86_64]" = ( "-momit-leaf-frame-pointer", "-fobjc-legacy-dispatch", ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-D_LIBCPP_VISIBLE=\"\"", ); WARNING_CFLAGS = ( "-Wall", "-Wextra", "-Wstrict-aliasing=2", "-Wstrict-overflow=4", "-Wno-unused-parameter", "-Wno-deprecated-objc-isa-usage", "-Wno-cast-of-sel-type", ); }; name = Release; }; 837F67AA1A771F63004D34FA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 837F67AB1A771F63004D34FA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; F9BCC725205C68E800DD9AFC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; COPY_PHASE_STRIP = NO; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; HEADER_SEARCH_PATHS = ( "$(DSTROOT)/usr/include/**", "$(DSTROOT)/usr/local/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", /System/Library/Frameworks/System.framework/PrivateHeaders, ); INSTALL_PATH = /usr/lib; IS_ZIPPERED = YES; OTHER_CFLAGS = ( "-fdollars-in-identifiers", "$(OTHER_CFLAGS)", ); OTHER_LDFLAGS = ( "-Xlinker", "-not_for_dyld_shared_cache", ); PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = "$(TARGET_NAME)"; PUBLIC_HEADERS_FOLDER_PATH = /usr/include/objc; SUPPORTS_TEXT_BASED_API = YES; TAPI_VERIFY_MODE = Pedantic; }; name = Debug; }; F9BCC726205C68E800DD9AFC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; HEADER_SEARCH_PATHS = ( "$(DSTROOT)/usr/include/**", "$(DSTROOT)/usr/local/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", /System/Library/Frameworks/System.framework/PrivateHeaders, ); INSTALL_PATH = /usr/lib; IS_ZIPPERED = YES; OTHER_CFLAGS = ( "-fdollars-in-identifiers", "$(OTHER_CFLAGS)", ); OTHER_LDFLAGS = ( "-Xlinker", "-not_for_dyld_shared_cache", ); PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = "$(TARGET_NAME)"; PUBLIC_HEADERS_FOLDER_PATH = /usr/include/objc; SUPPORTS_TEXT_BASED_API = YES; TAPI_VERIFY_MODE = Pedantic; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1DEB914A08733D8E0010E9CD /* Build configuration list for PBXNativeTarget "objc" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB914B08733D8E0010E9CD /* Debug */, 1DEB914C08733D8E0010E9CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1DEB914E08733D8E0010E9CD /* Build configuration list for PBXProject "objc" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB914F08733D8E0010E9CD /* Debug */, 1DEB915008733D8E0010E9CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 837F67A91A771F63004D34FA /* Build configuration list for PBXAggregateTarget "objc-simulator" */ = { isa = XCConfigurationList; buildConfigurations = ( 837F67AA1A771F63004D34FA /* Debug */, 837F67AB1A771F63004D34FA /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F9BCC724205C68E800DD9AFC /* Build configuration list for PBXNativeTarget "objc-trampolines" */ = { isa = XCConfigurationList; buildConfigurations = ( F9BCC725205C68E800DD9AFC /* Debug */, F9BCC726205C68E800DD9AFC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; } ================================================ FILE: InterView-obj-isa-class/objc4-750/objc.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: InterView-obj-isa-class/objc4-750/objc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: InterView-obj-isa-class/objc4-750/objcrt/objcrt.vcproj ================================================ ================================================ FILE: InterView-obj-isa-class/objc4-750/prebuild.bat ================================================ @echo off echo prebuild: installing headers xcopy /Y "%ProjectDir%runtime\objc.h" "%DSTROOT%\AppleInternal\include\objc\" xcopy /Y "%ProjectDir%runtime\objc-api.h" "%DSTROOT%\AppleInternal\include\objc\" xcopy /Y "%ProjectDir%runtime\objc-auto.h" "%DSTROOT%\AppleInternal\include\objc\" xcopy /Y "%ProjectDir%runtime\objc-exception.h" "%DSTROOT%\AppleInternal\include\objc\" xcopy /Y "%ProjectDir%runtime\message.h" "%DSTROOT%\AppleInternal\include\objc\" xcopy /Y "%ProjectDir%runtime\runtime.h" "%DSTROOT%\AppleInternal\include\objc\" xcopy /Y "%ProjectDir%runtime\hashtable.h" "%DSTROOT%\AppleInternal\include\objc\" xcopy /Y "%ProjectDir%runtime\hashtable2.h" "%DSTROOT%\AppleInternal\include\objc\" xcopy /Y "%ProjectDir%runtime\maptable.h" "%DSTROOT%\AppleInternal\include\objc\" echo prebuild: setting version version ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Messengers.subproj/objc-msg-arm.s ================================================ /* * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2007 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /******************************************************************** * * objc-msg-arm.s - ARM code to support objc messaging * ********************************************************************/ #ifdef __arm__ #include #include "objc-config.h" #include "isa.h" #ifndef _ARM_ARCH_7 # error requires armv7 #endif // Set FP=1 on architectures that pass parameters in floating-point registers #if __ARM_ARCH_7K__ # define FP 1 #else # define FP 0 #endif #if FP # if !__ARM_NEON__ # error sorry # endif # define FP_RETURN_ZERO \ vmov.i32 q0, #0 ; \ vmov.i32 q1, #0 ; \ vmov.i32 q2, #0 ; \ vmov.i32 q3, #0 # define FP_SAVE \ vpush {q0-q3} # define FP_RESTORE \ vpop {q0-q3} #else # define FP_RETURN_ZERO # define FP_SAVE # define FP_RESTORE #endif .syntax unified #define MI_EXTERN(var) \ .non_lazy_symbol_pointer ;\ L##var##$$non_lazy_ptr: ;\ .indirect_symbol var ;\ .long 0 #define MI_GET_EXTERN(reg,var) \ movw reg, :lower16:(L##var##$$non_lazy_ptr-7f-4) ;\ movt reg, :upper16:(L##var##$$non_lazy_ptr-7f-4) ;\ 7: add reg, pc ;\ ldr reg, [reg] #define MI_GET_ADDRESS(reg,var) \ movw reg, :lower16:(var-7f-4) ;\ movt reg, :upper16:(var-7f-4) ;\ 7: add reg, pc ;\ .data #if SUPPORT_INDEXED_ISA .align 2 .globl _objc_indexed_classes _objc_indexed_classes: .fill ISA_INDEX_COUNT, 4, 0 #endif // _objc_entryPoints and _objc_exitPoints are used by method dispatch // caching code to figure out whether any threads are actively // in the cache for dispatching. The labels surround the asm code // that do cache lookups. The tables are zero-terminated. .align 2 .private_extern _objc_entryPoints _objc_entryPoints: .long _cache_getImp .long _objc_msgSend .long _objc_msgSend_stret .long _objc_msgSendSuper .long _objc_msgSendSuper_stret .long _objc_msgSendSuper2 .long _objc_msgSendSuper2_stret .long _objc_msgLookup .long _objc_msgLookup_stret .long _objc_msgLookupSuper2 .long _objc_msgLookupSuper2_stret .long 0 .private_extern _objc_exitPoints _objc_exitPoints: .long LExit_cache_getImp .long LExit_objc_msgSend .long LExit_objc_msgSend_stret .long LExit_objc_msgSendSuper .long LExit_objc_msgSendSuper_stret .long LExit_objc_msgSendSuper2 .long LExit_objc_msgSendSuper2_stret .long LExit_objc_msgLookup .long LExit_objc_msgLookup_stret .long LExit_objc_msgLookupSuper2 .long LExit_objc_msgLookupSuper2_stret .long 0 /******************************************************************** * Names for relative labels * DO NOT USE THESE LABELS ELSEWHERE * Reserved labels: 6: 7: 8: 9: ********************************************************************/ // 6: used by CacheLookup // 7: used by MI_GET_ADDRESS etc // 8: used by CacheLookup #define LNilReceiver 9 #define LNilReceiver_f 9f #define LNilReceiver_b 9b /******************************************************************** * Macro parameters ********************************************************************/ #define NORMAL 0 #define STRET 1 /******************************************************************** * * Structure definitions. * ********************************************************************/ /* objc_super parameter to sendSuper */ #define RECEIVER 0 #define CLASS 4 /* Selected field offsets in class structure */ #define ISA 0 #define SUPERCLASS 4 #define CACHE 8 #define CACHE_MASK 12 /* Field offsets in method cache bucket */ #define CACHED_SEL 0 #define CACHED_IMP 4 /* Selected field offsets in method structure */ #define METHOD_NAME 0 #define METHOD_TYPES 4 #define METHOD_IMP 8 ////////////////////////////////////////////////////////////////////// // // ENTRY functionName // // Assembly directives to begin an exported function. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro ENTRY /* name */ .text .thumb .align 5 .globl $0 .thumb_func $0: .endmacro .macro STATIC_ENTRY /*name*/ .text .thumb .align 5 .private_extern $0 .thumb_func $0: .endmacro ////////////////////////////////////////////////////////////////////// // // END_ENTRY functionName // // Assembly directives to end an exported function. Just a placeholder, // a close-parenthesis for ENTRY, until it is needed for something. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro END_ENTRY /* name */ LExit$0: .endmacro ///////////////////////////////////////////////////////////////////// // // CacheLookup NORMAL|STRET // CacheLookup2 NORMAL|STRET // // Locate the implementation for a selector in a class's method cache. // // Takes: // $0 = NORMAL, STRET // r0 or r1 (STRET) = receiver // r1 or r2 (STRET) = selector // r9 = class to search in // // On exit: r9 clobbered // (found) continues after CacheLookup, IMP in r12, eq set // (not found) continues after CacheLookup2 // ///////////////////////////////////////////////////////////////////// .macro CacheLookup ldrh r12, [r9, #CACHE_MASK] // r12 = mask ldr r9, [r9, #CACHE] // r9 = buckets .if $0 == STRET and r12, r12, r2 // r12 = index = SEL & mask .else and r12, r12, r1 // r12 = index = SEL & mask .endif add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8 ldr r12, [r9, #CACHED_SEL] // r12 = bucket->sel 6: .if $0 == STRET teq r12, r2 .else teq r12, r1 .endif bne 8f ldr r12, [r9, #CACHED_IMP] // r12 = bucket->imp .if $0 == STRET tst r12, r12 // set ne for stret forwarding .else // eq already set for nonstret forwarding by `teq` above .endif .endmacro .macro CacheLookup2 #if CACHED_SEL != 0 # error this code requires that SEL be at offset 0 #endif 8: cmp r12, #1 blo 8f // if (bucket->sel == 0) cache miss it eq // if (bucket->sel == 1) cache wrap ldreq r9, [r9, #CACHED_IMP] // bucket->imp is before first bucket ldr r12, [r9, #8]! // r12 = (++bucket)->sel b 6b 8: .endmacro ///////////////////////////////////////////////////////////////////// // // GetClassFromIsa return-type // // Given an Isa, return the class for the Isa. // // Takes: // r9 = class // // On exit: r12 clobbered // r9 contains the class for this Isa. // ///////////////////////////////////////////////////////////////////// .macro GetClassFromIsa #if SUPPORT_INDEXED_ISA // Note: We are doing a little wasted work here to load values we might not // need. Branching turns out to be even worse when performance was measured. MI_GET_ADDRESS(r12, _objc_indexed_classes) tst.w r9, #ISA_INDEX_IS_NPI_MASK itt ne ubfxne r9, r9, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS ldrne.w r9, [r12, r9, lsl #2] #endif .endmacro /******************************************************************** * IMP cache_getImp(Class cls, SEL sel) * * On entry: r0 = class whose cache is to be searched * r1 = selector to search for * * If found, returns method implementation. * If not found, returns NULL. ********************************************************************/ STATIC_ENTRY _cache_getImp mov r9, r0 CacheLookup NORMAL // cache hit, IMP in r12 mov r0, r12 bx lr // return imp CacheLookup2 GETIMP // cache miss, return nil mov r0, #0 bx lr END_ENTRY _cache_getImp /******************************************************************** * * id objc_msgSend(id self, SEL _cmd, ...); * IMP objc_msgLookup(id self, SEL _cmd, ...); * * objc_msgLookup ABI: * IMP returned in r12 * Forwarding returned in Z flag * r9 reserved for our use but not used * ********************************************************************/ ENTRY _objc_msgSend cbz r0, LNilReceiver_f ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class CacheLookup NORMAL // cache hit, IMP in r12, eq already set for nonstret forwarding bx r12 // call imp CacheLookup2 NORMAL // cache miss ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class b __objc_msgSend_uncached LNilReceiver: // r0 is already zero mov r1, #0 mov r2, #0 mov r3, #0 FP_RETURN_ZERO bx lr END_ENTRY _objc_msgSend ENTRY _objc_msgLookup cbz r0, LNilReceiver_f ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class CacheLookup NORMAL // cache hit, IMP in r12, eq already set for nonstret forwarding bx lr CacheLookup2 NORMAL // cache miss ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class b __objc_msgLookup_uncached LNilReceiver: MI_GET_ADDRESS(r12, __objc_msgNil) bx lr END_ENTRY _objc_msgLookup STATIC_ENTRY __objc_msgNil // r0 is already zero mov r1, #0 mov r2, #0 mov r3, #0 FP_RETURN_ZERO bx lr END_ENTRY __objc_msgNil /******************************************************************** * void objc_msgSend_stret(void *st_addr, id self, SEL op, ...); * IMP objc_msgLookup_stret(void *st_addr, id self, SEL op, ...); * * objc_msgSend_stret is the struct-return form of msgSend. * The ABI calls for r0 to be used as the address of the structure * being returned, with the parameters in the succeeding registers. * * On entry: r0 is the address where the structure is returned, * r1 is the message receiver, * r2 is the selector ********************************************************************/ ENTRY _objc_msgSend_stret cbz r1, LNilReceiver_f ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class CacheLookup STRET // cache hit, IMP in r12, ne already set for stret forwarding bx r12 CacheLookup2 STRET // cache miss ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class b __objc_msgSend_stret_uncached LNilReceiver: bx lr END_ENTRY _objc_msgSend_stret ENTRY _objc_msgLookup_stret cbz r1, LNilReceiver_f ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class CacheLookup STRET // cache hit, IMP in r12, ne already set for stret forwarding bx lr CacheLookup2 STRET // cache miss ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class b __objc_msgLookup_stret_uncached LNilReceiver: MI_GET_ADDRESS(r12, __objc_msgNil_stret) bx lr END_ENTRY _objc_msgLookup_stret STATIC_ENTRY __objc_msgNil_stret bx lr END_ENTRY __objc_msgNil_stret /******************************************************************** * id objc_msgSendSuper(struct objc_super *super, SEL op, ...) * * struct objc_super { * id receiver; * Class cls; // the class to search * } ********************************************************************/ ENTRY _objc_msgSendSuper ldr r9, [r0, #CLASS] // r9 = struct super->class CacheLookup NORMAL // cache hit, IMP in r12, eq already set for nonstret forwarding ldr r0, [r0, #RECEIVER] // load real receiver bx r12 // call imp CacheLookup2 NORMAL // cache miss ldr r9, [r0, #CLASS] // r9 = struct super->class ldr r0, [r0, #RECEIVER] // load real receiver b __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper /******************************************************************** * id objc_msgSendSuper2(struct objc_super *super, SEL op, ...) * * struct objc_super { * id receiver; * Class cls; // SUBCLASS of the class to search * } ********************************************************************/ ENTRY _objc_msgSendSuper2 ldr r9, [r0, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass CacheLookup NORMAL // cache hit, IMP in r12, eq already set for nonstret forwarding ldr r0, [r0, #RECEIVER] // load real receiver bx r12 // call imp CacheLookup2 NORMAL // cache miss ldr r9, [r0, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass ldr r0, [r0, #RECEIVER] // load real receiver b __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 ENTRY _objc_msgLookupSuper2 ldr r9, [r0, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass CacheLookup NORMAL // cache hit, IMP in r12, eq already set for nonstret forwarding ldr r0, [r0, #RECEIVER] // load real receiver bx lr CacheLookup2 NORMAL // cache miss ldr r9, [r0, #CLASS] ldr r9, [r9, #SUPERCLASS] // r9 = class to search ldr r0, [r0, #RECEIVER] // load real receiver b __objc_msgLookup_uncached END_ENTRY _objc_msgLookupSuper2 /******************************************************************** * void objc_msgSendSuper_stret(void *st_addr, objc_super *self, SEL op, ...); * * objc_msgSendSuper_stret is the struct-return form of msgSendSuper. * The ABI calls for r0 to be used as the address of the structure * being returned, with the parameters in the succeeding registers. * * On entry: r0 is the address where the structure is returned, * r1 is the address of the objc_super structure, * r2 is the selector ********************************************************************/ ENTRY _objc_msgSendSuper_stret ldr r9, [r1, #CLASS] // r9 = struct super->class CacheLookup STRET // cache hit, IMP in r12, ne already set for stret forwarding ldr r1, [r1, #RECEIVER] // load real receiver bx r12 // call imp CacheLookup2 STRET // cache miss ldr r9, [r1, #CLASS] // r9 = struct super->class ldr r1, [r1, #RECEIVER] // load real receiver b __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper_stret /******************************************************************** * id objc_msgSendSuper2_stret ********************************************************************/ ENTRY _objc_msgSendSuper2_stret ldr r9, [r1, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass CacheLookup STRET // cache hit, IMP in r12, ne already set for stret forwarding ldr r1, [r1, #RECEIVER] // load real receiver bx r12 // call imp CacheLookup2 STRET // cache miss ldr r9, [r1, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass ldr r1, [r1, #RECEIVER] // load real receiver b __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper2_stret ENTRY _objc_msgLookupSuper2_stret ldr r9, [r1, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass CacheLookup STRET // cache hit, IMP in r12, ne already set for stret forwarding ldr r1, [r1, #RECEIVER] // load real receiver bx lr CacheLookup2 STRET // cache miss ldr r9, [r1, #CLASS] ldr r9, [r9, #SUPERCLASS] // r9 = class to search ldr r1, [r1, #RECEIVER] // load real receiver b __objc_msgLookup_stret_uncached END_ENTRY _objc_msgLookupSuper2_stret ///////////////////////////////////////////////////////////////////// // // MethodTableLookup NORMAL|STRET // // Locate the implementation for a selector in a class's method lists. // // Takes: // $0 = NORMAL, STRET // r0 or r1 (STRET) = receiver // r1 or r2 (STRET) = selector // r9 = class to search in // // On exit: IMP in r12, eq/ne set for forwarding // ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup stmfd sp!, {r0-r3,r7,lr} add r7, sp, #16 sub sp, #8 // align stack FP_SAVE .if $0 == NORMAL // receiver already in r0 // selector already in r1 .else mov r0, r1 // receiver mov r1, r2 // selector .endif mov r2, r9 // class to search blx __class_lookupMethodAndLoadCache3 mov r12, r0 // r12 = IMP .if $0 == NORMAL cmp r12, r12 // set eq for nonstret forwarding .else tst r12, r12 // set ne for stret forwarding .endif FP_RESTORE add sp, #8 // align stack ldmfd sp!, {r0-r3,r7,lr} .endmacro /******************************************************************** * * _objc_msgSend_uncached * _objc_msgSend_stret_uncached * _objc_msgLookup_uncached * _objc_msgLookup_stret_uncached * The uncached method lookup. * ********************************************************************/ STATIC_ENTRY __objc_msgSend_uncached // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r9 is the class to search MethodTableLookup NORMAL // returns IMP in r12 bx r12 END_ENTRY __objc_msgSend_uncached STATIC_ENTRY __objc_msgSend_stret_uncached // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r9 is the class to search MethodTableLookup STRET // returns IMP in r12 bx r12 END_ENTRY __objc_msgSend_stret_uncached STATIC_ENTRY __objc_msgLookup_uncached // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r9 is the class to search MethodTableLookup NORMAL // returns IMP in r12 bx lr END_ENTRY __objc_msgLookup_uncached STATIC_ENTRY __objc_msgLookup_stret_uncached // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r9 is the class to search MethodTableLookup STRET // returns IMP in r12 bx lr END_ENTRY __objc_msgLookup_stret_uncached /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); * * _objc_msgForward and _objc_msgForward_stret are the externally-callable * functions returned by things like method_getImplementation(). * _objc_msgForward_impcache is the function pointer actually stored in * method caches. * ********************************************************************/ MI_EXTERN(__objc_forward_handler) MI_EXTERN(__objc_forward_stret_handler) STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret beq __objc_msgForward b __objc_msgForward_stret END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward // Non-stret version MI_GET_EXTERN(r12, __objc_forward_handler) ldr r12, [r12] bx r12 END_ENTRY __objc_msgForward ENTRY __objc_msgForward_stret // Struct-return version MI_GET_EXTERN(r12, __objc_forward_stret_handler) ldr r12, [r12] bx r12 END_ENTRY __objc_msgForward_stret ENTRY _objc_msgSend_noarg b _objc_msgSend END_ENTRY _objc_msgSend_noarg ENTRY _objc_msgSend_debug b _objc_msgSend END_ENTRY _objc_msgSend_debug ENTRY _objc_msgSendSuper2_debug b _objc_msgSendSuper2 END_ENTRY _objc_msgSendSuper2_debug ENTRY _objc_msgSend_stret_debug b _objc_msgSend_stret END_ENTRY _objc_msgSend_stret_debug ENTRY _objc_msgSendSuper2_stret_debug b _objc_msgSendSuper2_stret END_ENTRY _objc_msgSendSuper2_stret_debug ENTRY _method_invoke // r1 is method triplet instead of SEL ldr r12, [r1, #METHOD_IMP] ldr r1, [r1, #METHOD_NAME] bx r12 END_ENTRY _method_invoke ENTRY _method_invoke_stret // r2 is method triplet instead of SEL ldr r12, [r2, #METHOD_IMP] ldr r2, [r2, #METHOD_NAME] bx r12 END_ENTRY _method_invoke_stret .section __DATA,__objc_msg_break .long 0 .long 0 #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Messengers.subproj/objc-msg-arm64.s ================================================ /* * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 2011 Apple Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /******************************************************************** * * objc-msg-arm64.s - ARM64 code to support objc messaging * ********************************************************************/ #ifdef __arm64__ #include #include "isa.h" #include "arm64-asm.h" .data // _objc_entryPoints and _objc_exitPoints are used by method dispatch // caching code to figure out whether any threads are actively // in the cache for dispatching. The labels surround the asm code // that do cache lookups. The tables are zero-terminated. .align 4 .private_extern _objc_entryPoints _objc_entryPoints: PTR _cache_getImp PTR _objc_msgSend PTR _objc_msgSendSuper PTR _objc_msgSendSuper2 PTR _objc_msgLookup PTR _objc_msgLookupSuper2 PTR 0 .private_extern _objc_exitPoints _objc_exitPoints: PTR LExit_cache_getImp PTR LExit_objc_msgSend PTR LExit_objc_msgSendSuper PTR LExit_objc_msgSendSuper2 PTR LExit_objc_msgLookup PTR LExit_objc_msgLookupSuper2 PTR 0 /* objc_super parameter to sendSuper */ #define RECEIVER 0 #define CLASS __SIZEOF_POINTER__ /* Selected field offsets in class structure */ #define SUPERCLASS __SIZEOF_POINTER__ #define CACHE (2 * __SIZEOF_POINTER__) /* Selected field offsets in method structure */ #define METHOD_NAME 0 #define METHOD_TYPES __SIZEOF_POINTER__ #define METHOD_IMP (2 * __SIZEOF_POINTER__) #define BUCKET_SIZE (2 * __SIZEOF_POINTER__) /******************************************************************** * GetClassFromIsa_p16 src * src is a raw isa field. Sets p16 to the corresponding class pointer. * The raw isa might be an indexed isa to be decoded, or a * packed isa that needs to be masked. * * On exit: * $0 is unchanged * p16 is a class pointer * x10 is clobbered ********************************************************************/ #if SUPPORT_INDEXED_ISA .align 3 .globl _objc_indexed_classes _objc_indexed_classes: .fill ISA_INDEX_COUNT, PTRSIZE, 0 #endif .macro GetClassFromIsa_p16 /* src */ #if SUPPORT_INDEXED_ISA // Indexed isa mov p16, $0 // optimistically set dst = src tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa // isa in p16 is indexed adrp x10, _objc_indexed_classes@PAGE add x10, x10, _objc_indexed_classes@PAGEOFF ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array 1: #elif __LP64__ // 64-bit packed isa and p16, $0, #ISA_MASK #else // 32-bit raw isa mov p16, $0 #endif .endmacro /******************************************************************** * ENTRY functionName * STATIC_ENTRY functionName * END_ENTRY functionName ********************************************************************/ .macro ENTRY /* name */ .text .align 5 .globl $0 $0: .endmacro .macro STATIC_ENTRY /*name*/ .text .align 5 .private_extern $0 $0: .endmacro .macro END_ENTRY /* name */ LExit$0: .endmacro /******************************************************************** * UNWIND name, flags * Unwind info generation ********************************************************************/ .macro UNWIND .section __LD,__compact_unwind,regular,debug PTR $0 .set LUnwind$0, LExit$0 - $0 .long LUnwind$0 .long $1 PTR 0 /* no personality */ PTR 0 /* no LSDA */ .text .endmacro #define NoFrame 0x02000000 // no frame, no SP adjustment #define FrameWithNoSaves 0x04000000 // frame, no non-volatile saves /******************************************************************** * * CacheLookup NORMAL|GETIMP|LOOKUP * * Locate the implementation for a selector in a class method cache. * * Takes: * x1 = selector * x16 = class to be searched * * Kills: * x9,x10,x11,x12, x17 * * On exit: (found) calls or returns IMP * with x16 = class, x17 = IMP * (not found) jumps to LCacheMiss * ********************************************************************/ #define NORMAL 0 #define GETIMP 1 #define LOOKUP 2 // CacheHit: x17 = cached IMP, x12 = address of cached IMP .macro CacheHit .if $0 == NORMAL TailCallCachedImp x17, x12 // authenticate and call imp .elseif $0 == GETIMP mov p0, p17 AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP ret // return IMP .elseif $0 == LOOKUP AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP ret // return imp via x17 .else .abort oops .endif .endmacro .macro CheckMiss // miss if bucket->sel == 0 .if $0 == GETIMP cbz p9, LGetImpMiss .elseif $0 == NORMAL cbz p9, __objc_msgSend_uncached .elseif $0 == LOOKUP cbz p9, __objc_msgLookup_uncached .else .abort oops .endif .endmacro .macro JumpMiss .if $0 == GETIMP b LGetImpMiss .elseif $0 == NORMAL b __objc_msgSend_uncached .elseif $0 == LOOKUP b __objc_msgLookup_uncached .else .abort oops .endif .endmacro .macro CacheLookup // p1 = SEL, p16 = isa ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask #if !__LP64__ and w11, w11, 0xffff // p11 = mask #endif and w12, w1, w11 // x12 = _cmd & mask add p12, p10, p12, LSL #(1+PTRSHIFT) // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) ldp p17, p9, [x12] // {imp, sel} = *bucket 1: cmp p9, p1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: p12 = not-hit bucket CheckMiss $0 // miss if bucket->sel == 0 cmp p12, p10 // wrap if bucket == buckets b.eq 3f ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket b 1b // loop 3: // wrap: p12 = first bucket, w11 = mask add p12, p12, w11, UXTW #(1+PTRSHIFT) // p12 = buckets + (mask << 1+PTRSHIFT) // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. ldp p17, p9, [x12] // {imp, sel} = *bucket 1: cmp p9, p1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: p12 = not-hit bucket CheckMiss $0 // miss if bucket->sel == 0 cmp p12, p10 // wrap if bucket == buckets b.eq 3f ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket b 1b // loop 3: // double wrap JumpMiss $0 .endmacro /******************************************************************** * * id objc_msgSend(id self, SEL _cmd, ...); * IMP objc_msgLookup(id self, SEL _cmd, ...); * * objc_msgLookup ABI: * IMP returned in x17 * x16 reserved for our use but not used * ********************************************************************/ #if SUPPORT_TAGGED_POINTERS .data .align 3 .globl _objc_debug_taggedpointer_classes _objc_debug_taggedpointer_classes: .fill 16, 8, 0 .globl _objc_debug_taggedpointer_ext_classes _objc_debug_taggedpointer_ext_classes: .fill 256, 8, 0 #endif ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame cmp p0, #0 // nil check and tagged pointer check #if SUPPORT_TAGGED_POINTERS b.le LNilOrTagged // (MSB tagged pointer looks negative) #else b.eq LReturnZero #endif ldr p13, [x0] // p13 = isa GetClassFromIsa_p16 p13 // p16 = class LGetIsaDone: CacheLookup NORMAL // calls imp or objc_msgSend_uncached #if SUPPORT_TAGGED_POINTERS LNilOrTagged: b.eq LReturnZero // nil check // tagged adrp x10, _objc_debug_taggedpointer_classes@PAGE add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF ubfx x11, x0, #60, #4 ldr x16, [x10, x11, LSL #3] adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF cmp x10, x16 b.ne LGetIsaDone // ext tagged adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF ubfx x11, x0, #52, #8 ldr x16, [x10, x11, LSL #3] b LGetIsaDone // SUPPORT_TAGGED_POINTERS #endif LReturnZero: // x0 is already zero mov x1, #0 movi d0, #0 movi d1, #0 movi d2, #0 movi d3, #0 ret END_ENTRY _objc_msgSend ENTRY _objc_msgLookup UNWIND _objc_msgLookup, NoFrame cmp p0, #0 // nil check and tagged pointer check #if SUPPORT_TAGGED_POINTERS b.le LLookup_NilOrTagged // (MSB tagged pointer looks negative) #else b.eq LLookup_Nil #endif ldr p13, [x0] // p13 = isa GetClassFromIsa_p16 p13 // p16 = class LLookup_GetIsaDone: CacheLookup LOOKUP // returns imp #if SUPPORT_TAGGED_POINTERS LLookup_NilOrTagged: b.eq LLookup_Nil // nil check // tagged mov x10, #0xf000000000000000 cmp x0, x10 b.hs LLookup_ExtTag adrp x10, _objc_debug_taggedpointer_classes@PAGE add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF ubfx x11, x0, #60, #4 ldr x16, [x10, x11, LSL #3] b LLookup_GetIsaDone LLookup_ExtTag: adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF ubfx x11, x0, #52, #8 ldr x16, [x10, x11, LSL #3] b LLookup_GetIsaDone // SUPPORT_TAGGED_POINTERS #endif LLookup_Nil: adrp x17, __objc_msgNil@PAGE add x17, x17, __objc_msgNil@PAGEOFF ret END_ENTRY _objc_msgLookup STATIC_ENTRY __objc_msgNil // x0 is already zero mov x1, #0 movi d0, #0 movi d1, #0 movi d2, #0 movi d3, #0 ret END_ENTRY __objc_msgNil ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame ldp p0, p16, [x0] // p0 = real receiver, p16 = class CacheLookup NORMAL // calls imp or objc_msgSend_uncached END_ENTRY _objc_msgSendSuper // no _objc_msgLookupSuper ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame ldp p0, p16, [x0] // p0 = real receiver, p16 = class ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass CacheLookup NORMAL END_ENTRY _objc_msgSendSuper2 ENTRY _objc_msgLookupSuper2 UNWIND _objc_msgLookupSuper2, NoFrame ldp p0, p16, [x0] // p0 = real receiver, p16 = class ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass CacheLookup LOOKUP END_ENTRY _objc_msgLookupSuper2 .macro MethodTableLookup // push frame SignLR stp fp, lr, [sp, #-16]! mov fp, sp // save parameter registers: x0..x8, q0..q7 sub sp, sp, #(10*8 + 8*16) stp q0, q1, [sp, #(0*16)] stp q2, q3, [sp, #(2*16)] stp q4, q5, [sp, #(4*16)] stp q6, q7, [sp, #(6*16)] stp x0, x1, [sp, #(8*16+0*8)] stp x2, x3, [sp, #(8*16+2*8)] stp x4, x5, [sp, #(8*16+4*8)] stp x6, x7, [sp, #(8*16+6*8)] str x8, [sp, #(8*16+8*8)] // receiver and selector already in x0 and x1 mov x2, x16 bl __class_lookupMethodAndLoadCache3 // IMP in x0 mov x17, x0 // restore registers and return ldp q0, q1, [sp, #(0*16)] ldp q2, q3, [sp, #(2*16)] ldp q4, q5, [sp, #(4*16)] ldp q6, q7, [sp, #(6*16)] ldp x0, x1, [sp, #(8*16+0*8)] ldp x2, x3, [sp, #(8*16+2*8)] ldp x4, x5, [sp, #(8*16+4*8)] ldp x6, x7, [sp, #(8*16+6*8)] ldr x8, [sp, #(8*16+8*8)] mov sp, fp ldp fp, lr, [sp], #16 AuthenticateLR .endmacro STATIC_ENTRY __objc_msgSend_uncached UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band p16 is the class to search MethodTableLookup TailCallFunctionPointer x17 END_ENTRY __objc_msgSend_uncached STATIC_ENTRY __objc_msgLookup_uncached UNWIND __objc_msgLookup_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band p16 is the class to search MethodTableLookup ret END_ENTRY __objc_msgLookup_uncached STATIC_ENTRY _cache_getImp GetClassFromIsa_p16 p0 CacheLookup GETIMP LGetImpMiss: mov p0, #0 ret END_ENTRY _cache_getImp /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); * * _objc_msgForward is the externally-callable * function returned by things like method_getImplementation(). * _objc_msgForward_impcache is the function pointer actually stored in * method caches. * ********************************************************************/ STATIC_ENTRY __objc_msgForward_impcache // No stret specialization. b __objc_msgForward END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward adrp x17, __objc_forward_handler@PAGE ldr p17, [x17, __objc_forward_handler@PAGEOFF] TailCallFunctionPointer x17 END_ENTRY __objc_msgForward ENTRY _objc_msgSend_noarg b _objc_msgSend END_ENTRY _objc_msgSend_noarg ENTRY _objc_msgSend_debug b _objc_msgSend END_ENTRY _objc_msgSend_debug ENTRY _objc_msgSendSuper2_debug b _objc_msgSendSuper2 END_ENTRY _objc_msgSendSuper2_debug ENTRY _method_invoke // x1 is method triplet instead of SEL add p16, p1, #METHOD_IMP ldr p17, [x16] ldr p1, [x1, #METHOD_NAME] TailCallMethodListImp x17, x16 END_ENTRY _method_invoke #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Messengers.subproj/objc-msg-i386.s ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #if defined(__i386__) && !TARGET_OS_SIMULATOR /******************************************************************** ******************************************************************** ** ** objc-msg-i386.s - i386 code to support objc messaging. ** ******************************************************************** ********************************************************************/ /******************************************************************** * Data used by the ObjC runtime. * ********************************************************************/ .data // _objc_entryPoints and _objc_exitPoints are used by objc // to get the critical regions for which method caches // cannot be garbage collected. .align 2 .private_extern _objc_entryPoints _objc_entryPoints: .long __cache_getImp .long __cache_getMethod .long _objc_msgSend .long _objc_msgSend_fpret .long _objc_msgSend_stret .long _objc_msgSendSuper .long _objc_msgSendSuper_stret .long 0 .private_extern _objc_exitPoints _objc_exitPoints: .long LGetImpExit .long LGetMethodExit .long LMsgSendExit .long LMsgSendFpretExit .long LMsgSendStretExit .long LMsgSendSuperExit .long LMsgSendSuperStretExit .long 0 /******************************************************************** * * Common offsets. * ********************************************************************/ self = 4 super = 4 selector = 8 marg_size = 12 marg_list = 16 first_arg = 12 struct_addr = 4 self_stret = 8 super_stret = 8 selector_stret = 12 marg_size_stret = 16 marg_list_stret = 20 /******************************************************************** * * Structure definitions. * ********************************************************************/ // objc_super parameter to sendSuper receiver = 0 class = 4 // Selected field offsets in class structure isa = 0 cache = 32 // Method descriptor method_name = 0 method_imp = 8 // Cache header mask = 0 occupied = 4 buckets = 8 // variable length array #if defined(OBJC_INSTRUMENTED) // Cache instrumentation data, follows buckets hitCount = 0 hitProbes = hitCount + 4 maxHitProbes = hitProbes + 4 missCount = maxHitProbes + 4 missProbes = missCount + 4 maxMissProbes = missProbes + 4 flushCount = maxMissProbes + 4 flushedEntries = flushCount + 4 // Buckets in CacheHitHistogram and CacheMissHistogram CACHE_HISTOGRAM_SIZE = 512 #endif ////////////////////////////////////////////////////////////////////// // // ENTRY functionName // // Assembly directives to begin an exported function. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro ENTRY .text .globl $0 .align 4, 0x90 $0: .endmacro .macro STATIC_ENTRY .text .private_extern $0 .align 4, 0x90 $0: .endmacro ////////////////////////////////////////////////////////////////////// // // END_ENTRY functionName // // Assembly directives to end an exported function. Just a placeholder, // a close-parenthesis for ENTRY, until it is needed for something. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro END_ENTRY .endmacro ////////////////////////////////////////////////////////////////////// // // CALL_MCOUNTER // // Calls mcount() profiling routine. Must be called immediately on // function entry, before any prologue executes. // ////////////////////////////////////////////////////////////////////// .macro CALL_MCOUNTER #ifdef PROFILE // Current stack contents: ret pushl %ebp movl %esp,%ebp subl $$8,%esp // Current stack contents: ret, ebp, pad, pad call mcount movl %ebp,%esp popl %ebp #endif .endmacro ///////////////////////////////////////////////////////////////////// // // // CacheLookup WORD_RETURN | STRUCT_RETURN, MSG_SEND | MSG_SENDSUPER | CACHE_GET, cacheMissLabel // // Locate the implementation for a selector in a class method cache. // // Takes: WORD_RETURN (first parameter is at sp+4) // STRUCT_RETURN (struct address is at sp+4, first parameter at sp+8) // MSG_SEND (first parameter is receiver) // MSG_SENDSUPER (first parameter is address of objc_super structure) // CACHE_GET (first parameter is class; return method triplet) // selector in %ecx // class to search in %edx // // cacheMissLabel = label to branch to iff method is not cached // // On exit: (found) MSG_SEND and MSG_SENDSUPER: return imp in eax // (found) CACHE_GET: return method triplet in eax // (not found) jumps to cacheMissLabel // ///////////////////////////////////////////////////////////////////// // Values to specify to method lookup macros whether the return type of // the method is word or structure. WORD_RETURN = 0 STRUCT_RETURN = 1 // Values to specify to method lookup macros whether the first argument // is an object/class reference or a 'objc_super' structure. MSG_SEND = 0 // first argument is receiver, search the isa MSG_SENDSUPER = 1 // first argument is objc_super, search the class CACHE_GET = 2 // first argument is class, search that class .macro CacheLookup // load variables and save caller registers. pushl %edi // save scratch register movl cache(%edx), %edi // cache = class->cache pushl %esi // save scratch register #if defined(OBJC_INSTRUMENTED) pushl %ebx // save non-volatile register pushl %eax // save cache pointer xorl %ebx, %ebx // probeCount = 0 #endif movl mask(%edi), %esi // mask = cache->mask movl %ecx, %edx // index = selector shrl $$2, %edx // index = selector >> 2 // search the receiver's cache // ecx = selector // edi = cache // esi = mask // edx = index // eax = method (soon) LMsgSendProbeCache_$0_$1_$2: #if defined(OBJC_INSTRUMENTED) addl $$1, %ebx // probeCount += 1 #endif andl %esi, %edx // index &= mask movl buckets(%edi, %edx, 4), %eax // meth = cache->buckets[index] testl %eax, %eax // check for end of bucket je LMsgSendCacheMiss_$0_$1_$2 // go to cache miss code cmpl method_name(%eax), %ecx // check for method name match je LMsgSendCacheHit_$0_$1_$2 // go handle cache hit addl $$1, %edx // bump index ... jmp LMsgSendProbeCache_$0_$1_$2 // ... and loop // not found in cache: restore state and go to callers handler LMsgSendCacheMiss_$0_$1_$2: #if defined(OBJC_INSTRUMENTED) popl %edx // retrieve cache pointer movl mask(%edx), %esi // mask = cache->mask testl %esi, %esi // a mask of zero is only for the... je LMsgSendMissInstrumentDone_$0_$1_$2 // ... emptyCache, do not record anything // locate and update the CacheInstrumentation structure addl $$1, %esi // entryCount = mask + 1 shll $$2, %esi // tableSize = entryCount * sizeof(entry) addl $buckets, %esi // offset = buckets + tableSize addl %edx, %esi // cacheData = &cache->buckets[mask+1] movl missCount(%esi), %edi // addl $$1, %edi // movl %edi, missCount(%esi) // cacheData->missCount += 1 movl missProbes(%esi), %edi // addl %ebx, %edi // movl %edi, missProbes(%esi) // cacheData->missProbes += probeCount movl maxMissProbes(%esi), %edi// if (cacheData->maxMissProbes < probeCount) cmpl %ebx, %edi // jge LMsgSendMaxMissProbeOK_$0_$1_$2 // movl %ebx, maxMissProbes(%esi)// cacheData->maxMissProbes = probeCount LMsgSendMaxMissProbeOK_$0_$1_$2: // update cache miss probe histogram cmpl $CACHE_HISTOGRAM_SIZE, %ebx // pin probeCount to max index jl LMsgSendMissHistoIndexSet_$0_$1_$2 movl $(CACHE_HISTOGRAM_SIZE-1), %ebx LMsgSendMissHistoIndexSet_$0_$1_$2: LEA_STATIC_DATA %esi, _CacheMissHistogram, EXTERNAL_SYMBOL shll $$2, %ebx // convert probeCount to histogram index addl %ebx, %esi // calculate &CacheMissHistogram[probeCount<<2] movl 0(%esi), %edi // get current tally addl $$1, %edi // movl %edi, 0(%esi) // tally += 1 LMsgSendMissInstrumentDone_$0_$1_$2: popl %ebx // restore non-volatile register #endif .if $0 == WORD_RETURN // Regular word return .if $1 == MSG_SEND // MSG_SEND popl %esi // restore callers register popl %edi // restore callers register movl self(%esp), %edx // get messaged object movl isa(%edx), %eax // get objects class .elseif $1 == MSG_SENDSUPER // MSG_SENDSUPER // replace "super" arg with "receiver" movl super+8(%esp), %edi // get super structure movl receiver(%edi), %edx // get messaged object movl %edx, super+8(%esp) // make it the first argument movl class(%edi), %eax // get messaged class popl %esi // restore callers register popl %edi // restore callers register .else // CACHE_GET popl %esi // restore callers register popl %edi // restore callers register .endif .else // Struct return .if $1 == MSG_SEND // MSG_SEND (stret) popl %esi // restore callers register popl %edi // restore callers register movl self_stret(%esp), %edx // get messaged object movl isa(%edx), %eax // get objects class .elseif $1 == MSG_SENDSUPER // MSG_SENDSUPER (stret) // replace "super" arg with "receiver" movl super_stret+8(%esp), %edi// get super structure movl receiver(%edi), %edx // get messaged object movl %edx, super_stret+8(%esp)// make it the first argument movl class(%edi), %eax // get messaged class popl %esi // restore callers register popl %edi // restore callers register .else // CACHE_GET !! This should not happen. .endif .endif // edx = receiver // ecx = selector // eax = class jmp $2 // go to callers handler // eax points to matching cache entry .align 4, 0x90 LMsgSendCacheHit_$0_$1_$2: #if defined(OBJC_INSTRUMENTED) popl %edx // retrieve cache pointer movl mask(%edx), %esi // mask = cache->mask testl %esi, %esi // a mask of zero is only for the... je LMsgSendHitInstrumentDone_$0_$1_$2 // ... emptyCache, do not record anything // locate and update the CacheInstrumentation structure addl $$1, %esi // entryCount = mask + 1 shll $$2, %esi // tableSize = entryCount * sizeof(entry) addl $buckets, %esi // offset = buckets + tableSize addl %edx, %esi // cacheData = &cache->buckets[mask+1] movl hitCount(%esi), %edi addl $$1, %edi movl %edi, hitCount(%esi) // cacheData->hitCount += 1 movl hitProbes(%esi), %edi addl %ebx, %edi movl %edi, hitProbes(%esi) // cacheData->hitProbes += probeCount movl maxHitProbes(%esi), %edi// if (cacheData->maxHitProbes < probeCount) cmpl %ebx, %edi jge LMsgSendMaxHitProbeOK_$0_$1_$2 movl %ebx, maxHitProbes(%esi)// cacheData->maxHitProbes = probeCount LMsgSendMaxHitProbeOK_$0_$1_$2: // update cache hit probe histogram cmpl $CACHE_HISTOGRAM_SIZE, %ebx // pin probeCount to max index jl LMsgSendHitHistoIndexSet_$0_$1_$2 movl $(CACHE_HISTOGRAM_SIZE-1), %ebx LMsgSendHitHistoIndexSet_$0_$1_$2: LEA_STATIC_DATA %esi, _CacheHitHistogram, EXTERNAL_SYMBOL shll $$2, %ebx // convert probeCount to histogram index addl %ebx, %esi // calculate &CacheHitHistogram[probeCount<<2] movl 0(%esi), %edi // get current tally addl $$1, %edi // movl %edi, 0(%esi) // tally += 1 LMsgSendHitInstrumentDone_$0_$1_$2: popl %ebx // restore non-volatile register #endif // load implementation address, restore state, and we're done .if $1 == CACHE_GET // method triplet is already in eax .else movl method_imp(%eax), %eax // imp = method->method_imp .endif .if $0 == WORD_RETURN // Regular word return .if $1 == MSG_SENDSUPER // MSG_SENDSUPER // replace "super" arg with "self" movl super+8(%esp), %edi movl receiver(%edi), %esi movl %esi, super+8(%esp) .endif .else // Struct return .if $1 == MSG_SENDSUPER // MSG_SENDSUPER (stret) // replace "super" arg with "self" movl super_stret+8(%esp), %edi movl receiver(%edi), %esi movl %esi, super_stret+8(%esp) .endif .endif // restore caller registers popl %esi popl %edi .endmacro ///////////////////////////////////////////////////////////////////// // // MethodTableLookup WORD_RETURN | STRUCT_RETURN, MSG_SEND | MSG_SENDSUPER // // Takes: WORD_RETURN (first parameter is at sp+4) // STRUCT_RETURN (struct address is at sp+4, first parameter at sp+8) // MSG_SEND (first parameter is receiver) // MSG_SENDSUPER (first parameter is address of objc_super structure) // // edx = receiver // ecx = selector // eax = class // (all set by CacheLookup's miss case) // // Stack must be at 0xXXXXXXXc on entrance. // // On exit: esp unchanged // imp in eax // ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup // stack has return address and nothing else subl $$(12+5*16), %esp movdqa %xmm3, 4*16(%esp) movdqa %xmm2, 3*16(%esp) movdqa %xmm1, 2*16(%esp) movdqa %xmm0, 1*16(%esp) movl %eax, 8(%esp) // class movl %ecx, 4(%esp) // selector movl %edx, 0(%esp) // receiver call __class_lookupMethodAndLoadCache3 movdqa 4*16(%esp), %xmm3 movdqa 3*16(%esp), %xmm2 movdqa 2*16(%esp), %xmm1 movdqa 1*16(%esp), %xmm0 addl $$(12+5*16), %esp // pop parameters .endmacro /******************************************************************** * Method _cache_getMethod(Class cls, SEL sel, IMP msgForward_internal_imp) * * If found, returns method triplet pointer. * If not found, returns NULL. * * NOTE: _cache_getMethod never returns any cache entry whose implementation * is _objc_msgForward_impcache. It returns 1 instead. This prevents thread- * safety and memory management bugs in _class_lookupMethodAndLoadCache. * See _class_lookupMethodAndLoadCache for details. * * _objc_msgForward_impcache is passed as a parameter because it's more * efficient to do the (PIC) lookup once in the caller than repeatedly here. ********************************************************************/ STATIC_ENTRY __cache_getMethod // load the class and selector movl selector(%esp), %ecx movl self(%esp), %edx // do lookup CacheLookup WORD_RETURN, CACHE_GET, LGetMethodMiss // cache hit, method triplet in %eax movl first_arg(%esp), %ecx // check for _objc_msgForward_impcache cmpl method_imp(%eax), %ecx // if (imp==_objc_msgForward_impcache) je 1f // return (Method)1 ret // else return method triplet address 1: movl $1, %eax ret LGetMethodMiss: // cache miss, return nil xorl %eax, %eax // zero %eax ret LGetMethodExit: END_ENTRY __cache_getMethod /******************************************************************** * IMP _cache_getImp(Class cls, SEL sel) * * If found, returns method implementation. * If not found, returns NULL. ********************************************************************/ STATIC_ENTRY __cache_getImp // load the class and selector movl selector(%esp), %ecx movl self(%esp), %edx // do lookup CacheLookup WORD_RETURN, CACHE_GET, LGetImpMiss // cache hit, method triplet in %eax movl method_imp(%eax), %eax // return method imp ret LGetImpMiss: // cache miss, return nil xorl %eax, %eax // zero %eax ret LGetImpExit: END_ENTRY __cache_getImp /******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); * ********************************************************************/ ENTRY _objc_msgSend CALL_MCOUNTER // load receiver and selector movl selector(%esp), %ecx movl self(%esp), %eax // check whether receiver is nil testl %eax, %eax je LMsgSendNilSelf // receiver (in %eax) is non-nil: search the cache LMsgSendReceiverOk: movl isa(%eax), %edx // class = self->isa CacheLookup WORD_RETURN, MSG_SEND, LMsgSendCacheMiss xor %edx, %edx // set nonstret for msgForward_internal jmp *%eax // cache miss: go search the method lists LMsgSendCacheMiss: MethodTableLookup WORD_RETURN, MSG_SEND xor %edx, %edx // set nonstret for msgForward_internal jmp *%eax // goto *imp // message sent to nil: redirect to nil receiver, if any LMsgSendNilSelf: // %eax is already zero movl $0,%edx xorps %xmm0, %xmm0 LMsgSendDone: ret // guaranteed non-nil entry point (disabled for now) // .globl _objc_msgSendNonNil // _objc_msgSendNonNil: // movl self(%esp), %eax // jmp LMsgSendReceiverOk LMsgSendExit: END_ENTRY _objc_msgSend /******************************************************************** * * id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...); * * struct objc_super { * id receiver; * Class class; * }; ********************************************************************/ ENTRY _objc_msgSendSuper CALL_MCOUNTER // load selector and class to search movl super(%esp), %eax // struct objc_super movl selector(%esp), %ecx movl class(%eax), %edx // struct objc_super->class // search the cache (class in %edx) CacheLookup WORD_RETURN, MSG_SENDSUPER, LMsgSendSuperCacheMiss xor %edx, %edx // set nonstret for msgForward_internal jmp *%eax // goto *imp // cache miss: go search the method lists LMsgSendSuperCacheMiss: MethodTableLookup WORD_RETURN, MSG_SENDSUPER xor %edx, %edx // set nonstret for msgForward_internal jmp *%eax // goto *imp // ignored selector: return self LMsgSendSuperIgnored: movl super(%esp), %eax movl receiver(%eax), %eax ret LMsgSendSuperExit: END_ENTRY _objc_msgSendSuper /******************************************************************** * id objc_msgSendv(id self, SEL _cmd, unsigned size, marg_list frame); * * On entry: * (sp+4) is the message receiver, * (sp+8) is the selector, * (sp+12) is the size of the marg_list, in bytes, * (sp+16) is the address of the marg_list * ********************************************************************/ ENTRY _objc_msgSendv #if defined(KERNEL) trap // _objc_msgSendv is not for the kernel #else pushl %ebp movl %esp, %ebp // stack is currently aligned assuming no extra arguments movl (marg_list+4)(%ebp), %edx addl $8, %edx // skip self & selector movl (marg_size+4)(%ebp), %ecx subl $8, %ecx // skip self & selector shrl $2, %ecx je LMsgSendvArgsOK // %esp = %esp - (16 - ((numVariableArguments & 3) << 2)) movl %ecx, %eax // 16-byte align stack andl $3, %eax shll $2, %eax subl $16, %esp addl %eax, %esp LMsgSendvArgLoop: decl %ecx movl 0(%edx, %ecx, 4), %eax pushl %eax jg LMsgSendvArgLoop LMsgSendvArgsOK: movl (selector+4)(%ebp), %ecx pushl %ecx movl (self+4)(%ebp),%ecx pushl %ecx call _objc_msgSend movl %ebp,%esp popl %ebp ret #endif END_ENTRY _objc_msgSendv /******************************************************************** * * double objc_msgSend_fpret(id self, SEL _cmd,...); * ********************************************************************/ ENTRY _objc_msgSend_fpret CALL_MCOUNTER // load receiver and selector movl selector(%esp), %ecx movl self(%esp), %eax // check whether receiver is nil testl %eax, %eax je LMsgSendFpretNilSelf // receiver (in %eax) is non-nil: search the cache LMsgSendFpretReceiverOk: movl isa(%eax), %edx // class = self->isa CacheLookup WORD_RETURN, MSG_SEND, LMsgSendFpretCacheMiss xor %edx, %edx // set nonstret for msgForward_internal jmp *%eax // goto *imp // cache miss: go search the method lists LMsgSendFpretCacheMiss: MethodTableLookup WORD_RETURN, MSG_SEND xor %edx, %edx // set nonstret for msgForward_internal jmp *%eax // goto *imp // message sent to nil: redirect to nil receiver, if any LMsgSendFpretNilSelf: // %eax is already zero fldz LMsgSendFpretDone: ret LMsgSendFpretExit: END_ENTRY _objc_msgSend_fpret /******************************************************************** * double objc_msgSendv_fpret(id self, SEL _cmd, unsigned size, marg_list frame); * * On entry: * (sp+4) is the message receiver, * (sp+8) is the selector, * (sp+12) is the size of the marg_list, in bytes, * (sp+16) is the address of the marg_list * ********************************************************************/ ENTRY _objc_msgSendv_fpret #if defined(KERNEL) trap // _objc_msgSendv is not for the kernel #else pushl %ebp movl %esp, %ebp // stack is currently aligned assuming no extra arguments movl (marg_list+4)(%ebp), %edx addl $8, %edx // skip self & selector movl (marg_size+4)(%ebp), %ecx subl $8, %ecx // skip self & selector shrl $2, %ecx je LMsgSendvFpretArgsOK // %esp = %esp - (16 - ((numVariableArguments & 3) << 2)) movl %ecx, %eax // 16-byte align stack andl $3, %eax shll $2, %eax subl $16, %esp addl %eax, %esp LMsgSendvFpretArgLoop: decl %ecx movl 0(%edx, %ecx, 4), %eax pushl %eax jg LMsgSendvFpretArgLoop LMsgSendvFpretArgsOK: movl (selector+4)(%ebp), %ecx pushl %ecx movl (self+4)(%ebp),%ecx pushl %ecx call _objc_msgSend_fpret movl %ebp,%esp popl %ebp ret #endif END_ENTRY _objc_msgSendv_fpret /******************************************************************** * * void objc_msgSend_stret(void *st_addr , id self, SEL _cmd, ...); * * * objc_msgSend_stret is the struct-return form of msgSend. * The ABI calls for (sp+4) to be used as the address of the structure * being returned, with the parameters in the succeeding locations. * * On entry: (sp+4)is the address where the structure is returned, * (sp+8) is the message receiver, * (sp+12) is the selector ********************************************************************/ ENTRY _objc_msgSend_stret CALL_MCOUNTER // load receiver and selector movl self_stret(%esp), %eax movl (selector_stret)(%esp), %ecx // check whether receiver is nil testl %eax, %eax je LMsgSendStretNilSelf // receiver (in %eax) is non-nil: search the cache LMsgSendStretReceiverOk: movl isa(%eax), %edx // class = self->isa CacheLookup STRUCT_RETURN, MSG_SEND, LMsgSendStretCacheMiss movl $1, %edx // set stret for objc_msgForward jmp *%eax // goto *imp // cache miss: go search the method lists LMsgSendStretCacheMiss: MethodTableLookup STRUCT_RETURN, MSG_SEND movl $1, %edx // set stret for objc_msgForward jmp *%eax // goto *imp // message sent to nil: redirect to nil receiver, if any LMsgSendStretNilSelf: ret $4 // pop struct return address (#2995932) // guaranteed non-nil entry point (disabled for now) // .globl _objc_msgSendNonNil_stret // _objc_msgSendNonNil_stret: // CALL_MCOUNTER // movl self_stret(%esp), %eax // jmp LMsgSendStretReceiverOk LMsgSendStretExit: END_ENTRY _objc_msgSend_stret /******************************************************************** * * void objc_msgSendSuper_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); * * struct objc_super { * id receiver; * Class class; * }; * * objc_msgSendSuper_stret is the struct-return form of msgSendSuper. * The ABI calls for (sp+4) to be used as the address of the structure * being returned, with the parameters in the succeeding registers. * * On entry: (sp+4)is the address where the structure is returned, * (sp+8) is the address of the objc_super structure, * (sp+12) is the selector * ********************************************************************/ ENTRY _objc_msgSendSuper_stret CALL_MCOUNTER // load selector and class to search movl super_stret(%esp), %eax // struct objc_super movl (selector_stret)(%esp), %ecx // get selector movl class(%eax), %edx // struct objc_super->class // search the cache (class in %edx) CacheLookup STRUCT_RETURN, MSG_SENDSUPER, LMsgSendSuperStretCacheMiss movl $1, %edx // set stret for objc_msgForward jmp *%eax // goto *imp // cache miss: go search the method lists LMsgSendSuperStretCacheMiss: MethodTableLookup STRUCT_RETURN, MSG_SENDSUPER movl $1, %edx // set stret for objc_msgForward jmp *%eax // goto *imp LMsgSendSuperStretExit: END_ENTRY _objc_msgSendSuper_stret /******************************************************************** * void objc_msgSendv_stret(void *st_addr, id self, SEL _cmd, unsigned size, marg_list frame); * * objc_msgSendv_stret is the struct-return form of msgSendv. * This function does not use the struct-return ABI; instead, the * structure return address is passed as a normal parameter. * * On entry: (sp+4) is the address in which the returned struct is put, * (sp+8) is the message receiver, * (sp+12) is the selector, * (sp+16) is the size of the marg_list, in bytes, * (sp+20) is the address of the marg_list * ********************************************************************/ ENTRY _objc_msgSendv_stret #if defined(KERNEL) trap // _objc_msgSendv_stret is not for the kernel #else pushl %ebp movl %esp, %ebp subl $12, %esp // align stack assuming no extra arguments movl (marg_list_stret+4)(%ebp), %edx addl $8, %edx // skip self & selector movl (marg_size_stret+4)(%ebp), %ecx subl $5, %ecx // skip self & selector shrl $2, %ecx jle LMsgSendvStretArgsOK // %esp = %esp - (16 - ((numVariableArguments & 3) << 2)) movl %ecx, %eax // 16-byte align stack andl $3, %eax shll $2, %eax subl $16, %esp addl %eax, %esp LMsgSendvStretArgLoop: decl %ecx movl 0(%edx, %ecx, 4), %eax pushl %eax jg LMsgSendvStretArgLoop LMsgSendvStretArgsOK: movl (selector_stret+4)(%ebp), %ecx pushl %ecx movl (self_stret+4)(%ebp),%ecx pushl %ecx movl (struct_addr+4)(%ebp),%ecx pushl %ecx call _objc_msgSend_stret movl %ebp,%esp popl %ebp ret #endif END_ENTRY _objc_msgSendv_stret /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); * ********************************************************************/ // _FwdSel is @selector(forward::), set up in map_images(). // ALWAYS dereference _FwdSel to get to "forward::" !! .data .align 2 .private_extern _FwdSel _FwdSel: .long 0 .cstring .align 2 LUnkSelStr: .ascii "Does not recognize selector %s (while forwarding %s)\0" .non_lazy_symbol_pointer L_forward_handler: .indirect_symbol __objc_forward_handler .long 0 L_forward_stret_handler: .indirect_symbol __objc_forward_stret_handler .long 0 STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band register %edx is nonzero for stret, zero otherwise // Check return type (stret or not) testl %edx, %edx jnz __objc_msgForward_stret jmp __objc_msgForward END_ENTRY _objc_msgForward_impcache ENTRY __objc_msgForward // Non-struct return version // Get PIC base into %edx call L__objc_msgForward$pic_base L__objc_msgForward$pic_base: popl %edx // Call user handler, if any movl L_forward_handler-L__objc_msgForward$pic_base(%edx),%ecx movl (%ecx), %ecx testl %ecx, %ecx // if not NULL je 1f // skip to default handler jmp *%ecx // call __objc_forward_handler 1: // No user handler // Push stack frame pushl %ebp movl %esp, %ebp // Die if forwarding "forward::" movl (selector+4)(%ebp), %eax movl _FwdSel-L__objc_msgForward$pic_base(%edx),%ecx cmpl %ecx, %eax je LMsgForwardError // Call [receiver forward:sel :margs] subl $8, %esp // 16-byte align the stack leal (self+4)(%ebp), %ecx pushl %ecx // &margs pushl %eax // sel movl _FwdSel-L__objc_msgForward$pic_base(%edx),%ecx pushl %ecx // forward:: pushl (self+4)(%ebp) // receiver call _objc_msgSend movl %ebp, %esp popl %ebp ret LMsgForwardError: // Call __objc_error(receiver, "unknown selector %s %s", "forward::", forwardedSel) subl $8, %esp // 16-byte align the stack pushl (selector+4+4)(%ebp) // the forwarded selector movl _FwdSel-L__objc_msgForward$pic_base(%edx),%eax pushl %eax leal LUnkSelStr-L__objc_msgForward$pic_base(%edx),%eax pushl %eax pushl (self+4)(%ebp) call ___objc_error // never returns END_ENTRY __objc_msgForward ENTRY __objc_msgForward_stret // Struct return version // Get PIC base into %edx call L__objc_msgForwardStret$pic_base L__objc_msgForwardStret$pic_base: popl %edx // Call user handler, if any movl L_forward_stret_handler-L__objc_msgForwardStret$pic_base(%edx), %ecx movl (%ecx), %ecx testl %ecx, %ecx // if not NULL je 1f // skip to default handler jmp *%ecx // call __objc_forward_stret_handler 1: // No user handler // Push stack frame pushl %ebp movl %esp, %ebp // Die if forwarding "forward::" movl (selector_stret+4)(%ebp), %eax movl _FwdSel-L__objc_msgForwardStret$pic_base(%edx), %ecx cmpl %ecx, %eax je LMsgForwardStretError // Call [receiver forward:sel :margs] subl $8, %esp // 16-byte align the stack leal (self_stret+4)(%ebp), %ecx pushl %ecx // &margs pushl %eax // sel movl _FwdSel-L__objc_msgForwardStret$pic_base(%edx),%ecx pushl %ecx // forward:: pushl (self_stret+4)(%ebp) // receiver call _objc_msgSend movl %ebp, %esp popl %ebp ret $4 // pop struct return address (#2995932) LMsgForwardStretError: // Call __objc_error(receiver, "unknown selector %s %s", "forward::", forwardedSelector) subl $8, %esp // 16-byte align the stack pushl (selector_stret+4+4)(%ebp) // the forwarded selector leal _FwdSel-L__objc_msgForwardStret$pic_base(%edx),%eax pushl %eax leal LUnkSelStr-L__objc_msgForwardStret$pic_base(%edx),%eax pushl %eax pushl (self_stret+4)(%ebp) call ___objc_error // never returns END_ENTRY __objc_msgForward_stret ENTRY _method_invoke movl selector(%esp), %ecx movl method_name(%ecx), %edx movl method_imp(%ecx), %eax movl %edx, selector(%esp) jmp *%eax END_ENTRY _method_invoke ENTRY _method_invoke_stret movl selector_stret(%esp), %ecx movl method_name(%ecx), %edx movl method_imp(%ecx), %eax movl %edx, selector_stret(%esp) jmp *%eax END_ENTRY _method_invoke_stret .section __DATA,__objc_msg_break .long 0 .long 0 #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Messengers.subproj/objc-msg-simulator-i386.s ================================================ /* * Copyright (c) 1999-2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #if defined(__i386__) && TARGET_OS_SIMULATOR #include "objc-config.h" .data // _objc_entryPoints and _objc_exitPoints are used by objc // to get the critical regions for which method caches // cannot be garbage collected. .align 2 .private_extern _objc_entryPoints _objc_entryPoints: .long _cache_getImp .long _objc_msgSend .long _objc_msgSend_fpret .long _objc_msgSend_stret .long _objc_msgSendSuper .long _objc_msgSendSuper2 .long _objc_msgSendSuper_stret .long _objc_msgSendSuper2_stret .long _objc_msgLookup .long _objc_msgLookup_fpret .long _objc_msgLookup_stret .long _objc_msgLookupSuper2 .long _objc_msgLookupSuper2_stret .long 0 .private_extern _objc_exitPoints _objc_exitPoints: .long LExit_cache_getImp .long LExit_objc_msgSend .long LExit_objc_msgSend_fpret .long LExit_objc_msgSend_stret .long LExit_objc_msgSendSuper .long LExit_objc_msgSendSuper2 .long LExit_objc_msgSendSuper_stret .long LExit_objc_msgSendSuper2_stret .long LExit_objc_msgLookup .long LExit_objc_msgLookup_fpret .long LExit_objc_msgLookup_stret .long LExit_objc_msgLookupSuper2 .long LExit_objc_msgLookupSuper2_stret .long 0 /******************************************************************** * Names for relative labels * DO NOT USE THESE LABELS ELSEWHERE * Reserved labels: 5: 6: 7: 8: 9: ********************************************************************/ #define LCacheMiss 5 #define LCacheMiss_f 5f #define LCacheMiss_b 5b #define LNilTestDone 6 #define LNilTestDone_f 6f #define LNilTestDone_b 6b #define LNilTestSlow 7 #define LNilTestSlow_f 7f #define LNilTestSlow_b 7b #define LGetIsaDone 8 #define LGetIsaDone_f 8f #define LGetIsaDone_b 8b #define LGetIsaSlow 9 #define LGetIsaSlow_f 9f #define LGetIsaSlow_b 9b /******************************************************************** * Macro parameters ********************************************************************/ #define NORMAL 0 #define FPRET 1 #define STRET 2 #define CALL 100 #define GETIMP 101 #define LOOKUP 102 /******************************************************************** * * Structure definitions. * ********************************************************************/ // Offsets from %esp #define self 4 #define super 4 #define selector 8 #define marg_size 12 #define marg_list 16 #define first_arg 12 #define struct_addr 4 #define self_stret 8 #define super_stret 8 #define selector_stret 12 #define marg_size_stret 16 #define marg_list_stret 20 // objc_super parameter to sendSuper #define receiver 0 #define class 4 // Selected field offsets in class structure #define isa 0 #define superclass 4 #define cache_buckets 8 #define cache_mask 12 // Method cache #define cached_sel 0 #define cached_imp 4 // Method descriptor #define method_name 0 #define method_imp 8 ////////////////////////////////////////////////////////////////////// // // ENTRY functionName // // Assembly directives to begin an exported function. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro ENTRY .text .globl $0 .align 2, 0x90 $0: .endmacro .macro STATIC_ENTRY .text .private_extern $0 .align 4, 0x90 $0: .endmacro ////////////////////////////////////////////////////////////////////// // // END_ENTRY functionName // // Assembly directives to end an exported function. Just a placeholder, // a close-parenthesis for ENTRY, until it is needed for something. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro END_ENTRY LExit$0: .endmacro /******************************************************************** * UNWIND name, flags * Unwind info generation ********************************************************************/ .macro UNWIND .section __LD,__compact_unwind,regular,debug .long $0 .set LUnwind$0, LExit$0 - $0 .long LUnwind$0 .long $1 .long 0 /* no personality */ .long 0 /* no LSDA */ .text .endmacro #define NoFrame 0x02010000 // no frame, no SP adjustment except return address #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller // // Locate the implementation for a selector in a class method cache. // // Takes: // $0 = NORMAL, FPRET, STRET // $1 = CALL, LOOKUP, GETIMP // ecx = selector to search for // edx = class to search // // On exit: ecx clobbered // (found) calls or returns IMP in eax, eq/ne set for forwarding // (not found) jumps to LCacheMiss, class still in edx // ///////////////////////////////////////////////////////////////////// .macro CacheHit // CacheHit must always be preceded by a not-taken `jne` instruction // in case the imp is _objc_msgForward_impcache. // eax = found bucket .if $1 == GETIMP movl cached_imp(%eax), %eax // return imp ret .else .if $0 != STRET // eq already set for forwarding by `jne` .else test %eax, %eax // set ne for stret forwarding .endif .if $1 == CALL jmp *cached_imp(%eax) // call imp .elseif $1 == LOOKUP movl cached_imp(%eax), %eax // return imp ret .else .abort oops .endif .endif .endmacro .macro CacheLookup movzwl cache_mask(%edx), %eax // eax = mask andl %ecx, %eax // eax = SEL & mask shll $$3, %eax // eax = offset = (SEL & mask) * 8 addl cache_buckets(%edx), %eax // eax = bucket = buckets+offset cmpl cached_sel(%eax), %ecx // if (bucket->sel != SEL) jne 1f // scan more // The `jne` above sets flags for CacheHit CacheHit $0, $1 // call or return imp 1: // loop cmpl $$1, cached_sel(%eax) jbe 3f // if (bucket->sel <= 1) wrap or miss addl $$8, %eax // bucket++ 2: cmpl cached_sel(%eax), %ecx // if (bucket->sel != sel) jne 1b // scan more // The `jne` above sets flags for CacheHit CacheHit $0, $1 // call or return imp 3: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap movl cached_imp(%eax), %eax // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. 1: // loop cmpl $$1, cached_sel(%eax) jbe 3f // if (bucket->sel <= 1) wrap or miss addl $$8, %eax // bucket++ 2: cmpl cached_sel(%eax), %ecx // if (bucket->sel != sel) jne 1b // scan more // The `jne` above sets flags for CacheHit CacheHit $0, $1 // call or return imp 3: // double wrap or miss jmp LCacheMiss_f .endmacro ///////////////////////////////////////////////////////////////////// // // MethodTableLookup NORMAL|STRET // // Takes: // receiver (not struct objc_super) and selector on stack // edx = class to search // // On exit: IMP in eax, eq/ne set for forwarding // ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup pushl %ebp movl %esp, %ebp subl $$(8+5*16), %esp .if $0 == NORMAL movl self+4(%ebp), %eax movl selector+4(%ebp), %ecx .else movl self_stret+4(%ebp), %eax movl selector_stret+4(%ebp), %ecx .endif movdqa %xmm3, 4*16(%esp) movdqa %xmm2, 3*16(%esp) movdqa %xmm1, 2*16(%esp) movdqa %xmm0, 1*16(%esp) movl %edx, 8(%esp) // class movl %ecx, 4(%esp) // selector movl %eax, 0(%esp) // receiver call __class_lookupMethodAndLoadCache3 // imp in eax movdqa 4*16(%esp), %xmm3 movdqa 3*16(%esp), %xmm2 movdqa 2*16(%esp), %xmm1 movdqa 1*16(%esp), %xmm0 .if $0 == NORMAL cmp %eax, %eax // set eq for nonstret forwarding .else test %eax, %eax // set ne for stret forwarding .endif leave .endmacro ///////////////////////////////////////////////////////////////////// // // NilTest return-type // // Takes: $0 = NORMAL or FPRET or STRET // eax = receiver // // On exit: Loads non-nil receiver in eax and self(esp) or self_stret(esp), // or returns zero. // // NilTestReturnZero return-type // // Takes: $0 = NORMAL or FPRET or STRET // eax = receiver // // On exit: Loads non-nil receiver in eax and self(esp) or self_stret(esp), // or returns zero. // // NilTestReturnIMP return-type // // Takes: $0 = NORMAL or FPRET or STRET // eax = receiver // // On exit: Loads non-nil receiver in eax and self(esp) or self_stret(esp), // or returns an IMP in eax that returns zero. // ///////////////////////////////////////////////////////////////////// .macro ZeroReturn xorl %eax, %eax xorl %edx, %edx xorps %xmm0, %xmm0 xorps %xmm1, %xmm1 .endmacro .macro ZeroReturnFPRET fldz .endmacro .macro ZeroReturnSTRET // empty .endmacro STATIC_ENTRY __objc_msgNil ZeroReturn ret END_ENTRY __objc_msgNil STATIC_ENTRY __objc_msgNil_fpret ZeroReturnFPRET ret END_ENTRY __objc_msgNil_fpret STATIC_ENTRY __objc_msgNil_stret ZeroReturnSTRET ret $4 END_ENTRY __objc_msgNil_stret .macro NilTest testl %eax, %eax jz LNilTestSlow_f LNilTestDone: .endmacro .macro NilTestReturnZero .align 3 LNilTestSlow: .if $0 == NORMAL ZeroReturn ret .elseif $0 == FPRET ZeroReturnFPRET ret .elseif $0 == STRET ZeroReturnSTRET ret $$4 .else .abort oops .endif .endmacro .macro NilTestReturnIMP .align 3 LNilTestSlow: call 1f 1: pop %eax .if $0 == NORMAL leal __objc_msgNil-1b(%eax), %eax .elseif $0 == FPRET leal __objc_msgNil_fpret-1b(%eax), %eax .elseif $0 == STRET leal __objc_msgNil_stret-1b(%eax), %eax .else .abort oops .endif ret .endmacro /******************************************************************** * IMP _cache_getImp(Class cls, SEL sel) * * If found, returns method implementation. * If not found, returns NULL. ********************************************************************/ STATIC_ENTRY _cache_getImp // load the class and selector movl selector(%esp), %ecx movl self(%esp), %edx CacheLookup NORMAL, GETIMP // returns IMP on success LCacheMiss: // cache miss, return nil xorl %eax, %eax ret END_ENTRY _cache_getImp /******************************************************************** * * id objc_msgSend(id self, SEL _cmd, ...); * IMP objc_msgLookup(id self, SEL _cmd, ...); * * objc_msgLookup ABI: * IMP returned in eax * Forwarding returned in Z flag * edx reserved for our use but not used * ********************************************************************/ ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame movl selector(%esp), %ecx movl self(%esp), %eax NilTest NORMAL movl isa(%eax), %edx // class = self->isa CacheLookup NORMAL, CALL // calls IMP on success NilTestReturnZero NORMAL LCacheMiss: // isa still in edx jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend ENTRY _objc_msgLookup UNWIND _objc_msgLookup, NoFrame movl selector(%esp), %ecx movl self(%esp), %eax NilTest NORMAL movl isa(%eax), %edx // class = self->isa CacheLookup NORMAL, LOOKUP // returns IMP on success NilTestReturnIMP NORMAL LCacheMiss: // isa still in edx jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookup /******************************************************************** * * id objc_msgSendSuper(struct objc_super *super, SEL _cmd, ...); * ********************************************************************/ ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame movl selector(%esp), %ecx movl super(%esp), %eax // struct objc_super movl class(%eax), %edx // struct objc_super->class movl receiver(%eax), %eax // struct objc_super->receiver movl %eax, super(%esp) // replace super arg with receiver CacheLookup NORMAL, CALL // calls IMP on success LCacheMiss: // class still in edx jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper /******************************************************************** * * id objc_msgSendSuper2(struct objc_super *super, SEL _cmd, ...); * IMP objc_msgLookupSuper2(struct objc_super *super, SEL _cmd, ...); * ********************************************************************/ ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame movl selector(%esp), %ecx movl super(%esp), %eax // struct objc_super movl class(%eax), %edx // struct objc_super->class movl receiver(%eax), %eax // struct objc_super->receiver movl %eax, super(%esp) // replace super arg with receiver movl superclass(%edx), %edx // edx = objc_super->class->super_class CacheLookup NORMAL, CALL // calls IMP on success LCacheMiss: // class still in edx jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 ENTRY _objc_msgLookupSuper2 UNWIND _objc_msgLookupSuper2, NoFrame movl selector(%esp), %ecx movl super(%esp), %eax // struct objc_super movl class(%eax), %edx // struct objc_super->class movl receiver(%eax), %eax // struct objc_super->receiver movl %eax, super(%esp) // replace super arg with receiver movl superclass(%edx), %edx // edx = objc_super->class->super_class CacheLookup NORMAL, LOOKUP // returns IMP on success LCacheMiss: // class still in edx jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookupSuper2 /******************************************************************** * * double objc_msgSend_fpret(id self, SEL _cmd, ...); * IMP objc_msgLookup_fpret(id self, SEL _cmd, ...); * ********************************************************************/ ENTRY _objc_msgSend_fpret UNWIND _objc_msgSend_fpret, NoFrame movl selector(%esp), %ecx movl self(%esp), %eax NilTest FPRET movl isa(%eax), %edx // class = self->isa CacheLookup FPRET, CALL // calls IMP on success NilTestReturnZero FPRET LCacheMiss: // class still in edx jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fpret ENTRY _objc_msgLookup_fpret UNWIND _objc_msgLookup_fpret, NoFrame movl selector(%esp), %ecx movl self(%esp), %eax NilTest FPRET movl isa(%eax), %edx // class = self->isa CacheLookup FPRET, LOOKUP // returns IMP on success NilTestReturnIMP FPRET LCacheMiss: // class still in edx jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookup_fpret /******************************************************************** * * void objc_msgSend_stret(void *st_addr, id self, SEL _cmd, ...); * IMP objc_msgLookup_stret(void *st_addr, id self, SEL _cmd, ...); * ********************************************************************/ ENTRY _objc_msgSend_stret UNWIND _objc_msgSend_stret, NoFrame movl selector_stret(%esp), %ecx movl self_stret(%esp), %eax NilTest STRET movl isa(%eax), %edx // class = self->isa CacheLookup STRET, CALL // calls IMP on success NilTestReturnZero STRET LCacheMiss: // class still in edx jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSend_stret ENTRY _objc_msgLookup_stret UNWIND _objc_msgLookup_stret, NoFrame movl selector_stret(%esp), %ecx movl self_stret(%esp), %eax NilTest STRET movl isa(%eax), %edx // class = self->isa CacheLookup STRET, LOOKUP // returns IMP on success NilTestReturnIMP STRET LCacheMiss: // class still in edx jmp __objc_msgLookup_stret_uncached END_ENTRY _objc_msgLookup_stret /******************************************************************** * * void objc_msgSendSuper_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); * ********************************************************************/ ENTRY _objc_msgSendSuper_stret UNWIND _objc_msgSendSuper_stret, NoFrame movl selector_stret(%esp), %ecx movl super_stret(%esp), %eax // struct objc_super movl class(%eax), %edx // struct objc_super->class movl receiver(%eax), %eax // struct objc_super->receiver movl %eax, super_stret(%esp) // replace super arg with receiver CacheLookup STRET, CALL // calls IMP on success LCacheMiss: // class still in edx jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper_stret /******************************************************************** * * void objc_msgSendSuper2_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); * IMP objc_msgLookupSuper2_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); * ********************************************************************/ ENTRY _objc_msgSendSuper2_stret UNWIND _objc_msgSendSuper2_stret, NoFrame movl selector_stret(%esp), %ecx movl super_stret(%esp), %eax // struct objc_super movl class(%eax), %edx // struct objc_super->class movl receiver(%eax), %eax // struct objc_super->receiver movl %eax, super_stret(%esp) // replace super arg with receiver mov superclass(%edx), %edx // edx = objc_super->class->super_class CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // class still in edx jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper2_stret ENTRY _objc_msgLookupSuper2_stret UNWIND _objc_msgLookupSuper2_stret, NoFrame movl selector_stret(%esp), %ecx movl super_stret(%esp), %eax // struct objc_super movl class(%eax), %edx // struct objc_super->class movl receiver(%eax), %eax // struct objc_super->receiver movl %eax, super_stret(%esp) // replace super arg with receiver mov superclass(%edx), %edx // edx = objc_super->class->super_class CacheLookup STRET, LOOKUP // returns IMP on success // cache miss: go search the method lists LCacheMiss: // class still in edx jmp __objc_msgLookup_stret_uncached END_ENTRY _objc_msgLookupSuper2_stret /******************************************************************** * * _objc_msgSend_uncached * _objc_msgSend_stret_uncached * _objc_msgLookup_uncached * _objc_msgLookup_stret_uncached * * The uncached method lookup. * ********************************************************************/ STATIC_ENTRY __objc_msgSend_uncached UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band edx is the searched class // edx is already the class to search MethodTableLookup NORMAL jmp *%eax // call imp END_ENTRY __objc_msgSend_uncached STATIC_ENTRY __objc_msgSend_stret_uncached UNWIND __objc_msgSend_stret_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band edx is the searched class // edx is already the class to search MethodTableLookup STRET jmp *%eax // call imp END_ENTRY __objc_msgSend_stret_uncached STATIC_ENTRY __objc_msgLookup_uncached UNWIND __objc_msgLookup_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band edx is the searched class // edx is already the class to search MethodTableLookup NORMAL // eax = IMP ret END_ENTRY __objc_msgLookup_uncached STATIC_ENTRY __objc_msgLookup_stret_uncached UNWIND __objc_msgLookup_stret_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band edx is the searched class // edx is already the class to search MethodTableLookup STRET // eax = IMP ret END_ENTRY __objc_msgLookup_stret_uncached /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); * * _objc_msgForward and _objc_msgForward_stret are the externally-callable * functions returned by things like method_getImplementation(). * _objc_msgForward_impcache is the function pointer actually stored in * method caches. * ********************************************************************/ .non_lazy_symbol_pointer L_forward_handler: .indirect_symbol __objc_forward_handler .long 0 L_forward_stret_handler: .indirect_symbol __objc_forward_stret_handler .long 0 STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. jne __objc_msgForward_stret jmp __objc_msgForward END_ENTRY _objc_msgForward_impcache ENTRY __objc_msgForward // Non-struct return version call 1f 1: popl %edx movl L_forward_handler-1b(%edx), %edx jmp *(%edx) END_ENTRY __objc_msgForward ENTRY __objc_msgForward_stret // Struct return version call 1f 1: popl %edx movl L_forward_stret_handler-1b(%edx), %edx jmp *(%edx) END_ENTRY __objc_msgForward_stret ENTRY _objc_msgSend_debug jmp _objc_msgSend END_ENTRY _objc_msgSend_debug ENTRY _objc_msgSendSuper2_debug jmp _objc_msgSendSuper2 END_ENTRY _objc_msgSendSuper2_debug ENTRY _objc_msgSend_stret_debug jmp _objc_msgSend_stret END_ENTRY _objc_msgSend_stret_debug ENTRY _objc_msgSendSuper2_stret_debug jmp _objc_msgSendSuper2_stret END_ENTRY _objc_msgSendSuper2_stret_debug ENTRY _objc_msgSend_fpret_debug jmp _objc_msgSend_fpret END_ENTRY _objc_msgSend_fpret_debug ENTRY _objc_msgSend_noarg jmp _objc_msgSend END_ENTRY _objc_msgSend_noarg ENTRY _method_invoke movl selector(%esp), %ecx movl method_name(%ecx), %edx movl method_imp(%ecx), %eax movl %edx, selector(%esp) jmp *%eax END_ENTRY _method_invoke ENTRY _method_invoke_stret movl selector_stret(%esp), %ecx movl method_name(%ecx), %edx movl method_imp(%ecx), %eax movl %edx, selector_stret(%esp) jmp *%eax END_ENTRY _method_invoke_stret .section __DATA,__objc_msg_break .long 0 .long 0 #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #if __x86_64__ && TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC /******************************************************************** ******************************************************************** ** ** objc-msg-x86_64.s - x86-64 code to support objc messaging. ** ******************************************************************** ********************************************************************/ .data // _objc_entryPoints and _objc_exitPoints are used by objc // to get the critical regions for which method caches // cannot be garbage collected. .align 4 .private_extern _objc_entryPoints _objc_entryPoints: .quad _cache_getImp .quad _objc_msgSend .quad _objc_msgSend_fpret .quad _objc_msgSend_fp2ret .quad _objc_msgSend_stret .quad _objc_msgSendSuper .quad _objc_msgSendSuper_stret .quad _objc_msgSendSuper2 .quad _objc_msgSendSuper2_stret .quad _objc_msgLookup .quad _objc_msgLookup_fpret .quad _objc_msgLookup_fp2ret .quad _objc_msgLookup_stret .quad _objc_msgLookupSuper2 .quad _objc_msgLookupSuper2_stret .quad 0 .private_extern _objc_exitPoints _objc_exitPoints: .quad LExit_cache_getImp .quad LExit_objc_msgSend .quad LExit_objc_msgSend_fpret .quad LExit_objc_msgSend_fp2ret .quad LExit_objc_msgSend_stret .quad LExit_objc_msgSendSuper .quad LExit_objc_msgSendSuper_stret .quad LExit_objc_msgSendSuper2 .quad LExit_objc_msgSendSuper2_stret .quad LExit_objc_msgLookup .quad LExit_objc_msgLookup_fpret .quad LExit_objc_msgLookup_fp2ret .quad LExit_objc_msgLookup_stret .quad LExit_objc_msgLookupSuper2 .quad LExit_objc_msgLookupSuper2_stret .quad 0 /******************************************************************** * Recommended multi-byte NOP instructions * (Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2B) ********************************************************************/ #define nop1 .byte 0x90 #define nop2 .byte 0x66,0x90 #define nop3 .byte 0x0F,0x1F,0x00 #define nop4 .byte 0x0F,0x1F,0x40,0x00 #define nop5 .byte 0x0F,0x1F,0x44,0x00,0x00 #define nop6 .byte 0x66,0x0F,0x1F,0x44,0x00,0x00 #define nop7 .byte 0x0F,0x1F,0x80,0x00,0x00,0x00,0x00 #define nop8 .byte 0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00 #define nop9 .byte 0x66,0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00 /******************************************************************** * Names for parameter registers. ********************************************************************/ #define a1 rdi #define a1d edi #define a1b dil #define a2 rsi #define a2d esi #define a2b sil #define a3 rdx #define a3d edx #define a4 rcx #define a4d ecx #define a5 r8 #define a5d r8d #define a6 r9 #define a6d r9d /******************************************************************** * Names for relative labels * DO NOT USE THESE LABELS ELSEWHERE * Reserved labels: 6: 7: 8: 9: ********************************************************************/ #define LCacheMiss 6 #define LCacheMiss_f 6f #define LCacheMiss_b 6b #define LGetIsaDone 7 #define LGetIsaDone_f 7f #define LGetIsaDone_b 7b #define LNilOrTagged 8 #define LNilOrTagged_f 8f #define LNilOrTagged_b 8b #define LNil 9 #define LNil_f 9f #define LNil_b 9b /******************************************************************** * Macro parameters ********************************************************************/ #define NORMAL 0 #define FPRET 1 #define FP2RET 2 #define STRET 3 #define CALL 100 #define GETIMP 101 #define LOOKUP 102 /******************************************************************** * * Structure definitions. * ********************************************************************/ // objc_super parameter to sendSuper #define receiver 0 #define class 8 // Selected field offsets in class structure // #define isa 0 USE GetIsa INSTEAD // Method descriptor #define method_name 0 #define method_imp 16 // Method cache #define cached_sel 0 #define cached_imp 8 ////////////////////////////////////////////////////////////////////// // // ENTRY functionName // // Assembly directives to begin an exported function. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro ENTRY .text .globl $0 .align 6, 0x90 $0: .endmacro .macro STATIC_ENTRY .text .private_extern $0 .align 2, 0x90 $0: .endmacro ////////////////////////////////////////////////////////////////////// // // END_ENTRY functionName // // Assembly directives to end an exported function. Just a placeholder, // a close-parenthesis for ENTRY, until it is needed for something. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro END_ENTRY LExit$0: .endmacro /******************************************************************** * UNWIND name, flags * Unwind info generation ********************************************************************/ .macro UNWIND .section __LD,__compact_unwind,regular,debug .quad $0 .set LUnwind$0, LExit$0 - $0 .long LUnwind$0 .long $1 .quad 0 /* no personality */ .quad 0 /* no LSDA */ .text .endmacro #define NoFrame 0x02010000 // no frame, no SP adjustment except return address #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller // // Locate the implementation for a class in a selector's method cache. // // Takes: // $0 = NORMAL, FPRET, FP2RET, STRET // $1 = CALL, LOOKUP, GETIMP // a1 or a2 (STRET) = receiver // a2 or a3 (STRET) = selector // r10 = class to search // // On exit: r10 clobbered // (found) calls or returns IMP in r11, eq/ne set for forwarding // (not found) jumps to LCacheMiss, class still in r10 // ///////////////////////////////////////////////////////////////////// .macro CacheHit // CacheHit must always be preceded by a not-taken `jne` instruction // in order to set the correct flags for _objc_msgForward_impcache. // r11 = found bucket .if $1 == GETIMP movq cached_imp(%r11), %rax // return imp ret .else .if $0 != STRET // eq already set for forwarding by `jne` .else test %r11, %r11 // set ne for stret forwarding .endif .if $1 == CALL jmp *cached_imp(%r11) // call imp .elseif $1 == LOOKUP movq cached_imp(%r11), %r11 // return imp ret .else .abort oops .endif .endif .endmacro .macro CacheLookup .if $0 != STRET movq %a2, %r11 // r11 = _cmd .else movq %a3, %r11 // r11 = _cmd .endif andl 24(%r10), %r11d // r11 = _cmd & class->cache.mask shlq $$4, %r11 // r11 = offset = (_cmd & mask)<<4 addq 16(%r10), %r11 // r11 = class->cache.buckets + offset .if $0 != STRET cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 1: // loop cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r11 // bucket++ 2: .if $0 != STRET cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 3: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap movq cached_imp(%r11), %r11 // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. 1: // loop cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r11 // bucket++ 2: .if $0 != STRET cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 3: // double wrap or miss jmp LCacheMiss_f .endmacro ///////////////////////////////////////////////////////////////////// // // MethodTableLookup NORMAL|STRET // // Takes: a1 or a2 (STRET) = receiver // a2 or a3 (STRET) = selector to search for // r10 = class to search // // On exit: imp in %r11, eq/ne set for forwarding // ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup push %rbp mov %rsp, %rbp sub $$0x80+8, %rsp // +8 for alignment movdqa %xmm0, -0x80(%rbp) push %rax // might be xmm parameter count movdqa %xmm1, -0x70(%rbp) push %a1 movdqa %xmm2, -0x60(%rbp) push %a2 movdqa %xmm3, -0x50(%rbp) push %a3 movdqa %xmm4, -0x40(%rbp) push %a4 movdqa %xmm5, -0x30(%rbp) push %a5 movdqa %xmm6, -0x20(%rbp) push %a6 movdqa %xmm7, -0x10(%rbp) // _class_lookupMethodAndLoadCache3(receiver, selector, class) .if $0 == NORMAL // receiver already in a1 // selector already in a2 .else movq %a2, %a1 movq %a3, %a2 .endif movq %r10, %a3 call __class_lookupMethodAndLoadCache3 // IMP is now in %rax movq %rax, %r11 movdqa -0x80(%rbp), %xmm0 pop %a6 movdqa -0x70(%rbp), %xmm1 pop %a5 movdqa -0x60(%rbp), %xmm2 pop %a4 movdqa -0x50(%rbp), %xmm3 pop %a3 movdqa -0x40(%rbp), %xmm4 pop %a2 movdqa -0x30(%rbp), %xmm5 pop %a1 movdqa -0x20(%rbp), %xmm6 pop %rax movdqa -0x10(%rbp), %xmm7 .if $0 == NORMAL cmp %r11, %r11 // set eq for nonstret forwarding .else test %r11, %r11 // set ne for stret forwarding .endif leave .endmacro ///////////////////////////////////////////////////////////////////// // // GetIsaCheckNil return-type // GetIsaSupport return-type // NilTestReturnZero return-type // NilTestReturnIMP return-type // // Sets r10 = obj->isa. // Looks up the real class if receiver is a tagged pointer object. // Returns zero or a zero-returning IMP if obj is nil. // // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // a1 or a2 (STRET) = receiver // // On exit from GetIsaCheckNil: // r10 = receiver->isa // r11 is clobbered // ///////////////////////////////////////////////////////////////////// .macro ZeroReturn xorl %eax, %eax xorl %edx, %edx xorps %xmm0, %xmm0 xorps %xmm1, %xmm1 .endmacro .macro ZeroReturnFPRET fldz ZeroReturn .endmacro .macro ZeroReturnFP2RET fldz fldz ZeroReturn .endmacro .macro ZeroReturnSTRET // rax gets the struct-return address as passed in rdi movq %rdi, %rax .endmacro STATIC_ENTRY __objc_msgNil ZeroReturn ret END_ENTRY __objc_msgNil STATIC_ENTRY __objc_msgNil_fpret ZeroReturnFPRET ret END_ENTRY __objc_msgNil_fpret STATIC_ENTRY __objc_msgNil_fp2ret ZeroReturnFP2RET ret END_ENTRY __objc_msgNil_fp2ret STATIC_ENTRY __objc_msgNil_stret ZeroReturnSTRET ret END_ENTRY __objc_msgNil_stret .macro GetIsaCheckNil .if $0 != STRET testq %a1, %a1 .else testq %a2, %a2 .endif jle LNilOrTagged_f // MSB tagged pointer looks negative .if $0 != STRET movq (%a1), %r10 // r10 = isa .else movq (%a2), %r10 // r10 = isa .endif LGetIsaDone: .endmacro .macro GetIsaSupport .align 3 LNilOrTagged: jz LNil_f // flags set by GetIsaCheckNil .if $0 != STRET movq %a1, %r11 .else movq %a2, %r11 .endif // basic tagged shrq $$60, %r11 leaq _objc_debug_taggedpointer_classes(%rip), %r10 movq (%r10, %r11, 8), %r10 // read isa from table leaq _OBJC_CLASS_$___NSUnrecognizedTaggedPointer(%rip), %r11 cmp %r10, %r11 jne LGetIsaDone_b // ext tagged .if $0 != STRET movq %a1, %r11 .else movq %a2, %r11 .endif shrq $$52, %r11 andl $$0xff, %r11d leaq _objc_debug_taggedpointer_ext_classes(%rip), %r10 movq (%r10, %r11, 8), %r10 // read isa from table jmp LGetIsaDone_b .endmacro .macro NilTestReturnZero LNil: .if $0 == NORMAL ZeroReturn .elseif $0 == FPRET ZeroReturnFPRET .elseif $0 == FP2RET ZeroReturnFP2RET .elseif $0 == STRET ZeroReturnSTRET .else .abort oops .endif ret .endmacro .macro NilTestReturnIMP LNil: .if $0 == NORMAL leaq __objc_msgNil(%rip), %r11 .elseif $0 == FPRET leaq __objc_msgNil_fpret(%rip), %r11 .elseif $0 == FP2RET leaq __objc_msgNil_fp2ret(%rip), %r11 .elseif $0 == STRET leaq __objc_msgNil_stret(%rip), %r11 .else .abort oops .endif ret .endmacro /******************************************************************** * IMP cache_getImp(Class cls, SEL sel) * * On entry: a1 = class whose cache is to be searched * a2 = selector to search for * * If found, returns method implementation. * If not found, returns NULL. ********************************************************************/ STATIC_ENTRY _cache_getImp // do lookup movq %a1, %r10 // move class to r10 for CacheLookup CacheLookup NORMAL, GETIMP // returns IMP on success LCacheMiss: // cache miss, return nil xorl %eax, %eax ret END_ENTRY _cache_getImp /******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); * IMP objc_msgLookup(id self, SEL _cmd, ...); * * objc_msgLookup ABI: * IMP returned in r11 * Forwarding returned in Z flag * r10 reserved for our use but not used * ********************************************************************/ .data .align 3 .globl _objc_debug_taggedpointer_classes _objc_debug_taggedpointer_classes: .fill 16, 8, 0 .globl _objc_debug_taggedpointer_ext_classes _objc_debug_taggedpointer_ext_classes: .fill 256, 8, 0 ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame GetIsaCheckNil NORMAL // r10 = self->isa, or return zero CacheLookup NORMAL, CALL // calls IMP on success GetIsaSupport NORMAL NilTestReturnZero NORMAL // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend ENTRY _objc_msgLookup GetIsaCheckNil NORMAL // r10 = self->isa, or return zero IMP CacheLookup NORMAL, LOOKUP // returns IMP on success GetIsaSupport NORMAL NilTestReturnIMP NORMAL // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookup ENTRY _objc_msgSend_fixup int3 END_ENTRY _objc_msgSend_fixup STATIC_ENTRY _objc_msgSend_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSend END_ENTRY _objc_msgSend_fixedup /******************************************************************** * * id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...); * * struct objc_super { * id receiver; * Class class; * }; ********************************************************************/ ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame // search the cache (objc_super in %a1) movq class(%a1), %r10 // class = objc_super->class movq receiver(%a1), %a1 // load real receiver CacheLookup NORMAL, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // class still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper /******************************************************************** * id objc_msgSendSuper2 ********************************************************************/ ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame // objc_super->class is superclass of class to search // search the cache (objc_super in %a1) movq class(%a1), %r10 // cls = objc_super->class movq receiver(%a1), %a1 // load real receiver movq 8(%r10), %r10 // cls = class->superclass CacheLookup NORMAL, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // superclass still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 ENTRY _objc_msgLookupSuper2 // objc_super->class is superclass of class to search // search the cache (objc_super in %a1) movq class(%a1), %r10 // cls = objc_super->class movq receiver(%a1), %a1 // load real receiver movq 8(%r10), %r10 // cls = class->superclass CacheLookup NORMAL, LOOKUP // returns IMP on success // cache miss: go search the method lists LCacheMiss: // superclass still in r10 jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookupSuper2 ENTRY _objc_msgSendSuper2_fixup int3 END_ENTRY _objc_msgSendSuper2_fixup STATIC_ENTRY _objc_msgSendSuper2_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSendSuper2 END_ENTRY _objc_msgSendSuper2_fixedup /******************************************************************** * * double objc_msgSend_fpret(id self, SEL _cmd,...); * Used for `long double` return only. `float` and `double` use objc_msgSend. * ********************************************************************/ ENTRY _objc_msgSend_fpret UNWIND _objc_msgSend_fpret, NoFrame GetIsaCheckNil FPRET // r10 = self->isa, or return zero CacheLookup FPRET, CALL // calls IMP on success GetIsaSupport FPRET NilTestReturnZero FPRET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fpret ENTRY _objc_msgLookup_fpret GetIsaCheckNil FPRET // r10 = self->isa, or return zero IMP CacheLookup FPRET, LOOKUP // returns IMP on success GetIsaSupport FPRET NilTestReturnIMP FPRET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookup_fpret ENTRY _objc_msgSend_fpret_fixup int3 END_ENTRY _objc_msgSend_fpret_fixup STATIC_ENTRY _objc_msgSend_fpret_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSend_fpret END_ENTRY _objc_msgSend_fpret_fixedup /******************************************************************** * * double objc_msgSend_fp2ret(id self, SEL _cmd,...); * Used for `complex long double` return only. * ********************************************************************/ ENTRY _objc_msgSend_fp2ret UNWIND _objc_msgSend_fp2ret, NoFrame GetIsaCheckNil FP2RET // r10 = self->isa, or return zero CacheLookup FP2RET, CALL // calls IMP on success GetIsaSupport FP2RET NilTestReturnZero FP2RET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fp2ret ENTRY _objc_msgLookup_fp2ret GetIsaCheckNil FP2RET // r10 = self->isa, or return zero IMP CacheLookup FP2RET, LOOKUP // returns IMP on success GetIsaSupport FP2RET NilTestReturnIMP FP2RET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookup_fp2ret ENTRY _objc_msgSend_fp2ret_fixup int3 END_ENTRY _objc_msgSend_fp2ret_fixup STATIC_ENTRY _objc_msgSend_fp2ret_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSend_fp2ret END_ENTRY _objc_msgSend_fp2ret_fixedup /******************************************************************** * * void objc_msgSend_stret(void *st_addr, id self, SEL _cmd, ...); * * objc_msgSend_stret is the struct-return form of msgSend. * The ABI calls for %a1 to be used as the address of the structure * being returned, with the parameters in the succeeding locations. * * On entry: %a1 is the address where the structure is returned, * %a2 is the message receiver, * %a3 is the selector ********************************************************************/ ENTRY _objc_msgSend_stret UNWIND _objc_msgSend_stret, NoFrame GetIsaCheckNil STRET // r10 = self->isa, or return zero CacheLookup STRET, CALL // calls IMP on success GetIsaSupport STRET NilTestReturnZero STRET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSend_stret ENTRY _objc_msgLookup_stret GetIsaCheckNil STRET // r10 = self->isa, or return zero IMP CacheLookup STRET, LOOKUP // returns IMP on success GetIsaSupport STRET NilTestReturnIMP STRET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgLookup_stret_uncached END_ENTRY _objc_msgLookup_stret ENTRY _objc_msgSend_stret_fixup int3 END_ENTRY _objc_msgSend_stret_fixup STATIC_ENTRY _objc_msgSend_stret_fixedup // Load _cmd from the message_ref movq 8(%a3), %a3 jmp _objc_msgSend_stret END_ENTRY _objc_msgSend_stret_fixedup /******************************************************************** * * void objc_msgSendSuper_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); * * struct objc_super { * id receiver; * Class class; * }; * * objc_msgSendSuper_stret is the struct-return form of msgSendSuper. * The ABI calls for (sp+4) to be used as the address of the structure * being returned, with the parameters in the succeeding registers. * * On entry: %a1 is the address where the structure is returned, * %a2 is the address of the objc_super structure, * %a3 is the selector * ********************************************************************/ ENTRY _objc_msgSendSuper_stret UNWIND _objc_msgSendSuper_stret, NoFrame // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // class still in r10 jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper_stret /******************************************************************** * id objc_msgSendSuper2_stret ********************************************************************/ ENTRY _objc_msgSendSuper2_stret UNWIND _objc_msgSendSuper2_stret, NoFrame // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver movq 8(%r10), %r10 // class = class->superclass CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // superclass still in r10 jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper2_stret ENTRY _objc_msgLookupSuper2_stret // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver movq 8(%r10), %r10 // class = class->superclass CacheLookup STRET, LOOKUP // returns IMP on success // cache miss: go search the method lists LCacheMiss: // superclass still in r10 jmp __objc_msgLookup_stret_uncached END_ENTRY _objc_msgLookupSuper2_stret ENTRY _objc_msgSendSuper2_stret_fixup int3 END_ENTRY _objc_msgSendSuper2_stret_fixup STATIC_ENTRY _objc_msgSendSuper2_stret_fixedup // Load _cmd from the message_ref movq 8(%a3), %a3 jmp _objc_msgSendSuper2_stret END_ENTRY _objc_msgSendSuper2_stret_fixedup /******************************************************************** * * _objc_msgSend_uncached * _objc_msgSend_stret_uncached * The uncached method lookup. * ********************************************************************/ STATIC_ENTRY __objc_msgSend_uncached UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r10 is the searched class // r10 is already the class to search MethodTableLookup NORMAL // r11 = IMP jmp *%r11 // goto *imp END_ENTRY __objc_msgSend_uncached STATIC_ENTRY __objc_msgSend_stret_uncached UNWIND __objc_msgSend_stret_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r10 is the searched class // r10 is already the class to search MethodTableLookup STRET // r11 = IMP jmp *%r11 // goto *imp END_ENTRY __objc_msgSend_stret_uncached STATIC_ENTRY __objc_msgLookup_uncached UNWIND __objc_msgLookup_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r10 is the searched class // r10 is already the class to search MethodTableLookup NORMAL // r11 = IMP ret END_ENTRY __objc_msgLookup_uncached STATIC_ENTRY __objc_msgLookup_stret_uncached UNWIND __objc_msgLookup_stret_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r10 is the searched class // r10 is already the class to search MethodTableLookup STRET // r11 = IMP ret END_ENTRY __objc_msgLookup_stret_uncached /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); * * _objc_msgForward and _objc_msgForward_stret are the externally-callable * functions returned by things like method_getImplementation(). * _objc_msgForward_impcache is the function pointer actually stored in * method caches. * ********************************************************************/ STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. jne __objc_msgForward_stret jmp __objc_msgForward END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward // Non-stret version movq __objc_forward_handler(%rip), %r11 jmp *%r11 END_ENTRY __objc_msgForward ENTRY __objc_msgForward_stret // Struct-return version movq __objc_forward_stret_handler(%rip), %r11 jmp *%r11 END_ENTRY __objc_msgForward_stret ENTRY _objc_msgSend_debug jmp _objc_msgSend END_ENTRY _objc_msgSend_debug ENTRY _objc_msgSendSuper2_debug jmp _objc_msgSendSuper2 END_ENTRY _objc_msgSendSuper2_debug ENTRY _objc_msgSend_stret_debug jmp _objc_msgSend_stret END_ENTRY _objc_msgSend_stret_debug ENTRY _objc_msgSendSuper2_stret_debug jmp _objc_msgSendSuper2_stret END_ENTRY _objc_msgSendSuper2_stret_debug ENTRY _objc_msgSend_fpret_debug jmp _objc_msgSend_fpret END_ENTRY _objc_msgSend_fpret_debug ENTRY _objc_msgSend_fp2ret_debug jmp _objc_msgSend_fp2ret END_ENTRY _objc_msgSend_fp2ret_debug ENTRY _objc_msgSend_noarg jmp _objc_msgSend END_ENTRY _objc_msgSend_noarg ENTRY _method_invoke movq method_imp(%a2), %r11 movq method_name(%a2), %a2 jmp *%r11 END_ENTRY _method_invoke ENTRY _method_invoke_stret movq method_imp(%a3), %r11 movq method_name(%a3), %a3 jmp *%r11 END_ENTRY _method_invoke_stret .section __DATA,__objc_msg_break .quad 0 .quad 0 #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Messengers.subproj/objc-msg-win32.m ================================================ #include "objc-private.h" // out-of-band parameter to objc_msgForward #define kFwdMsgSend 1 #define kFwdMsgSendStret 0 // objc_msgSend parameters #define SELF 8[ebp] #define SUPER 8[ebp] #define SELECTOR 12[ebp] #define FIRST_ARG 16[ebp] // objc_msgSend_stret parameters #define STRUCT_ADDR 8[ebp] #define SELF_STRET 12[ebp] #define SUPER_STRET 12[ebp] #define SELECTOR_STRET 16[ebp] // objc_super parameter to sendSuper #define super_receiver 0 #define super_class 4 // struct objc_class fields #define isa 0 #define cache 32 // struct objc_method fields #define method_name 0 #define method_imp 8 // struct objc_cache fields #define mask 0 #define occupied 4 #define buckets 8 void *_objc_forward_handler = NULL; void *_objc_forward_stret_handler = NULL; __declspec(naked) Method _cache_getMethod(Class cls, SEL sel, IMP objc_msgForward_imp) { __asm { push ebp mov ebp, esp mov ecx, SELECTOR mov edx, SELF // CacheLookup WORD_RETURN, CACHE_GET push edi mov edi, cache[edx] push esi mov esi, mask[edi] mov edx, ecx shr edx, 2 SCAN: and edx, esi mov eax, buckets[edi][edx*4] test eax, eax je MISS cmp ecx, method_name[eax] je HIT add edx, 1 jmp SCAN MISS: xor eax, eax pop esi pop edi leave ret HIT: mov ecx, FIRST_ARG cmp ecx, method_imp[eax] je MISS pop esi pop edi leave ret } } __declspec(naked) IMP _cache_getImp(Class cls, SEL sel) { __asm { push ebp mov ebp, esp mov ecx, SELECTOR mov edx, SELF // CacheLookup WORD_RETURN, CACHE_GET push edi mov edi, cache[edx] push esi mov esi, mask[edi] mov edx, ecx shr edx, 2 SCAN: and edx, esi mov eax, buckets[edi][edx*4] test eax, eax je MISS cmp ecx, method_name[eax] je HIT add edx, 1 jmp SCAN MISS: pop esi pop edi xor eax, eax leave ret HIT: pop esi pop edi mov eax, method_imp[eax] leave ret } } OBJC_EXPORT __declspec(naked) id objc_msgSend(id a, SEL b, ...) { __asm { push ebp mov ebp, esp // load receiver and selector mov ecx, SELECTOR mov eax, SELF // check whether receiver is nil test eax, eax je NIL // receiver (in eax) is non-nil: search the cache mov edx, isa[eax] // CacheLookup WORD_RETURN, MSG_SEND push edi mov edi, cache[edx] push esi mov esi, mask[edi] mov edx, ecx shr edx, 2 SCAN: and edx, esi mov eax, buckets[edi][edx*4] test eax, eax je MISS cmp ecx, method_name[eax] je HIT add edx, 1 jmp SCAN HIT: mov eax, method_imp[eax] pop esi pop edi mov edx, kFwdMsgSend leave jmp eax // cache miss: search method lists MISS: pop esi pop edi mov edx, SELF mov eax, isa[edx] // MethodTableLookup WORD_RETURN, MSG_SEND push eax push ecx push edx call _class_lookupMethodAndLoadCache3 mov edx, kFwdMsgSend leave jmp eax // message send to nil: return zero NIL: // eax is already zero mov edx, 0 leave ret } } OBJC_EXPORT __declspec(naked) double objc_msgSend_fpret(id a, SEL b, ...) { __asm { push ebp mov ebp, esp // load receiver and selector mov ecx, SELECTOR mov eax, SELF // check whether receiver is nil test eax, eax je NIL // receiver (in eax) is non-nil: search the cache mov edx, isa[eax] // CacheLookup WORD_RETURN, MSG_SEND push edi mov edi, cache[edx] push esi mov esi, mask[edi] mov edx, ecx shr edx, 2 SCAN: and edx, esi mov eax, buckets[edi][edx*4] test eax, eax je MISS cmp ecx, method_name[eax] je HIT add edx, 1 jmp SCAN HIT: mov eax, method_imp[eax] pop esi pop edi mov edx, kFwdMsgSend leave jmp eax // cache miss: search method lists MISS: pop esi pop edi mov edx, SELF mov eax, isa[edx] // MethodTableLookup WORD_RETURN, MSG_SEND push eax push ecx push edx call _class_lookupMethodAndLoadCache3 mov edx, kFwdMsgSend leave jmp eax // message send to nil: return zero NIL: fldz leave ret } } OBJC_EXPORT __declspec(naked) id objc_msgSendSuper(struct objc_super *a, SEL b, ...) { __asm { push ebp mov ebp, esp // load class and selector mov eax, SUPER mov ecx, SELECTOR mov edx, super_class[eax] // search the cache (class in edx) // CacheLookup WORD_RETURN, MSG_SENDSUPER push edi mov edi, cache[edx] push esi mov esi, mask[edi] mov edx, ecx shr edx, 2 SCAN: and edx, esi mov eax, buckets[edi][edx*4] test eax, eax je MISS cmp ecx, method_name[eax] je HIT add edx, 1 jmp SCAN HIT: mov eax, method_imp[eax] pop esi pop edi mov edx, SUPER mov edx, super_receiver[edx] mov SUPER, edx mov edx, kFwdMsgSend leave jmp eax // cache miss: search method lists MISS: pop esi pop edi mov eax, SUPER mov edx, super_receiver[eax] mov SUPER, edx mov eax, super_class[eax] // MethodTableLookup WORD_RETURN, MSG_SENDSUPER push eax push ecx push edx call _class_lookupMethodAndLoadCache3 mov edx, kFwdMsgSend leave jmp eax } } OBJC_EXPORT __declspec(naked) void objc_msgSend_stret(void) { __asm { push ebp mov ebp, esp // load receiver and selector mov ecx, SELECTOR_STRET mov eax, SELF_STRET // check whether receiver is nil test eax, eax je NIL // receiver (in eax) is non-nil: search the cache mov edx, isa[eax] // CacheLookup WORD_RETURN, MSG_SEND push edi mov edi, cache[edx] push esi mov esi, mask[edi] mov edx, ecx shr edx, 2 SCAN: and edx, esi mov eax, buckets[edi][edx*4] test eax, eax je MISS cmp ecx, method_name[eax] je HIT add edx, 1 jmp SCAN HIT: mov eax, method_imp[eax] pop esi pop edi mov edx, kFwdMsgSendStret leave jmp eax // cache miss: search method lists MISS: pop esi pop edi mov edx, SELF_STRET mov eax, isa[edx] // MethodTableLookup WORD_RETURN, MSG_SEND push eax push ecx push edx call _class_lookupMethodAndLoadCache3 mov edx, kFwdMsgSendStret leave jmp eax // message send to nil: return zero NIL: // eax is already zero mov edx, 0 leave ret } } OBJC_EXPORT __declspec(naked) id objc_msgSendSuper_stret(struct objc_super *a, SEL b, ...) { __asm { push ebp mov ebp, esp // load class and selector mov eax, SUPER_STRET mov ecx, SELECTOR_STRET mov edx, super_class[eax] // search the cache (class in edx) // CacheLookup WORD_RETURN, MSG_SENDSUPER push edi mov edi, cache[edx] push esi mov esi, mask[edi] mov edx, ecx shr edx, 2 SCAN: and edx, esi mov eax, buckets[edi][edx*4] test eax, eax je MISS cmp ecx, method_name[eax] je HIT add edx, 1 jmp SCAN HIT: mov eax, method_imp[eax] pop esi pop edi mov edx, SUPER_STRET mov edx, super_receiver[edx] mov SUPER_STRET, edx mov edx, kFwdMsgSendStret leave jmp eax // cache miss: search method lists MISS: pop esi pop edi mov eax, SUPER_STRET mov edx, super_receiver[eax] mov SUPER_STRET, edx mov eax, super_class[eax] // MethodTableLookup WORD_RETURN, MSG_SENDSUPER push eax push ecx push edx call _class_lookupMethodAndLoadCache3 mov edx, kFwdMsgSendStret leave jmp eax } } OBJC_EXPORT __declspec(naked) id _objc_msgForward(id a, SEL b, ...) { __asm { mov ecx, _objc_forward_handler jmp ecx } } OBJC_EXPORT __declspec(naked) id _objc_msgForward_stret(id a, SEL b, ...) { __asm { mov ecx, _objc_forward_stret_handler jmp ecx } } __declspec(naked) id _objc_msgForward_cached(id a, SEL b, ...) { __asm { cmp edx, kFwdMsgSendStret je STRET jmp _objc_msgForward STRET: jmp _objc_msgForward_stret } } OBJC_EXPORT __declspec(naked) void method_invoke(void) { __asm { push ebp mov ebp, esp mov ecx, SELECTOR mov edx, method_name[ecx] mov eax, method_imp[ecx] mov SELECTOR, edx leave jmp eax } } OBJC_EXPORT __declspec(naked) void method_invoke_stret(void) { __asm { push ebp mov ebp, esp mov ecx, SELECTOR_STRET mov edx, method_name[ecx] mov eax, method_imp[ecx] mov SELECTOR_STRET, edx leave jmp eax } } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Messengers.subproj/objc-msg-x86_64.s ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #if __x86_64__ && !(TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC) #include "isa.h" /******************************************************************** ******************************************************************** ** ** objc-msg-x86_64.s - x86-64 code to support objc messaging. ** ******************************************************************** ********************************************************************/ .data // _objc_entryPoints and _objc_exitPoints are used by objc // to get the critical regions for which method caches // cannot be garbage collected. .align 4 .private_extern _objc_entryPoints _objc_entryPoints: .quad _cache_getImp .quad _objc_msgSend .quad _objc_msgSend_fpret .quad _objc_msgSend_fp2ret .quad _objc_msgSend_stret .quad _objc_msgSendSuper .quad _objc_msgSendSuper_stret .quad _objc_msgSendSuper2 .quad _objc_msgSendSuper2_stret .quad _objc_msgLookup .quad _objc_msgLookup_fpret .quad _objc_msgLookup_fp2ret .quad _objc_msgLookup_stret .quad _objc_msgLookupSuper2 .quad _objc_msgLookupSuper2_stret .quad 0 .private_extern _objc_exitPoints _objc_exitPoints: .quad LExit_cache_getImp .quad LExit_objc_msgSend .quad LExit_objc_msgSend_fpret .quad LExit_objc_msgSend_fp2ret .quad LExit_objc_msgSend_stret .quad LExit_objc_msgSendSuper .quad LExit_objc_msgSendSuper_stret .quad LExit_objc_msgSendSuper2 .quad LExit_objc_msgSendSuper2_stret .quad LExit_objc_msgLookup .quad LExit_objc_msgLookup_fpret .quad LExit_objc_msgLookup_fp2ret .quad LExit_objc_msgLookup_stret .quad LExit_objc_msgLookupSuper2 .quad LExit_objc_msgLookupSuper2_stret .quad 0 /******************************************************************** * Recommended multi-byte NOP instructions * (Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2B) ********************************************************************/ #define nop1 .byte 0x90 #define nop2 .byte 0x66,0x90 #define nop3 .byte 0x0F,0x1F,0x00 #define nop4 .byte 0x0F,0x1F,0x40,0x00 #define nop5 .byte 0x0F,0x1F,0x44,0x00,0x00 #define nop6 .byte 0x66,0x0F,0x1F,0x44,0x00,0x00 #define nop7 .byte 0x0F,0x1F,0x80,0x00,0x00,0x00,0x00 #define nop8 .byte 0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00 #define nop9 .byte 0x66,0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00 /******************************************************************** * Harmless branch prefix hint for instruction alignment ********************************************************************/ #define PN .byte 0x2e /******************************************************************** * Names for parameter registers. ********************************************************************/ #define a1 rdi #define a1d edi #define a1b dil #define a2 rsi #define a2d esi #define a2b sil #define a3 rdx #define a3d edx #define a4 rcx #define a4d ecx #define a5 r8 #define a5d r8d #define a6 r9 #define a6d r9d /******************************************************************** * Names for relative labels * DO NOT USE THESE LABELS ELSEWHERE * Reserved labels: 6: 7: 8: 9: ********************************************************************/ #define LCacheMiss 6 #define LCacheMiss_f 6f #define LCacheMiss_b 6b #define LNilTestSlow 7 #define LNilTestSlow_f 7f #define LNilTestSlow_b 7b #define LGetIsaDone 8 #define LGetIsaDone_f 8f #define LGetIsaDone_b 8b #define LGetIsaSlow 9 #define LGetIsaSlow_f 9f #define LGetIsaSlow_b 9b /******************************************************************** * Macro parameters ********************************************************************/ #define NORMAL 0 #define FPRET 1 #define FP2RET 2 #define STRET 3 #define CALL 100 #define GETIMP 101 #define LOOKUP 102 /******************************************************************** * * Structure definitions. * ********************************************************************/ // objc_super parameter to sendSuper #define receiver 0 #define class 8 // Selected field offsets in class structure // #define isa 0 USE GetIsa INSTEAD // Method descriptor #define method_name 0 #define method_imp 16 // Method cache #define cached_sel 0 #define cached_imp 8 ////////////////////////////////////////////////////////////////////// // // ENTRY functionName // // Assembly directives to begin an exported function. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro ENTRY .text .globl $0 .align 6, 0x90 $0: .endmacro .macro STATIC_ENTRY .text .private_extern $0 .align 2, 0x90 $0: .endmacro ////////////////////////////////////////////////////////////////////// // // END_ENTRY functionName // // Assembly directives to end an exported function. Just a placeholder, // a close-parenthesis for ENTRY, until it is needed for something. // // Takes: functionName - name of the exported function ////////////////////////////////////////////////////////////////////// .macro END_ENTRY LExit$0: .endmacro /******************************************************************** * UNWIND name, flags * Unwind info generation ********************************************************************/ .macro UNWIND .section __LD,__compact_unwind,regular,debug .quad $0 .set LUnwind$0, LExit$0 - $0 .long LUnwind$0 .long $1 .quad 0 /* no personality */ .quad 0 /* no LSDA */ .text .endmacro #define NoFrame 0x02010000 // no frame, no SP adjustment except return address #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller // // Locate the implementation for a class in a selector's method cache. // // Takes: // $0 = NORMAL, FPRET, FP2RET, STRET // $1 = CALL, LOOKUP, GETIMP // a1 or a2 (STRET) = receiver // a2 or a3 (STRET) = selector // r10 = class to search // // On exit: r10 clobbered // (found) calls or returns IMP in r11, eq/ne set for forwarding // (not found) jumps to LCacheMiss, class still in r10 // ///////////////////////////////////////////////////////////////////// .macro CacheHit // CacheHit must always be preceded by a not-taken `jne` instruction // in order to set the correct flags for _objc_msgForward_impcache. // r11 = found bucket .if $1 == GETIMP movq cached_imp(%r11), %rax // return imp ret .else .if $0 != STRET // eq already set for forwarding by `jne` .else test %r11, %r11 // set ne for stret forwarding .endif .if $1 == CALL jmp *cached_imp(%r11) // call imp .elseif $1 == LOOKUP movq cached_imp(%r11), %r11 // return imp ret .else .abort oops .endif .endif .endmacro .macro CacheLookup .if $0 != STRET movq %a2, %r11 // r11 = _cmd .else movq %a3, %r11 // r11 = _cmd .endif andl 24(%r10), %r11d // r11 = _cmd & class->cache.mask shlq $$4, %r11 // r11 = offset = (_cmd & mask)<<4 addq 16(%r10), %r11 // r11 = class->cache.buckets + offset .if $0 != STRET cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 1: // loop cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r11 // bucket++ 2: .if $0 != STRET cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 3: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap movq cached_imp(%r11), %r11 // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. 1: // loop cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r11 // bucket++ 2: .if $0 != STRET cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 3: // double wrap or miss jmp LCacheMiss_f .endmacro ///////////////////////////////////////////////////////////////////// // // MethodTableLookup NORMAL|STRET // // Takes: a1 or a2 (STRET) = receiver // a2 or a3 (STRET) = selector to search for // r10 = class to search // // On exit: imp in %r11, eq/ne set for forwarding // ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup push %rbp mov %rsp, %rbp sub $$0x80+8, %rsp // +8 for alignment movdqa %xmm0, -0x80(%rbp) push %rax // might be xmm parameter count movdqa %xmm1, -0x70(%rbp) push %a1 movdqa %xmm2, -0x60(%rbp) push %a2 movdqa %xmm3, -0x50(%rbp) push %a3 movdqa %xmm4, -0x40(%rbp) push %a4 movdqa %xmm5, -0x30(%rbp) push %a5 movdqa %xmm6, -0x20(%rbp) push %a6 movdqa %xmm7, -0x10(%rbp) // _class_lookupMethodAndLoadCache3(receiver, selector, class) .if $0 == NORMAL // receiver already in a1 // selector already in a2 .else movq %a2, %a1 movq %a3, %a2 .endif movq %r10, %a3 call __class_lookupMethodAndLoadCache3 // IMP is now in %rax movq %rax, %r11 movdqa -0x80(%rbp), %xmm0 pop %a6 movdqa -0x70(%rbp), %xmm1 pop %a5 movdqa -0x60(%rbp), %xmm2 pop %a4 movdqa -0x50(%rbp), %xmm3 pop %a3 movdqa -0x40(%rbp), %xmm4 pop %a2 movdqa -0x30(%rbp), %xmm5 pop %a1 movdqa -0x20(%rbp), %xmm6 pop %rax movdqa -0x10(%rbp), %xmm7 .if $0 == NORMAL cmp %r11, %r11 // set eq for nonstret forwarding .else test %r11, %r11 // set ne for stret forwarding .endif leave .endmacro ///////////////////////////////////////////////////////////////////// // // GetIsaFast return-type // GetIsaSupport return-type // // Sets r10 = obj->isa. Consults the tagged isa table if necessary. // // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // a1 or a2 (STRET) = receiver // // On exit: r10 = receiver->isa // r11 is clobbered // ///////////////////////////////////////////////////////////////////// .macro GetIsaFast .if $0 != STRET testb $$1, %a1b PN jnz LGetIsaSlow_f movq $$ ISA_MASK, %r10 andq (%a1), %r10 .else testb $$1, %a2b PN jnz LGetIsaSlow_f movq $$ ISA_MASK, %r10 andq (%a2), %r10 .endif LGetIsaDone: .endmacro .macro GetIsaSupport LGetIsaSlow: .if $0 != STRET movl %a1d, %r11d .else movl %a2d, %r11d .endif andl $$0xF, %r11d // basic tagged leaq _objc_debug_taggedpointer_classes(%rip), %r10 movq (%r10, %r11, 8), %r10 // read isa from table leaq _OBJC_CLASS_$___NSUnrecognizedTaggedPointer(%rip), %r11 cmp %r10, %r11 jne LGetIsaDone_b // extended tagged .if $0 != STRET movl %a1d, %r11d .else movl %a2d, %r11d .endif shrl $$4, %r11d andl $$0xFF, %r11d leaq _objc_debug_taggedpointer_ext_classes(%rip), %r10 movq (%r10, %r11, 8), %r10 // read isa from table jmp LGetIsaDone_b .endmacro ///////////////////////////////////////////////////////////////////// // // NilTest return-type // // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // %a1 or %a2 (STRET) = receiver // // On exit: Loads non-nil receiver in %a1 or %a2 (STRET) // or returns. // // NilTestReturnZero return-type // // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // %a1 or %a2 (STRET) = receiver // // On exit: Loads non-nil receiver in %a1 or %a2 (STRET) // or returns zero. // // NilTestReturnIMP return-type // // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // %a1 or %a2 (STRET) = receiver // // On exit: Loads non-nil receiver in %a1 or %a2 (STRET) // or returns an IMP in r11 that returns zero. // ///////////////////////////////////////////////////////////////////// .macro ZeroReturn xorl %eax, %eax xorl %edx, %edx xorps %xmm0, %xmm0 xorps %xmm1, %xmm1 .endmacro .macro ZeroReturnFPRET fldz ZeroReturn .endmacro .macro ZeroReturnFP2RET fldz fldz ZeroReturn .endmacro .macro ZeroReturnSTRET // rax gets the struct-return address as passed in rdi movq %rdi, %rax .endmacro STATIC_ENTRY __objc_msgNil ZeroReturn ret END_ENTRY __objc_msgNil STATIC_ENTRY __objc_msgNil_fpret ZeroReturnFPRET ret END_ENTRY __objc_msgNil_fpret STATIC_ENTRY __objc_msgNil_fp2ret ZeroReturnFP2RET ret END_ENTRY __objc_msgNil_fp2ret STATIC_ENTRY __objc_msgNil_stret ZeroReturnSTRET ret END_ENTRY __objc_msgNil_stret .macro NilTest .if $0 != STRET testq %a1, %a1 .else testq %a2, %a2 .endif PN jz LNilTestSlow_f .endmacro .macro NilTestReturnZero .align 3 LNilTestSlow: .if $0 == NORMAL ZeroReturn .elseif $0 == FPRET ZeroReturnFPRET .elseif $0 == FP2RET ZeroReturnFP2RET .elseif $0 == STRET ZeroReturnSTRET .else .abort oops .endif ret .endmacro .macro NilTestReturnIMP .align 3 LNilTestSlow: .if $0 == NORMAL leaq __objc_msgNil(%rip), %r11 .elseif $0 == FPRET leaq __objc_msgNil_fpret(%rip), %r11 .elseif $0 == FP2RET leaq __objc_msgNil_fp2ret(%rip), %r11 .elseif $0 == STRET leaq __objc_msgNil_stret(%rip), %r11 .else .abort oops .endif ret .endmacro /******************************************************************** * IMP cache_getImp(Class cls, SEL sel) * * On entry: a1 = class whose cache is to be searched * a2 = selector to search for * * If found, returns method implementation. * If not found, returns NULL. ********************************************************************/ STATIC_ENTRY _cache_getImp // do lookup movq %a1, %r10 // move class to r10 for CacheLookup CacheLookup NORMAL, GETIMP // returns IMP on success LCacheMiss: // cache miss, return nil xorl %eax, %eax ret END_ENTRY _cache_getImp /******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); * IMP objc_msgLookup(id self, SEL _cmd, ...); * * objc_msgLookup ABI: * IMP returned in r11 * Forwarding returned in Z flag * r10 reserved for our use but not used * ********************************************************************/ .data .align 3 .globl _objc_debug_taggedpointer_classes _objc_debug_taggedpointer_classes: .fill 16, 8, 0 .globl _objc_debug_taggedpointer_ext_classes _objc_debug_taggedpointer_ext_classes: .fill 256, 8, 0 ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame NilTest NORMAL GetIsaFast NORMAL // r10 = self->isa CacheLookup NORMAL, CALL // calls IMP on success NilTestReturnZero NORMAL GetIsaSupport NORMAL // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend ENTRY _objc_msgLookup NilTest NORMAL GetIsaFast NORMAL // r10 = self->isa CacheLookup NORMAL, LOOKUP // returns IMP on success NilTestReturnIMP NORMAL GetIsaSupport NORMAL // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookup ENTRY _objc_msgSend_fixup int3 END_ENTRY _objc_msgSend_fixup STATIC_ENTRY _objc_msgSend_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSend END_ENTRY _objc_msgSend_fixedup /******************************************************************** * * id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...); * * struct objc_super { * id receiver; * Class class; * }; ********************************************************************/ ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame // search the cache (objc_super in %a1) movq class(%a1), %r10 // class = objc_super->class movq receiver(%a1), %a1 // load real receiver CacheLookup NORMAL, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // class still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper /******************************************************************** * id objc_msgSendSuper2 ********************************************************************/ ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame // objc_super->class is superclass of class to search // search the cache (objc_super in %a1) movq class(%a1), %r10 // cls = objc_super->class movq receiver(%a1), %a1 // load real receiver movq 8(%r10), %r10 // cls = class->superclass CacheLookup NORMAL, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // superclass still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 ENTRY _objc_msgLookupSuper2 // objc_super->class is superclass of class to search // search the cache (objc_super in %a1) movq class(%a1), %r10 // cls = objc_super->class movq receiver(%a1), %a1 // load real receiver movq 8(%r10), %r10 // cls = class->superclass CacheLookup NORMAL, LOOKUP // returns IMP on success // cache miss: go search the method lists LCacheMiss: // superclass still in r10 jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookupSuper2 ENTRY _objc_msgSendSuper2_fixup int3 END_ENTRY _objc_msgSendSuper2_fixup STATIC_ENTRY _objc_msgSendSuper2_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSendSuper2 END_ENTRY _objc_msgSendSuper2_fixedup /******************************************************************** * * double objc_msgSend_fpret(id self, SEL _cmd,...); * Used for `long double` return only. `float` and `double` use objc_msgSend. * ********************************************************************/ ENTRY _objc_msgSend_fpret UNWIND _objc_msgSend_fpret, NoFrame NilTest FPRET GetIsaFast FPRET // r10 = self->isa CacheLookup FPRET, CALL // calls IMP on success NilTestReturnZero FPRET GetIsaSupport FPRET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fpret ENTRY _objc_msgLookup_fpret NilTest FPRET GetIsaFast FPRET // r10 = self->isa CacheLookup FPRET, LOOKUP // returns IMP on success NilTestReturnIMP FPRET GetIsaSupport FPRET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookup_fpret ENTRY _objc_msgSend_fpret_fixup int3 END_ENTRY _objc_msgSend_fpret_fixup STATIC_ENTRY _objc_msgSend_fpret_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSend_fpret END_ENTRY _objc_msgSend_fpret_fixedup /******************************************************************** * * double objc_msgSend_fp2ret(id self, SEL _cmd,...); * Used for `complex long double` return only. * ********************************************************************/ ENTRY _objc_msgSend_fp2ret UNWIND _objc_msgSend_fp2ret, NoFrame NilTest FP2RET GetIsaFast FP2RET // r10 = self->isa CacheLookup FP2RET, CALL // calls IMP on success NilTestReturnZero FP2RET GetIsaSupport FP2RET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fp2ret ENTRY _objc_msgLookup_fp2ret NilTest FP2RET GetIsaFast FP2RET // r10 = self->isa CacheLookup FP2RET, LOOKUP // returns IMP on success NilTestReturnIMP FP2RET GetIsaSupport FP2RET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgLookup_uncached END_ENTRY _objc_msgLookup_fp2ret ENTRY _objc_msgSend_fp2ret_fixup int3 END_ENTRY _objc_msgSend_fp2ret_fixup STATIC_ENTRY _objc_msgSend_fp2ret_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSend_fp2ret END_ENTRY _objc_msgSend_fp2ret_fixedup /******************************************************************** * * void objc_msgSend_stret(void *st_addr, id self, SEL _cmd, ...); * * objc_msgSend_stret is the struct-return form of msgSend. * The ABI calls for %a1 to be used as the address of the structure * being returned, with the parameters in the succeeding locations. * * On entry: %a1 is the address where the structure is returned, * %a2 is the message receiver, * %a3 is the selector ********************************************************************/ ENTRY _objc_msgSend_stret UNWIND _objc_msgSend_stret, NoFrame NilTest STRET GetIsaFast STRET // r10 = self->isa CacheLookup STRET, CALL // calls IMP on success NilTestReturnZero STRET GetIsaSupport STRET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSend_stret ENTRY _objc_msgLookup_stret NilTest STRET GetIsaFast STRET // r10 = self->isa CacheLookup STRET, LOOKUP // returns IMP on success NilTestReturnIMP STRET GetIsaSupport STRET // cache miss: go search the method lists LCacheMiss: // isa still in r10 jmp __objc_msgLookup_stret_uncached END_ENTRY _objc_msgLookup_stret ENTRY _objc_msgSend_stret_fixup int3 END_ENTRY _objc_msgSend_stret_fixup STATIC_ENTRY _objc_msgSend_stret_fixedup // Load _cmd from the message_ref movq 8(%a3), %a3 jmp _objc_msgSend_stret END_ENTRY _objc_msgSend_stret_fixedup /******************************************************************** * * void objc_msgSendSuper_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); * * struct objc_super { * id receiver; * Class class; * }; * * objc_msgSendSuper_stret is the struct-return form of msgSendSuper. * The ABI calls for (sp+4) to be used as the address of the structure * being returned, with the parameters in the succeeding registers. * * On entry: %a1 is the address where the structure is returned, * %a2 is the address of the objc_super structure, * %a3 is the selector * ********************************************************************/ ENTRY _objc_msgSendSuper_stret UNWIND _objc_msgSendSuper_stret, NoFrame // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // class still in r10 jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper_stret /******************************************************************** * id objc_msgSendSuper2_stret ********************************************************************/ ENTRY _objc_msgSendSuper2_stret UNWIND _objc_msgSendSuper2_stret, NoFrame // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver movq 8(%r10), %r10 // class = class->superclass CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // superclass still in r10 jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper2_stret ENTRY _objc_msgLookupSuper2_stret // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver movq 8(%r10), %r10 // class = class->superclass CacheLookup STRET, LOOKUP // returns IMP on success // cache miss: go search the method lists LCacheMiss: // superclass still in r10 jmp __objc_msgLookup_stret_uncached END_ENTRY _objc_msgLookupSuper2_stret ENTRY _objc_msgSendSuper2_stret_fixup int3 END_ENTRY _objc_msgSendSuper2_stret_fixup STATIC_ENTRY _objc_msgSendSuper2_stret_fixedup // Load _cmd from the message_ref movq 8(%a3), %a3 jmp _objc_msgSendSuper2_stret END_ENTRY _objc_msgSendSuper2_stret_fixedup /******************************************************************** * * _objc_msgSend_uncached * _objc_msgSend_stret_uncached * _objc_msgLookup_uncached * _objc_msgLookup_stret_uncached * * The uncached method lookup. * ********************************************************************/ STATIC_ENTRY __objc_msgSend_uncached UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r10 is the searched class // r10 is already the class to search MethodTableLookup NORMAL // r11 = IMP jmp *%r11 // goto *imp END_ENTRY __objc_msgSend_uncached STATIC_ENTRY __objc_msgSend_stret_uncached UNWIND __objc_msgSend_stret_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r10 is the searched class // r10 is already the class to search MethodTableLookup STRET // r11 = IMP jmp *%r11 // goto *imp END_ENTRY __objc_msgSend_stret_uncached STATIC_ENTRY __objc_msgLookup_uncached UNWIND __objc_msgLookup_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r10 is the searched class // r10 is already the class to search MethodTableLookup NORMAL // r11 = IMP ret END_ENTRY __objc_msgLookup_uncached STATIC_ENTRY __objc_msgLookup_stret_uncached UNWIND __objc_msgLookup_stret_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r10 is the searched class // r10 is already the class to search MethodTableLookup STRET // r11 = IMP ret END_ENTRY __objc_msgLookup_stret_uncached /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); * * _objc_msgForward and _objc_msgForward_stret are the externally-callable * functions returned by things like method_getImplementation(). * _objc_msgForward_impcache is the function pointer actually stored in * method caches. * ********************************************************************/ STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. jne __objc_msgForward_stret jmp __objc_msgForward END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward // Non-stret version movq __objc_forward_handler(%rip), %r11 jmp *%r11 END_ENTRY __objc_msgForward ENTRY __objc_msgForward_stret // Struct-return version movq __objc_forward_stret_handler(%rip), %r11 jmp *%r11 END_ENTRY __objc_msgForward_stret ENTRY _objc_msgSend_debug jmp _objc_msgSend END_ENTRY _objc_msgSend_debug ENTRY _objc_msgSendSuper2_debug jmp _objc_msgSendSuper2 END_ENTRY _objc_msgSendSuper2_debug ENTRY _objc_msgSend_stret_debug jmp _objc_msgSend_stret END_ENTRY _objc_msgSend_stret_debug ENTRY _objc_msgSendSuper2_stret_debug jmp _objc_msgSendSuper2_stret END_ENTRY _objc_msgSendSuper2_stret_debug ENTRY _objc_msgSend_fpret_debug jmp _objc_msgSend_fpret END_ENTRY _objc_msgSend_fpret_debug ENTRY _objc_msgSend_fp2ret_debug jmp _objc_msgSend_fp2ret END_ENTRY _objc_msgSend_fp2ret_debug ENTRY _objc_msgSend_noarg jmp _objc_msgSend END_ENTRY _objc_msgSend_noarg ENTRY _method_invoke movq method_imp(%a2), %r11 movq method_name(%a2), %a2 jmp *%r11 END_ENTRY _method_invoke ENTRY _method_invoke_stret movq method_imp(%a3), %r11 movq method_name(%a3), %a3 jmp *%r11 END_ENTRY _method_invoke_stret .section __DATA,__objc_msg_break .quad 0 .quad 0 #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Module/ObjectiveC.apinotes ================================================ --- Name: ObjectiveC Classes: - Name: NSArray SwiftBridge: 'Swift.Array' - Name: NSDictionary SwiftBridge: 'Swift.Dictionary' - Name: NSSet SwiftBridge: 'Swift.Set' - Name: NSString SwiftBridge: 'Swift.String' - Name: List Methods: - Selector: init MethodKind: Instance NullabilityOfRet: N - Selector: 'isEqual:' MethodKind: Instance Nullability: - O NullabilityOfRet: S - Name: NSObject SwiftName: NSObject Methods: - Selector: alloc MethodKind: Class NullabilityOfRet: N - Selector: 'allocWithZone:' MethodKind: Class Nullability: - S NullabilityOfRet: N - Selector: class MethodKind: Class Availability: nonswift AvailabilityMsg: use 'self' instead - Selector: 'conformsToProtocol:' MethodKind: Class Nullability: - N NullabilityOfRet: S - Selector: copy MethodKind: Instance NullabilityOfRet: N - Selector: dealloc MethodKind: Instance Availability: nonswift AvailabilityMsg: use 'deinit' to define a de-initializer - Selector: debugDescription MethodKind: Class NullabilityOfRet: N - Selector: description MethodKind: Class NullabilityOfRet: N - Selector: 'forwardingTargetForSelector:' MethodKind: Instance Nullability: - S NullabilityOfRet: O - Selector: 'forwardInvocation:' MethodKind: Instance Availability: nonswift - Selector: init MethodKind: Instance NullabilityOfRet: N DesignatedInit: true - Selector: 'instanceMethodSignatureForSelector:' MethodKind: Class Availability: nonswift - Selector: 'isSubclassOfClass:' MethodKind: Class Nullability: - N NullabilityOfRet: S - Selector: 'methodSignatureForSelector:' MethodKind: Instance Availability: nonswift - Selector: mutableCopy MethodKind: Instance NullabilityOfRet: N - Selector: new MethodKind: Class NullabilityOfRet: N - Selector: superclass MethodKind: Class NullabilityOfRet: O - Name: Object Methods: - Selector: init MethodKind: Instance NullabilityOfRet: N - Selector: 'isEqual:' MethodKind: Instance Nullability: - O NullabilityOfRet: S Protocols: - Name: NSObject SwiftName: NSObjectProtocol Methods: - Selector: class MethodKind: Instance Availability: nonswift AvailabilityMsg: use 'type(of:)' instead - Selector: 'conformsToProtocol:' MethodKind: Instance Nullability: - N NullabilityOfRet: S - Selector: 'isEqual:' MethodKind: Instance Nullability: - O NullabilityOfRet: S - Selector: 'isKindOfClass:' MethodKind: Instance Nullability: - N NullabilityOfRet: S - Selector: 'isMemberOfClass:' MethodKind: Instance Nullability: - N NullabilityOfRet: S - Selector: self MethodKind: Instance NullabilityOfRet: N Properties: - Name: debugDescription Nullability: N - Name: description Nullability: N - Name: superclass Nullability: O Tags: - Name: _NSZone SwiftName: _NSZone # Runtime functions did not yet have nullability in Swift 3. SwiftVersions: - Version: 3 Functions: # objc.h swift3 - Name: object_getClassName NullabilityOfRet: U Nullability: [U] - Name: sel_isMapped Nullability: [U] - Name: sel_getUid NullabilityOfRet: U Nullability: [U] # objc-exception.h swift3 - Name: objc_exception_throw Nullability: [U] - Name: objc_begin_catch NullabilityOfRet: U Nullability: [U] - Name: objc_setExceptionPreprocessor NullabilityOfRet: U Nullability: [U] - Name: objc_setExceptionMatcher NullabilityOfRet: U Nullability: [U] - Name: objc_setUncaughtExceptionHandler NullabilityOfRet: U Nullability: [U] - Name: objc_addExceptionHandler Nullability: [U, U] # objc-sync.h swift3 - Name: objc_sync_enter Nullability: [U] - Name: objc_sync_exit Nullability: [U] # runtime.h swift3 - Name: object_getClass NullabilityOfRet: U Nullability: [U] - Name: object_setClass NullabilityOfRet: U Nullability: [U, U] - Name: object_isClass Nullability: [U] - Name: object_getIvar NullabilityOfRet: U Nullability: [U, U] - Name: object_setIvar Nullability: [U, U, U] - Name: object_setIvarWithStrongDefault Nullability: [U, U, U] - Name: objc_getClass NullabilityOfRet: U Nullability: [U] - Name: objc_getMetaClass NullabilityOfRet: U Nullability: [U] - Name: objc_lookUpClass NullabilityOfRet: U Nullability: [U] - Name: objc_getRequiredClass NullabilityOfRet: U Nullability: [U] - Name: objc_getClassList Parameters: - Position: 0 Type: "Class _Nullable * _Null_unspecified" - Name: objc_copyClassList ResultType: "Class _Nullable * _Null_unspecified" Nullability: [U] - Name: class_getName NullabilityOfRet: U Nullability: [U] - Name: class_isMetaClass Nullability: [U] - Name: class_getSuperclass NullabilityOfRet: U Nullability: [U] - Name: class_getVersion Nullability: [U] - Name: class_setVersion Nullability: [U] - Name: class_getInstanceSize Nullability: [U] - Name: class_getInstanceVariable NullabilityOfRet: U Nullability: [U, U] - Name: class_getClassVariable NullabilityOfRet: U Nullability: [U, U] - Name: class_copyIvarList ResultType: "Ivar _Nullable * _Null_unspecified" Nullability: [U, U] - Name: class_getInstanceMethod NullabilityOfRet: U Nullability: [U, U] - Name: class_getClassMethod NullabilityOfRet: U Nullability: [U, U] - Name: class_getMethodImplementation NullabilityOfRet: U Nullability: [U, U] - Name: class_getMethodImplementation_stret NullabilityOfRet: U Nullability: [U, U] - Name: class_respondsToSelector Nullability: [U, U] - Name: class_copyMethodList Nullability: [U, U] ResultType: "Method _Nullable * _Null_unspecified" - Name: class_conformsToProtocol Nullability: [U, U] - Name: class_copyProtocolList # fixme ResultType: NullabilityOfRet: U Nullability: [U, U] - Name: class_getProperty NullabilityOfRet: U Nullability: [U, U] - Name: class_copyPropertyList ResultType: "objc_property_t _Nullable * _Null_unspecified" Nullability: [U, U] - Name: class_getIvarLayout NullabilityOfRet: U Nullability: [U] - Name: class_getWeakIvarLayout NullabilityOfRet: U Nullability: [U] - Name: class_addMethod Nullability: [U, U, U, U] - Name: class_replaceMethod NullabilityOfRet: U Nullability: [U, U, U, U] - Name: class_addIvar Nullability: [U, U, U, U, U] - Name: class_addProtocol Nullability: [U, U] - Name: class_addProperty Nullability: [U, U, U, U] - Name: class_replaceProperty Nullability: [U, U, U, U] - Name: class_setIvarLayout Nullability: [U, U] - Name: class_setWeakIvarLayout Nullability: [U, U] - Name: class_createInstance NullabilityOfRet: U Nullability: [U, U] - Name: objc_allocateClassPair NullabilityOfRet: U Nullability: [U, U, U] - Name: objc_registerClassPair Nullability: [U] - Name: objc_duplicateClass NullabilityOfRet: U Nullability: [U, U, U] - Name: objc_disposeClassPair Nullability: [U] - Name: method_getName NullabilityOfRet: U Nullability: [U] - Name: method_getImplementation NullabilityOfRet: U Nullability: [U] - Name: method_getTypeEncoding NullabilityOfRet: U Nullability: [U] - Name: method_getNumberOfArguments Nullability: [U] - Name: method_copyReturnType NullabilityOfRet: U Nullability: [U] - Name: method_copyArgumentType NullabilityOfRet: U Nullability: [U, U] - Name: method_getReturnType Nullability: [U, U, U] - Name: method_getArgumentType Nullability: [U, U, U, U] - Name: method_getDescription NullabilityOfRet: U Nullability: [U] - Name: method_setImplementation NullabilityOfRet: U Nullability: [U, U] - Name: method_exchangeImplementations Nullability: [U, U] - Name: ivar_getName NullabilityOfRet: U Nullability: [U] - Name: ivar_getTypeEncoding NullabilityOfRet: U Nullability: [U] - Name: ivar_getOffset Nullability: [U] - Name: property_getName NullabilityOfRet: U Nullability: [U] - Name: property_getAttributes NullabilityOfRet: U Nullability: [U] - Name: property_copyAttributeList NullabilityOfRet: U Nullability: [U, U] - Name: property_copyAttributeValue NullabilityOfRet: U Nullability: [U, U] - Name: objc_getProtocol NullabilityOfRet: U Nullability: [U] - Name: objc_copyProtocolList # fixme ResultType: NullabilityOfRet: U Nullability: [U] - Name: protocol_conformsToProtocol Nullability: [U, U] - Name: protocol_isEqual Nullability: [U, U] - Name: protocol_getName NullabilityOfRet: U Nullability: [U] - Name: protocol_getMethodDescription Nullability: [U, U, U, U] - Name: protocol_copyMethodDescriptionList NullabilityOfRet: U Nullability: [U, U, U, U] - Name: protocol_getProperty NullabilityOfRet: U Nullability: [U, U, U, U] - Name: protocol_copyPropertyList ResultType: "objc_property_t _Nullable * _Null_unspecified" Nullability: [U, U] - Name: protocol_copyPropertyList2 ResultType: "objc_property_t _Nullable * _Null_unspecified" Nullability: [U, U, U, U] - Name: protocol_copyProtocolList # fixme ResultType: NullabilityOfRet: U Nullability: [U, U] - Name: objc_allocateProtocol NullabilityOfRet: U Nullability: [U] - Name: objc_registerProtocol Nullability: [U] - Name: protocol_addMethodDescription Nullability: [U, U, U, U, U] - Name: protocol_addProtocol Nullability: [U, U] - Name: protocol_addProperty Nullability: [U, U, U, U, U, U] - Name: objc_copyImageNames ResultType: "const char * _Nullable * _Null_unspecified" Nullability: [U] - Name: class_getImageName NullabilityOfRet: U Nullability: [U] - Name: objc_copyClassNamesForImage ResultType: "const char * _Nullable * _Null_unspecified" Nullability: [U, U] - Name: sel_getName NullabilityOfRet: U Nullability: [U] - Name: sel_registerName NullabilityOfRet: U Nullability: [U] - Name: sel_isEqual Nullability: [U, U] - Name: objc_enumerationMutation Nullability: [U] - Name: objc_setEnumerationMutationHandler Nullability: [U] - Name: objc_setForwardHandler Nullability: [U, U] - Name: imp_implementationWithBlock NullabilityOfRet: U Nullability: [U] - Name: imp_getBlock NullabilityOfRet: U Nullability: [U] - Name: imp_removeBlock Nullability: [U] - Name: objc_loadWeak NullabilityOfRet: U Nullability: [U] - Name: objc_storeWeak NullabilityOfRet: U Nullability: [U, U] - Name: objc_setAssociatedObject Nullability: [U, U, U, U] - Name: objc_getAssociatedObject NullabilityOfRet: U Nullability: [U, U] - Name: objc_removeAssociatedObjects Nullability: [U] ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Module/module.modulemap ================================================ module ObjectiveC [system] [extern_c] { umbrella "." export * module * { export * } module NSObject { requires objc header "NSObject.h" export * } #if defined(BUILD_FOR_OSX) module List { // Uses @defs, which does not work in ObjC++ or non-ARC. requires objc, !objc_arc, !cplusplus header "List.h" export * } module Object { requires objc header "Object.h" export * } module Protocol { requires objc header "Protocol.h" export * } #endif #if !defined(BUILD_FOR_OSX) // These file are not available outside macOS. exclude header "hashtable.h" exclude header "hashtable2.h" #endif } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/NSObjCRuntime.h ================================================ /* NSObjCRuntime.h Copyright (c) 1994-2012, Apple Inc. All rights reserved. */ #ifndef _OBJC_NSOBJCRUNTIME_H_ #define _OBJC_NSOBJCRUNTIME_H_ #include #include #if __LP64__ || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64 typedef long NSInteger; typedef unsigned long NSUInteger; #else typedef int NSInteger; typedef unsigned int NSUInteger; #endif #define NSIntegerMax LONG_MAX #define NSIntegerMin LONG_MIN #define NSUIntegerMax ULONG_MAX #define NSINTEGER_DEFINED 1 #ifndef NS_DESIGNATED_INITIALIZER #if __has_attribute(objc_designated_initializer) #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) #else #define NS_DESIGNATED_INITIALIZER #endif #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/NSObject.h ================================================ /* NSObject.h Copyright (c) 1994-2012, Apple Inc. All rights reserved. */ #ifndef _OBJC_NSOBJECT_H_ #define _OBJC_NSOBJECT_H_ #if __OBJC__ #include #include @class NSString, NSMethodSignature, NSInvocation; @protocol NSObject - (BOOL)isEqual:(id)object; @property (readonly) NSUInteger hash; @property (readonly) Class superclass; - (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead"); - (instancetype)self; - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2; - (BOOL)isProxy; - (BOOL)isKindOfClass:(Class)aClass; - (BOOL)isMemberOfClass:(Class)aClass; - (BOOL)conformsToProtocol:(Protocol *)aProtocol; - (BOOL)respondsToSelector:(SEL)aSelector; - (instancetype)retain OBJC_ARC_UNAVAILABLE; - (oneway void)release OBJC_ARC_UNAVAILABLE; - (instancetype)autorelease OBJC_ARC_UNAVAILABLE; - (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE; - (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE; @property (readonly, copy) NSString *description; @optional @property (readonly, copy) NSString *debugDescription; @end OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ROOT_CLASS OBJC_EXPORT @interface NSObject { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop } + (void)load; + (void)initialize; - (instancetype)init #if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER NS_DESIGNATED_INITIALIZER #endif ; + (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead"); + (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead"); + (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead"); - (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer"); - (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported"); - (id)copy; - (id)mutableCopy; + (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE; + (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE; + (BOOL)instancesRespondToSelector:(SEL)aSelector; + (BOOL)conformsToProtocol:(Protocol *)protocol; - (IMP)methodForSelector:(SEL)aSelector; + (IMP)instanceMethodForSelector:(SEL)aSelector; - (void)doesNotRecognizeSelector:(SEL)aSelector; - (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); - (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""); - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); - (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE; - (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE; + (BOOL)isSubclassOfClass:(Class)aClass; + (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + (NSUInteger)hash; + (Class)superclass; + (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead"); + (NSString *)description; + (NSString *)debugDescription; @end #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/NSObject.mm ================================================ /* * Copyright (c) 2010-2012 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "objc-private.h" #include "NSObject.h" #include "objc-weak.h" #include "llvm-DenseMap.h" #include "NSObject.h" #include #include #include #include #include #include #include #include #include #include #include #include @interface NSInvocation - (SEL)selector; @end /*********************************************************************** * Weak ivar support **********************************************************************/ static id defaultBadAllocHandler(Class cls) { _objc_fatal("attempt to allocate object of class '%s' failed", cls->nameForLogging()); } static id(*badAllocHandler)(Class) = &defaultBadAllocHandler; static id callBadAllocHandler(Class cls) { // fixme add re-entrancy protection in case allocation fails inside handler return (*badAllocHandler)(cls); } void _objc_setBadAllocHandler(id(*newHandler)(Class)) { badAllocHandler = newHandler; } namespace { // The order of these bits is important. #define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) #define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit #define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit #define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1)) #define SIDE_TABLE_RC_SHIFT 2 #define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1) // RefcountMap disguises its pointers because we // don't want the table to act as a root for `leaks`. typedef objc::DenseMap,size_t,true> RefcountMap; // Template parameters. enum HaveOld { DontHaveOld = false, DoHaveOld = true }; enum HaveNew { DontHaveNew = false, DoHaveNew = true }; struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; SideTable() { memset(&weak_table, 0, sizeof(weak_table)); } ~SideTable() { _objc_fatal("Do not delete SideTable."); } void lock() { slock.lock(); } void unlock() { slock.unlock(); } void forceReset() { slock.forceReset(); } // Address-ordered lock discipline for a pair of side tables. template static void lockTwo(SideTable *lock1, SideTable *lock2); template static void unlockTwo(SideTable *lock1, SideTable *lock2); }; template<> void SideTable::lockTwo (SideTable *lock1, SideTable *lock2) { spinlock_t::lockTwo(&lock1->slock, &lock2->slock); } template<> void SideTable::lockTwo (SideTable *lock1, SideTable *) { lock1->lock(); } template<> void SideTable::lockTwo (SideTable *, SideTable *lock2) { lock2->lock(); } template<> void SideTable::unlockTwo (SideTable *lock1, SideTable *lock2) { spinlock_t::unlockTwo(&lock1->slock, &lock2->slock); } template<> void SideTable::unlockTwo (SideTable *lock1, SideTable *) { lock1->unlock(); } template<> void SideTable::unlockTwo (SideTable *, SideTable *lock2) { lock2->unlock(); } // We cannot use a C++ static initializer to initialize SideTables because // libc calls us before our C++ initializers run. We also don't want a global // pointer to this struct because of the extra indirection. // Do it the hard way. alignas(StripedMap) static uint8_t SideTableBuf[sizeof(StripedMap)]; static void SideTableInit() { new (SideTableBuf) StripedMap(); } static StripedMap& SideTables() { return *reinterpret_cast*>(SideTableBuf); } // anonymous namespace }; void SideTableLockAll() { SideTables().lockAll(); } void SideTableUnlockAll() { SideTables().unlockAll(); } void SideTableForceResetAll() { SideTables().forceResetAll(); } void SideTableDefineLockOrder() { SideTables().defineLockOrder(); } void SideTableLocksPrecedeLock(const void *newlock) { SideTables().precedeLock(newlock); } void SideTableLocksSucceedLock(const void *oldlock) { SideTables().succeedLock(oldlock); } void SideTableLocksPrecedeLocks(StripedMap& newlocks) { int i = 0; const void *newlock; while ((newlock = newlocks.getLock(i++))) { SideTables().precedeLock(newlock); } } void SideTableLocksSucceedLocks(StripedMap& oldlocks) { int i = 0; const void *oldlock; while ((oldlock = oldlocks.getLock(i++))) { SideTables().succeedLock(oldlock); } } // // The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block} // id objc_retainBlock(id x) { return (id)_Block_copy(x); } // // The following SHOULD be called by the compiler directly, but the request hasn't been made yet :-) // BOOL objc_should_deallocate(id object) { return YES; } id objc_retain_autorelease(id obj) { return objc_autorelease(objc_retain(obj)); } void objc_storeStrong(id *location, id obj) { id prev = *location; if (obj == prev) { return; } objc_retain(obj); *location = obj; objc_release(prev); } // Update a weak variable. // If HaveOld is true, the variable has an existing value // that needs to be cleaned up. This value might be nil. // If HaveNew is true, there is a new value that needs to be // assigned into the variable. This value might be nil. // If CrashIfDeallocating is true, the process is halted if newObj is // deallocating or newObj's class does not support weak references. // If CrashIfDeallocating is false, nil is stored instead. enum CrashIfDeallocating { DontCrashIfDeallocating = false, DoCrashIfDeallocating = true }; template static id storeWeak(id *location, objc_object *newObj) { assert(haveOld || haveNew); if (!haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; // Acquire locks for old and new values. // Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us. retry: if (haveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (haveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } SideTable::lockTwo(oldTable, newTable); if (haveOld && *location != oldObj) { SideTable::unlockTwo(oldTable, newTable); goto retry; } // Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. if (haveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); // If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls; goto retry; } } // Clean up old value, if any. if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // Assign new value, if any. if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo(oldTable, newTable); return (id)newObj; } /** * This function stores a new value into a __weak variable. It would * be used anywhere a __weak variable is the target of an assignment. * * @param location The address of the weak pointer itself * @param newObj The new object this weak ptr should now point to * * @return \e newObj */ id objc_storeWeak(id *location, id newObj) { return storeWeak (location, (objc_object *)newObj); } /** * This function stores a new value into a __weak variable. * If the new object is deallocating or the new object's class * does not support weak references, stores nil instead. * * @param location The address of the weak pointer itself * @param newObj The new object this weak ptr should now point to * * @return The value stored (either the new object or nil) */ id objc_storeWeakOrNil(id *location, id newObj) { return storeWeak (location, (objc_object *)newObj); } /** * Initialize a fresh weak pointer to some object location. * It would be used for code like: * * (The nil case) * __weak id weakPtr; * (The non-nil case) * NSObject *o = ...; * __weak id weakPtr = o; * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location Address of __weak ptr. * @param newObj Object ptr. */ id objc_initWeak(id *location, id newObj) { if (!newObj) { *location = nil; return nil; } return storeWeak (location, (objc_object*)newObj); } id objc_initWeakOrNil(id *location, id newObj) { if (!newObj) { *location = nil; return nil; } return storeWeak (location, (objc_object*)newObj); } /** * Destroys the relationship between a weak pointer * and the object it is referencing in the internal weak * table. If the weak pointer is not referencing anything, * there is no need to edit the weak table. * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location The weak pointer address. */ void objc_destroyWeak(id *location) { (void)storeWeak (location, nil); } /* Once upon a time we eagerly cleared *location if we saw the object was deallocating. This confuses code like NSPointerFunctions which tries to pre-flight the raw storage and assumes if the storage is zero then the weak system is done interfering. That is false: the weak system is still going to check and clear the storage later. This can cause objc_weak_error complaints and crashes. So we now don't touch the storage until deallocation completes. */ id objc_loadWeakRetained(id *location) { id obj; id result; Class cls; SideTable *table; retry: // fixme std::atomic this load obj = *location; if (!obj) return nil; if (obj->isTaggedPointer()) return obj; table = &SideTables()[obj]; table->lock(); if (*location != obj) { table->unlock(); goto retry; } result = obj; cls = obj->ISA(); if (! cls->hasCustomRR()) { // Fast case. We know +initialize is complete because // default-RR can never be set before then. assert(cls->isInitialized()); if (! obj->rootTryRetain()) { result = nil; } } else { // Slow case. We must check for +initialize and call it outside // the lock if necessary in order to avoid deadlocks. if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) { BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) class_getMethodImplementation(cls, SEL_retainWeakReference); if ((IMP)tryRetain == _objc_msgForward) { result = nil; } else if (! (*tryRetain)(obj, SEL_retainWeakReference)) { result = nil; } } else { table->unlock(); _class_initialize(cls); goto retry; } } table->unlock(); return result; } /** * This loads the object referenced by a weak pointer and returns it, after * retaining and autoreleasing the object to ensure that it stays alive * long enough for the caller to use it. This function would be used * anywhere a __weak variable is used in an expression. * * @param location The weak pointer address * * @return The object pointed to by \e location, or \c nil if \e location is \c nil. */ id objc_loadWeak(id *location) { if (!*location) return nil; return objc_autorelease(objc_loadWeakRetained(location)); } /** * This function copies a weak pointer from one location to another, * when the destination doesn't already contain a weak pointer. It * would be used for code like: * * __weak id src = ...; * __weak id dst = src; * * This function IS NOT thread-safe with respect to concurrent * modifications to the destination variable. (Concurrent weak clear is safe.) * * @param dst The destination variable. * @param src The source variable. */ void objc_copyWeak(id *dst, id *src) { id obj = objc_loadWeakRetained(src); objc_initWeak(dst, obj); objc_release(obj); } /** * Move a weak pointer from one location to another. * Before the move, the destination must be uninitialized. * After the move, the source is nil. * * This function IS NOT thread-safe with respect to concurrent * modifications to either weak variable. (Concurrent weak clear is safe.) * */ void objc_moveWeak(id *dst, id *src) { objc_copyWeak(dst, src); objc_destroyWeak(src); *src = nil; } /*********************************************************************** Autorelease pool implementation A thread's autorelease pool is a stack of pointers. Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. Thread-local storage points to the hot page, where newly autoreleased objects are stored. **********************************************************************/ // Set this to 1 to mprotect() autorelease pool contents #define PROTECT_AUTORELEASEPOOL 0 // Set this to 1 to validate the entire autorelease pool header all the time // (i.e. use check() instead of fastcheck() everywhere) #define CHECK_AUTORELEASEPOOL (DEBUG) BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj)); BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token)); namespace { struct magic_t { static const uint32_t M0 = 0xA1A1A1A1; # define M1 "AUTORELEASE!" static const size_t M1_len = 12; uint32_t m[4]; magic_t() { assert(M1_len == strlen(M1)); assert(M1_len == 3 * sizeof(m[1])); m[0] = M0; strncpy((char *)&m[1], M1, M1_len); } ~magic_t() { m[0] = m[1] = m[2] = m[3] = 0; } bool check() const { return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len)); } bool fastcheck() const { #if CHECK_AUTORELEASEPOOL return check(); #else return (m[0] == M0); #endif } # undef M1 }; class AutoreleasePoolPage { // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is // pushed and it has never contained any objects. This saves memory // when the top level (i.e. libdispatch) pushes and pops pools but // never uses them. # define EMPTY_POOL_PLACEHOLDER ((id*)1) # define POOL_BOUNDARY nil static pthread_key_t const key = AUTORELEASE_POOL_KEY; static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing static size_t const SIZE = #if PROTECT_AUTORELEASEPOOL PAGE_MAX_SIZE; // must be multiple of vm page size #else PAGE_MAX_SIZE; // size and alignment, power of 2 #endif static size_t const COUNT = SIZE / sizeof(id); magic_t const magic; id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; // SIZE-sizeof(*this) bytes of contents follow static void * operator new(size_t size) { return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE); } static void operator delete(void * p) { return free(p); } inline void protect() { #if PROTECT_AUTORELEASEPOOL mprotect(this, SIZE, PROT_READ); check(); #endif } inline void unprotect() { #if PROTECT_AUTORELEASEPOOL check(); mprotect(this, SIZE, PROT_READ | PROT_WRITE); #endif } AutoreleasePoolPage(AutoreleasePoolPage *newParent) : magic(), next(begin()), thread(pthread_self()), parent(newParent), child(nil), depth(parent ? 1+parent->depth : 0), hiwat(parent ? parent->hiwat : 0) { if (parent) { parent->check(); assert(!parent->child); parent->unprotect(); parent->child = this; parent->protect(); } protect(); } ~AutoreleasePoolPage() { check(); unprotect(); assert(empty()); // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage assert(!child); } void busted(bool die = true) { magic_t right; (die ? _objc_fatal : _objc_inform) ("autorelease pool page %p corrupted\n" " magic 0x%08x 0x%08x 0x%08x 0x%08x\n" " should be 0x%08x 0x%08x 0x%08x 0x%08x\n" " pthread %p\n" " should be %p\n", this, magic.m[0], magic.m[1], magic.m[2], magic.m[3], right.m[0], right.m[1], right.m[2], right.m[3], this->thread, pthread_self()); } void check(bool die = true) { if (!magic.check() || !pthread_equal(thread, pthread_self())) { busted(die); } } void fastcheck(bool die = true) { #if CHECK_AUTORELEASEPOOL check(die); #else if (! magic.fastcheck()) { busted(die); } #endif } id * begin() { return (id *) ((uint8_t *)this+sizeof(*this)); } id * end() { return (id *) ((uint8_t *)this+SIZE); } bool empty() { return next == begin(); } bool full() { return next == end(); } bool lessThanHalfFull() { return (next - begin() < (end() - begin()) / 2); } id *add(id obj) { assert(!full()); unprotect(); id *ret = next; // faster than `return next-1` because of aliasing *next++ = obj; protect(); return ret; } void releaseAll() { releaseUntil(begin()); } void releaseUntil(id *stop) { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it while (page->empty()) { page = page->parent; setHotPage(page); } page->unprotect(); id obj = *--page->next; memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj != POOL_BOUNDARY) { objc_release(obj); } } setHotPage(this); #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { assert(page->empty()); } #endif } void kill() { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage AutoreleasePoolPage *page = this; while (page->child) page = page->child; AutoreleasePoolPage *deathptr; do { deathptr = page; page = page->parent; if (page) { page->unprotect(); page->child = nil; page->protect(); } delete deathptr; } while (deathptr != this); } static void tls_dealloc(void *p) { if (p == (void*)EMPTY_POOL_PLACEHOLDER) { // No objects or pool pages to clean up here. return; } // reinstate TLS value while we work setHotPage((AutoreleasePoolPage *)p); if (AutoreleasePoolPage *page = coldPage()) { if (!page->empty()) pop(page->begin()); // pop all of the pools if (DebugMissingPools || DebugPoolAllocation) { // pop() killed the pages already } else { page->kill(); // free all of the pages } } // clear TLS value so TLS destruction doesn't loop setHotPage(nil); } static AutoreleasePoolPage *pageForPointer(const void *p) { return pageForPointer((uintptr_t)p); } static AutoreleasePoolPage *pageForPointer(uintptr_t p) { AutoreleasePoolPage *result; uintptr_t offset = p % SIZE; assert(offset >= sizeof(AutoreleasePoolPage)); result = (AutoreleasePoolPage *)(p - offset); result->fastcheck(); return result; } static inline bool haveEmptyPoolPlaceholder() { id *tls = (id *)tls_get_direct(key); return (tls == EMPTY_POOL_PLACEHOLDER); } static inline id* setEmptyPoolPlaceholder() { assert(tls_get_direct(key) == nil); tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER); return EMPTY_POOL_PLACEHOLDER; } static inline AutoreleasePoolPage *hotPage() { AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; } static inline void setHotPage(AutoreleasePoolPage *page) { if (page) page->fastcheck(); tls_set_direct(key, (void *)page); } static inline AutoreleasePoolPage *coldPage() { AutoreleasePoolPage *result = hotPage(); if (result) { while (result->parent) { result = result->parent; result->fastcheck(); } } return result; } static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } } static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. assert(page == hotPage()); assert(page->full() || DebugPoolAllocation); do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); setHotPage(page); return page->add(obj); } static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { // "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet assert(!hotPage()); bool pushExtraBoundary = false; if (haveEmptyPoolPlaceholder()) { // We are pushing a second pool over the empty placeholder pool // or pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. pushExtraBoundary = true; } else if (obj != POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", pthread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) { // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. return setEmptyPoolPlaceholder(); } // We are pushing an object or a non-placeholder'd pool. // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); // Push a boundary on behalf of the previously-placeholder'd pool. if (pushExtraBoundary) { page->add(POOL_BOUNDARY); } // Push the requested object or pool. return page->add(obj); } static __attribute__((noinline)) id *autoreleaseNewPage(id obj) { AutoreleasePoolPage *page = hotPage(); if (page) return autoreleaseFullPage(obj, page); else return autoreleaseNoPage(obj); } public: static inline id autorelease(id obj) { assert(obj); assert(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; } static inline void *push() { id *dest; if (DebugPoolAllocation) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; } static void badPop(void *token) { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. if (DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0, 2_0)) { // OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal. _objc_fatal ("Invalid or prematurely-freed autorelease pool %p.", token); } // Old SDK. Bad pop is warned once. static bool complained = false; if (!complained) { complained = true; _objc_inform_now_and_on_crash ("Invalid or prematurely-freed autorelease pool %p. " "Set a breakpoint on objc_autoreleasePoolInvalid to debug. " "Proceeding anyway because the app is old " "(SDK version " SDK_FORMAT "). Memory errors are likely.", token, FORMAT_SDK(sdkVersion())); } objc_autoreleasePoolInvalid(token); } static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Popping the top-level placeholder pool. if (hotPage()) { // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. pop(coldPage()->begin()); } else { // Pool was never used. Clear the placeholder. setHotPage(nil); } return; } page = pageForPointer(token); stop = (id *)token; if (*stop != POOL_BOUNDARY) { if (stop == page->begin() && !page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold page in place // 2. an object is autoreleased with no pool } else { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. return badPop(token); } } if (PrintPoolHiwat) printHiwat(); page->releaseUntil(stop); // memory: delete empty children if (DebugPoolAllocation && page->empty()) { // special case: delete everything during page-per-pool debugging AutoreleasePoolPage *parent = page->parent; page->kill(); setHotPage(parent); } else if (DebugMissingPools && page->empty() && !page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: keep one empty child if page is more than half full if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } } static void init() { int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, AutoreleasePoolPage::tls_dealloc); assert(r == 0); } void print() { _objc_inform("[%p] ................ PAGE %s %s %s", this, full() ? "(full)" : "", this == hotPage() ? "(hot)" : "", this == coldPage() ? "(cold)" : ""); check(false); for (id *p = begin(); p < next; p++) { if (*p == POOL_BOUNDARY) { _objc_inform("[%p] ################ POOL %p", p, p); } else { _objc_inform("[%p] %#16lx %s", p, (unsigned long)*p, object_getClassName(*p)); } } } static void printAll() { _objc_inform("##############"); _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self()); AutoreleasePoolPage *page; ptrdiff_t objects = 0; for (page = coldPage(); page; page = page->child) { objects += page->next - page->begin(); } _objc_inform("%llu releases pending.", (unsigned long long)objects); if (haveEmptyPoolPlaceholder()) { _objc_inform("[%p] ................ PAGE (placeholder)", EMPTY_POOL_PLACEHOLDER); _objc_inform("[%p] ################ POOL (placeholder)", EMPTY_POOL_PLACEHOLDER); } else { for (page = coldPage(); page; page = page->child) { page->print(); } } _objc_inform("##############"); } static void printHiwat() { // Check and propagate high water mark // Ignore high water marks under 256 to suppress noise. AutoreleasePoolPage *p = hotPage(); uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin()); if (mark > p->hiwat && mark > 256) { for( ; p; p = p->parent) { p->unprotect(); p->hiwat = mark; p->protect(); } _objc_inform("POOL HIGHWATER: new high water mark of %u " "pending releases for thread %p:", mark, pthread_self()); void *stack[128]; int count = backtrace(stack, sizeof(stack)/sizeof(stack[0])); char **sym = backtrace_symbols(stack, count); for (int i = 0; i < count; i++) { _objc_inform("POOL HIGHWATER: %s", sym[i]); } free(sym); } } #undef POOL_BOUNDARY }; // anonymous namespace }; /*********************************************************************** * Slow paths for inline control **********************************************************************/ #if SUPPORT_NONPOINTER_ISA NEVER_INLINE id objc_object::rootRetain_overflow(bool tryRetain) { return rootRetain(tryRetain, true); } NEVER_INLINE bool objc_object::rootRelease_underflow(bool performDealloc) { return rootRelease(performDealloc, true); } // Slow path of clearDeallocating() // for objects with nonpointer isa // that were ever weakly referenced // or whose retain count ever overflowed to the side table. NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; table.lock(); if (isa.weakly_referenced) { weak_clear_no_lock(&table.weak_table, (id)this); } if (isa.has_sidetable_rc) { table.refcnts.erase(this); } table.unlock(); } #endif __attribute__((noinline,used)) id objc_object::rootAutorelease2() { assert(!isTaggedPointer()); return AutoreleasePoolPage::autorelease((id)this); } BREAKPOINT_FUNCTION( void objc_overrelease_during_dealloc_error(void) ); NEVER_INLINE bool objc_object::overrelease_error() { _objc_inform_now_and_on_crash("%s object %p overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug", object_getClassName((id)this), this); objc_overrelease_during_dealloc_error(); return false; // allow rootRelease() to tail-call this } /*********************************************************************** * Retain count operations for side table. **********************************************************************/ #if DEBUG // Used to assert that an object is not present in the side table. bool objc_object::sidetable_present() { bool result = false; SideTable& table = SideTables()[this]; table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it != table.refcnts.end()) result = true; if (weak_is_registered_no_lock(&table.weak_table, (id)this)) result = true; table.unlock(); return result; } #endif #if SUPPORT_NONPOINTER_ISA void objc_object::sidetable_lock() { SideTable& table = SideTables()[this]; table.lock(); } void objc_object::sidetable_unlock() { SideTable& table = SideTables()[this]; table.unlock(); } // Move the entire retain count to the side table, // as well as isDeallocating and weaklyReferenced. void objc_object::sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced) { assert(!isa.nonpointer); // should already be changed to raw pointer SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; size_t oldRefcnt = refcntStorage; // not deallocating - that was in the isa assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); uintptr_t carry; size_t refcnt = addc(oldRefcnt, extra_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); if (carry) refcnt = SIDE_TABLE_RC_PINNED; if (isDeallocating) refcnt |= SIDE_TABLE_DEALLOCATING; if (weaklyReferenced) refcnt |= SIDE_TABLE_WEAKLY_REFERENCED; refcntStorage = refcnt; } // Move some retain counts to the side table from the isa field. // Returns true if the object is now pinned. bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { assert(isa.nonpointer); SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; size_t oldRefcnt = refcntStorage; // isa-side bits should not be set here assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true; uintptr_t carry; size_t newRefcnt = addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); if (carry) { refcntStorage = SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK); return true; } else { refcntStorage = newRefcnt; return false; } } // Move some retain counts from the side table to the isa field. // Returns the actual count subtracted, which may be less than the request. size_t objc_object::sidetable_subExtraRC_nolock(size_t delta_rc) { assert(isa.nonpointer); SideTable& table = SideTables()[this]; RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end() || it->second == 0) { // Side table retain count is zero. Can't borrow. return 0; } size_t oldRefcnt = it->second; // isa-side bits should not be set here assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT); assert(oldRefcnt > newRefcnt); // shouldn't underflow it->second = newRefcnt; return delta_rc; } size_t objc_object::sidetable_getExtraRC_nolock() { assert(isa.nonpointer); SideTable& table = SideTables()[this]; RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) return 0; else return it->second >> SIDE_TABLE_RC_SHIFT; } // SUPPORT_NONPOINTER_ISA #endif id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this; } bool objc_object::sidetable_tryRetain() { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; // NO SPINLOCK HERE // _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), // which already acquired the lock on our behalf. // fixme can't do this efficiently with os_lock_handoff_s // if (table.slock == 0) { // _objc_fatal("Do not call -_tryRetain."); // } bool result = true; RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) { table.refcnts[this] = SIDE_TABLE_RC_ONE; } else if (it->second & SIDE_TABLE_DEALLOCATING) { result = false; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second += SIDE_TABLE_RC_ONE; } return result; } uintptr_t objc_object::sidetable_retainCount() { SideTable& table = SideTables()[this]; size_t refcnt_result = 1; table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it != table.refcnts.end()) { // this is valid for SIDE_TABLE_RC_PINNED too refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT; } table.unlock(); return refcnt_result; } bool objc_object::sidetable_isDeallocating() { SideTable& table = SideTables()[this]; // NO SPINLOCK HERE // _objc_rootIsDeallocating() is called exclusively by _objc_storeWeak(), // which already acquired the lock on our behalf. // fixme can't do this efficiently with os_lock_handoff_s // if (table.slock == 0) { // _objc_fatal("Do not call -_isDeallocating."); // } RefcountMap::iterator it = table.refcnts.find(this); return (it != table.refcnts.end()) && (it->second & SIDE_TABLE_DEALLOCATING); } bool objc_object::sidetable_isWeaklyReferenced() { bool result = false; SideTable& table = SideTables()[this]; table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it != table.refcnts.end()) { result = it->second & SIDE_TABLE_WEAKLY_REFERENCED; } table.unlock(); return result; } void objc_object::sidetable_setWeaklyReferenced_nolock() { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; table.refcnts[this] |= SIDE_TABLE_WEAKLY_REFERENCED; } // rdar://20206767 // return uintptr_t instead of bool so that the various raw-isa // -release paths all return zero in eax uintptr_t objc_object::sidetable_release(bool performDealloc) { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; bool do_dealloc = false; table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) { do_dealloc = true; table.refcnts[this] = SIDE_TABLE_DEALLOCATING; } else if (it->second < SIDE_TABLE_DEALLOCATING) { // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it. do_dealloc = true; it->second |= SIDE_TABLE_DEALLOCATING; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second -= SIDE_TABLE_RC_ONE; } table.unlock(); if (do_dealloc && performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return do_dealloc; } void objc_object::sidetable_clearDeallocating() { SideTable& table = SideTables()[this]; // clear any weak table items // clear extra retain count and deallocating bit // (fixme warn or abort if extra retain count == 0 ?) table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it != table.refcnts.end()) { if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) { weak_clear_no_lock(&table.weak_table, (id)this); } table.refcnts.erase(it); } table.unlock(); } /*********************************************************************** * Optimized retain/release/autorelease entrypoints **********************************************************************/ #if __OBJC2__ __attribute__((aligned(16))) id objc_retain(id obj) { if (!obj) return obj; if (obj->isTaggedPointer()) return obj; return obj->retain(); } __attribute__((aligned(16))) void objc_release(id obj) { if (!obj) return; if (obj->isTaggedPointer()) return; return obj->release(); } __attribute__((aligned(16))) id objc_autorelease(id obj) { if (!obj) return obj; if (obj->isTaggedPointer()) return obj; return obj->autorelease(); } // OBJC2 #else // not OBJC2 id objc_retain(id obj) { return [obj retain]; } void objc_release(id obj) { [obj release]; } id objc_autorelease(id obj) { return [obj autorelease]; } #endif /*********************************************************************** * Basic operations for root class implementations a.k.a. _objc_root*() **********************************************************************/ bool _objc_rootTryRetain(id obj) { assert(obj); return obj->rootTryRetain(); } bool _objc_rootIsDeallocating(id obj) { assert(obj); return obj->rootIsDeallocating(); } void objc_clear_deallocating(id obj) { assert(obj); if (obj->isTaggedPointer()) return; obj->clearDeallocating(); } bool _objc_rootReleaseWasZero(id obj) { assert(obj); return obj->rootReleaseShouldDealloc(); } id _objc_rootAutorelease(id obj) { assert(obj); return obj->rootAutorelease(); } uintptr_t _objc_rootRetainCount(id obj) { assert(obj); return obj->rootRetainCount(); } id _objc_rootRetain(id obj) { assert(obj); return obj->rootRetain(); } void _objc_rootRelease(id obj) { assert(obj); obj->rootRelease(); } id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) { id obj; #if __OBJC2__ // allocWithZone under __OBJC2__ ignores the zone parameter (void)zone; obj = class_createInstance(cls, 0); #else if (!zone) { obj = class_createInstance(cls, 0); } else { obj = class_createInstanceFromZone(cls, 0, zone); } #endif if (slowpath(!obj)) obj = callBadAllocHandler(cls); return obj; } // Call [cls alloc] or [cls allocWithZone:nil], with appropriate // shortcutting optimizations. static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { if (slowpath(checkNil && !cls)) return nil; #if __OBJC2__ if (fastpath(!cls->ISA()->hasCustomAWZ())) { // No alloc/allocWithZone implementation. Go straight to the allocator. // fixme store hasCustomAWZ in the non-meta class and // add it to canAllocFast's summary if (fastpath(cls->canAllocFast())) { // No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(!obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (slowpath(!obj)) return callBadAllocHandler(cls); return obj; } } #endif // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; } // Base class implementation of +alloc. cls is not nil. // Calls [cls allocWithZone:nil]. id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } // Calls [cls alloc]. id objc_alloc(Class cls) { return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/); } // Calls [cls allocWithZone:nil]. id objc_allocWithZone(Class cls) { return callAlloc(cls, true/*checkNil*/, true/*allocWithZone*/); } void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); } void _objc_rootFinalize(id obj __unused) { assert(obj); _objc_fatal("_objc_rootFinalize called with garbage collection off"); } id _objc_rootInit(id obj) { // In practice, it will be hard to rely on this function. // Many classes do not properly chain -init calls. return obj; } malloc_zone_t * _objc_rootZone(id obj) { (void)obj; #if __OBJC2__ // allocWithZone under __OBJC2__ ignores the zone parameter return malloc_default_zone(); #else malloc_zone_t *rval = malloc_zone_from_ptr(obj); return rval ? rval : malloc_default_zone(); #endif } uintptr_t _objc_rootHash(id obj) { return (uintptr_t)obj; } void * objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); } void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); } void * _objc_autoreleasePoolPush(void) { return objc_autoreleasePoolPush(); } void _objc_autoreleasePoolPop(void *ctxt) { objc_autoreleasePoolPop(ctxt); } void _objc_autoreleasePoolPrint(void) { AutoreleasePoolPage::printAll(); } // Same as objc_release but suitable for tail-calling // if you need the value back and don't want to push a frame before this point. __attribute__((noinline)) static id objc_releaseAndReturn(id obj) { objc_release(obj); return obj; } // Same as objc_retainAutorelease but suitable for tail-calling // if you don't want to push a frame before this point. __attribute__((noinline)) static id objc_retainAutoreleaseAndReturn(id obj) { return objc_retainAutorelease(obj); } // Prepare a value at +1 for return through a +0 autoreleasing convention. id objc_autoreleaseReturnValue(id obj) { if (prepareOptimizedReturn(ReturnAtPlus1)) return obj; return objc_autorelease(obj); } // Prepare a value at +0 for return through a +0 autoreleasing convention. id objc_retainAutoreleaseReturnValue(id obj) { if (prepareOptimizedReturn(ReturnAtPlus0)) return obj; // not objc_autoreleaseReturnValue(objc_retain(obj)) // because we don't need another optimization attempt return objc_retainAutoreleaseAndReturn(obj); } // Accept a value returned through a +0 autoreleasing convention for use at +1. id objc_retainAutoreleasedReturnValue(id obj) { if (acceptOptimizedReturn() == ReturnAtPlus1) return obj; return objc_retain(obj); } // Accept a value returned through a +0 autoreleasing convention for use at +0. id objc_unsafeClaimAutoreleasedReturnValue(id obj) { if (acceptOptimizedReturn() == ReturnAtPlus0) return obj; return objc_releaseAndReturn(obj); } id objc_retainAutorelease(id obj) { return objc_autorelease(objc_retain(obj)); } void _objc_deallocOnMainThreadHelper(void *context) { id obj = (id)context; [obj dealloc]; } // convert objc_objectptr_t to id, callee must take ownership. id objc_retainedObject(objc_objectptr_t pointer) { return (id)pointer; } // convert objc_objectptr_t to id, without ownership transfer. id objc_unretainedObject(objc_objectptr_t pointer) { return (id)pointer; } // convert id to objc_objectptr_t, no ownership transfer. objc_objectptr_t objc_unretainedPointer(id object) { return object; } void arr_init(void) { AutoreleasePoolPage::init(); SideTableInit(); } #if SUPPORT_TAGGED_POINTERS // Placeholder for old debuggers. When they inspect an // extended tagged pointer object they will see this isa. @interface __NSUnrecognizedTaggedPointer : NSObject @end @implementation __NSUnrecognizedTaggedPointer +(void) load { } -(id) retain { return self; } -(oneway void) release { } -(id) autorelease { return self; } @end #endif @implementation NSObject + (void)load { } + (void)initialize { } + (id)self { return (id)self; } - (id)self { return self; } + (Class)class { return self; } - (Class)class { return object_getClass(self); } + (Class)superclass { return self->superclass; } - (Class)superclass { return [self class]->superclass; } + (BOOL)isMemberOfClass:(Class)cls { return object_getClass((id)self) == cls; } - (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; } + (BOOL)isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } - (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } + (BOOL)isSubclassOfClass:(Class)cls { for (Class tcls = self; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } + (BOOL)isAncestorOfObject:(NSObject *)obj { for (Class tcls = [obj class]; tcls; tcls = tcls->superclass) { if (tcls == self) return YES; } return NO; } + (BOOL)instancesRespondToSelector:(SEL)sel { if (!sel) return NO; return class_respondsToSelector(self, sel); } + (BOOL)respondsToSelector:(SEL)sel { if (!sel) return NO; return class_respondsToSelector_inst(object_getClass(self), sel, self); } - (BOOL)respondsToSelector:(SEL)sel { if (!sel) return NO; return class_respondsToSelector_inst([self class], sel, self); } + (BOOL)conformsToProtocol:(Protocol *)protocol { if (!protocol) return NO; for (Class tcls = self; tcls; tcls = tcls->superclass) { if (class_conformsToProtocol(tcls, protocol)) return YES; } return NO; } - (BOOL)conformsToProtocol:(Protocol *)protocol { if (!protocol) return NO; for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (class_conformsToProtocol(tcls, protocol)) return YES; } return NO; } + (NSUInteger)hash { return _objc_rootHash(self); } - (NSUInteger)hash { return _objc_rootHash(self); } + (BOOL)isEqual:(id)obj { return obj == (id)self; } - (BOOL)isEqual:(id)obj { return obj == self; } + (BOOL)isFault { return NO; } - (BOOL)isFault { return NO; } + (BOOL)isProxy { return NO; } - (BOOL)isProxy { return NO; } + (IMP)instanceMethodForSelector:(SEL)sel { if (!sel) [self doesNotRecognizeSelector:sel]; return class_getMethodImplementation(self, sel); } + (IMP)methodForSelector:(SEL)sel { if (!sel) [self doesNotRecognizeSelector:sel]; return object_getMethodImplementation((id)self, sel); } - (IMP)methodForSelector:(SEL)sel { if (!sel) [self doesNotRecognizeSelector:sel]; return object_getMethodImplementation(self, sel); } + (BOOL)resolveClassMethod:(SEL)sel { return NO; } + (BOOL)resolveInstanceMethod:(SEL)sel { return NO; } // Replaced by CF (throws an NSException) + (void)doesNotRecognizeSelector:(SEL)sel { _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", class_getName(self), sel_getName(sel), self); } // Replaced by CF (throws an NSException) - (void)doesNotRecognizeSelector:(SEL)sel { _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", object_getClassName(self), sel_getName(sel), self); } + (id)performSelector:(SEL)sel { if (!sel) [self doesNotRecognizeSelector:sel]; return ((id(*)(id, SEL))objc_msgSend)((id)self, sel); } + (id)performSelector:(SEL)sel withObject:(id)obj { if (!sel) [self doesNotRecognizeSelector:sel]; return ((id(*)(id, SEL, id))objc_msgSend)((id)self, sel, obj); } + (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 { if (!sel) [self doesNotRecognizeSelector:sel]; return ((id(*)(id, SEL, id, id))objc_msgSend)((id)self, sel, obj1, obj2); } - (id)performSelector:(SEL)sel { if (!sel) [self doesNotRecognizeSelector:sel]; return ((id(*)(id, SEL))objc_msgSend)(self, sel); } - (id)performSelector:(SEL)sel withObject:(id)obj { if (!sel) [self doesNotRecognizeSelector:sel]; return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj); } - (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 { if (!sel) [self doesNotRecognizeSelector:sel]; return ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2); } // Replaced by CF (returns an NSMethodSignature) + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel { _objc_fatal("+[NSObject instanceMethodSignatureForSelector:] " "not available without CoreFoundation"); } // Replaced by CF (returns an NSMethodSignature) + (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { _objc_fatal("+[NSObject methodSignatureForSelector:] " "not available without CoreFoundation"); } // Replaced by CF (returns an NSMethodSignature) - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { _objc_fatal("-[NSObject methodSignatureForSelector:] " "not available without CoreFoundation"); } + (void)forwardInvocation:(NSInvocation *)invocation { [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)]; } - (void)forwardInvocation:(NSInvocation *)invocation { [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)]; } + (id)forwardingTargetForSelector:(SEL)sel { return nil; } - (id)forwardingTargetForSelector:(SEL)sel { return nil; } // Replaced by CF (returns an NSString) + (NSString *)description { return nil; } // Replaced by CF (returns an NSString) - (NSString *)description { return nil; } + (NSString *)debugDescription { return [self description]; } - (NSString *)debugDescription { return [self description]; } + (id)new { return [callAlloc(self, false/*checkNil*/) init]; } + (id)retain { return (id)self; } // Replaced by ObjectAlloc - (id)retain { return ((id)self)->rootRetain(); } + (BOOL)_tryRetain { return YES; } // Replaced by ObjectAlloc - (BOOL)_tryRetain { return ((id)self)->rootTryRetain(); } + (BOOL)_isDeallocating { return NO; } - (BOOL)_isDeallocating { return ((id)self)->rootIsDeallocating(); } + (BOOL)allowsWeakReference { return YES; } + (BOOL)retainWeakReference { return YES; } - (BOOL)allowsWeakReference { return ! [self _isDeallocating]; } - (BOOL)retainWeakReference { return [self _tryRetain]; } + (oneway void)release { } // Replaced by ObjectAlloc - (oneway void)release { ((id)self)->rootRelease(); } + (id)autorelease { return (id)self; } // Replaced by ObjectAlloc - (id)autorelease { return ((id)self)->rootAutorelease(); } + (NSUInteger)retainCount { return ULONG_MAX; } - (NSUInteger)retainCount { return ((id)self)->rootRetainCount(); } + (id)alloc { return _objc_rootAlloc(self); } // Replaced by ObjectAlloc + (id)allocWithZone:(struct _NSZone *)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } // Replaced by CF (throws an NSException) + (id)init { return (id)self; } - (id)init { return _objc_rootInit(self); } // Replaced by CF (throws an NSException) + (void)dealloc { } // Replaced by NSZombies - (void)dealloc { _objc_rootDealloc(self); } // Previously used by GC. Now a placeholder for binary compatibility. - (void) finalize { } + (struct _NSZone *)zone { return (struct _NSZone *)_objc_rootZone(self); } - (struct _NSZone *)zone { return (struct _NSZone *)_objc_rootZone(self); } + (id)copy { return (id)self; } + (id)copyWithZone:(struct _NSZone *)zone { return (id)self; } - (id)copy { return [(id)self copyWithZone:nil]; } + (id)mutableCopy { return (id)self; } + (id)mutableCopyWithZone:(struct _NSZone *)zone { return (id)self; } - (id)mutableCopy { return [(id)self mutableCopyWithZone:nil]; } @end ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Object.h ================================================ /* * Copyright (c) 1999-2003, 2005-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* Object.h Copyright 1988-1996 NeXT Software, Inc. DEFINED AS: A common class HEADER FILES: */ #ifndef _OBJC_OBJECT_H_ #define _OBJC_OBJECT_H_ #include #include #if __OBJC__ && !__OBJC2__ __OSX_AVAILABLE(10.0) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ROOT_CLASS @interface Object { Class isa; /* A pointer to the instance's class structure */ } /* Initializing classes and instances */ + (id)initialize; - (id)init; /* Creating, copying, and freeing instances */ + (id)new; + (id)free; - (id)free; + (id)alloc; - (id)copy; + (id)allocFromZone:(void *)zone; - (id)copyFromZone:(void *)zone; - (void *)zone; /* Identifying classes */ + (id)class; + (id)superclass; + (const char *) name; - (id)class; - (id)superclass; - (const char *) name; /* Identifying and comparing instances */ - (id)self; - (unsigned int) hash; - (BOOL) isEqual:anObject; /* Testing inheritance relationships */ - (BOOL) isKindOf: aClassObject; - (BOOL) isMemberOf: aClassObject; - (BOOL) isKindOfClassNamed: (const char *)aClassName; - (BOOL) isMemberOfClassNamed: (const char *)aClassName; /* Testing class functionality */ + (BOOL) instancesRespondTo:(SEL)aSelector; - (BOOL) respondsTo:(SEL)aSelector; /* Testing protocol conformance */ - (BOOL) conformsTo: (Protocol *)aProtocolObject; + (BOOL) conformsTo: (Protocol *)aProtocolObject; /* Obtaining method descriptors from protocols */ - (struct objc_method_description *) descriptionForMethod:(SEL)aSel; + (struct objc_method_description *) descriptionForInstanceMethod:(SEL)aSel; /* Obtaining method handles */ - (IMP) methodFor:(SEL)aSelector; + (IMP) instanceMethodFor:(SEL)aSelector; /* Sending messages determined at run time */ - (id)perform:(SEL)aSelector; - (id)perform:(SEL)aSelector with:anObject; - (id)perform:(SEL)aSelector with:object1 with:object2; /* Posing */ + (id)poseAs: aClassObject; /* Enforcing intentions */ - (id)subclassResponsibility:(SEL)aSelector; - (id)notImplemented:(SEL)aSelector; /* Error handling */ - (id)doesNotRecognize:(SEL)aSelector; - (id)error:(const char *)aString, ...; /* Debugging */ - (void) printForDebugger:(void *)stream; /* Archiving */ - (id)awake; - (id)write:(void *)stream; - (id)read:(void *)stream; + (int) version; + (id)setVersion: (int) aVersion; /* Forwarding */ - (id)forward: (SEL)sel : (marg_list)args; - (id)performv: (SEL)sel : (marg_list)args; @end /* Abstract Protocol for Archiving */ @interface Object (Archiving) - (id)startArchiving: (void *)stream; - (id)finishUnarchiving; @end /* Abstract Protocol for Dynamic Loading */ @interface Object (DynamicLoading) //+ finishLoading:(headerType *)header; struct mach_header; + (id)finishLoading:(struct mach_header *)header; + (id)startUnloading; @end #endif #endif /* _OBJC_OBJECT_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Object.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* Object.m Copyright 1988-1996 NeXT Software, Inc. */ #include "objc-private.h" #undef id #undef Class typedef struct objc_class *Class; typedef struct objc_object *id; #if __OBJC2__ @implementation Object + (id)initialize { return self; } + (id)class { return self; } -(id) retain { return _objc_rootRetain(self); } -(void) release { _objc_rootRelease(self); } -(id) autorelease { return _objc_rootAutorelease(self); } +(id) retain { return self; } +(void) release { } +(id) autorelease { return self; } @end // __OBJC2__ #else // not __OBJC2__ #include #include #include #include #include "Object.h" #include "Protocol.h" #include "objc-runtime.h" // Error Messages static const char _errShouldHaveImp[] = "should have implemented the '%s' method.", _errShouldNotImp[] = "should NOT have implemented the '%s' method.", _errLeftUndone[] = "method '%s' not implemented", _errBadSel[] = "method %s given invalid selector %s", _errDoesntRecognize[] = "does not recognize selector %c%s"; @implementation Object + (id)initialize { return self; } - (id)awake { return self; } + (id)poseAs: aFactory { return class_poseAs(self, aFactory); } + (id)new { id newObject = (*_alloc)((Class)self, 0); Class metaClass = self->ISA(); if (class_getVersion(metaClass) > 1) return [newObject init]; else return newObject; } + (id)alloc { return (*_zoneAlloc)((Class)self, 0, malloc_default_zone()); } + (id)allocFromZone:(void *) z { return (*_zoneAlloc)((Class)self, 0, z); } - (id)init { return self; } - (const char *)name { return class_getName(isa); } + (const char *)name { return class_getName((Class)self); } - (unsigned)hash { return (unsigned)(((uintptr_t)self) >> 2); } - (BOOL)isEqual:anObject { return anObject == self; } - (id)free { return (*_dealloc)(self); } + (id)free { return nil; } - (id)self { return self; } -(id)class { return (id)isa; } + (id)class { return self; } - (void *)zone { void *z = malloc_zone_from_ptr(self); return z ? z : malloc_default_zone(); } + (id)superclass { return self->superclass; } - (id)superclass { return isa->superclass; } + (int) version { return class_getVersion((Class)self); } + (id)setVersion: (int) aVersion { class_setVersion((Class)self, aVersion); return self; } - (BOOL)isKindOf:aClass { Class cls; for (cls = isa; cls; cls = cls->superclass) if (cls == (Class)aClass) return YES; return NO; } - (BOOL)isMemberOf:aClass { return isa == (Class)aClass; } - (BOOL)isKindOfClassNamed:(const char *)aClassName { Class cls; for (cls = isa; cls; cls = cls->superclass) if (strcmp(aClassName, class_getName(cls)) == 0) return YES; return NO; } - (BOOL)isMemberOfClassNamed:(const char *)aClassName { return strcmp(aClassName, class_getName(isa)) == 0; } + (BOOL)instancesRespondTo:(SEL)aSelector { return class_respondsToMethod((Class)self, aSelector); } - (BOOL)respondsTo:(SEL)aSelector { return class_respondsToMethod(isa, aSelector); } - (id)copy { return [self copyFromZone: [self zone]]; } - (id)copyFromZone:(void *)z { return (*_zoneCopy)(self, 0, z); } - (IMP)methodFor:(SEL)aSelector { return class_lookupMethod(isa, aSelector); } + (IMP)instanceMethodFor:(SEL)aSelector { return class_lookupMethod(self, aSelector); } - (id)perform:(SEL)aSelector { if (aSelector) return ((id(*)(id, SEL))objc_msgSend)(self, aSelector); else return [self error:_errBadSel, sel_getName(_cmd), aSelector]; } - (id)perform:(SEL)aSelector with:anObject { if (aSelector) return ((id(*)(id, SEL, id))objc_msgSend)(self, aSelector, anObject); else return [self error:_errBadSel, sel_getName(_cmd), aSelector]; } - (id)perform:(SEL)aSelector with:obj1 with:obj2 { if (aSelector) return ((id(*)(id, SEL, id, id))objc_msgSend)(self, aSelector, obj1, obj2); else return [self error:_errBadSel, sel_getName(_cmd), aSelector]; } - (id)subclassResponsibility:(SEL)aSelector { return [self error:_errShouldHaveImp, sel_getName(aSelector)]; } - (id)notImplemented:(SEL)aSelector { return [self error:_errLeftUndone, sel_getName(aSelector)]; } - (id)doesNotRecognize:(SEL)aMessage { return [self error:_errDoesntRecognize, class_isMetaClass(isa) ? '+' : '-', sel_getName(aMessage)]; } - (id)error:(const char *)aCStr, ... { va_list ap; va_start(ap,aCStr); (*_error)(self, aCStr, ap); _objc_error (self, aCStr, ap); /* In case (*_error)() returns. */ va_end(ap); return nil; } - (void) printForDebugger:(void *)stream { } - (id)write:(void *) stream { return self; } - (id)read:(void *) stream { return self; } - (id)forward: (SEL) sel : (marg_list) args { return [self doesNotRecognize: sel]; } /* this method is not part of the published API */ - (unsigned)methodArgSize:(SEL)sel { Method method = class_getInstanceMethod((Class)isa, sel); if (! method) return 0; return method_getSizeOfArguments(method); } - (id)performv: (SEL) sel : (marg_list) args { unsigned size; // Messages to nil object always return nil if (! self) return nil; // Calculate size of the marg_list from the method's // signature. This looks for the method in self // and its superclasses. size = [self methodArgSize: sel]; // If neither self nor its superclasses implement // it, forward the message because self might know // someone who does. This is a "chained" forward... if (! size) return [self forward: sel: args]; // Message self with the specified selector and arguments return objc_msgSendv (self, sel, size, args); } /* Testing protocol conformance */ - (BOOL) conformsTo: (Protocol *)aProtocolObj { return [(id)isa conformsTo:aProtocolObj]; } + (BOOL) conformsTo: (Protocol *)aProtocolObj { Class cls; for (cls = self; cls; cls = cls->superclass) { if (class_conformsToProtocol(cls, aProtocolObj)) return YES; } return NO; } /* Looking up information for a method */ - (struct objc_method_description *) descriptionForMethod:(SEL)aSelector { Class cls; struct objc_method_description *m; /* Look in the protocols first. */ for (cls = isa; cls; cls = cls->superclass) { if (cls->ISA()->version >= 3) { struct objc_protocol_list *protocols = (struct objc_protocol_list *)cls->protocols; while (protocols) { int i; for (i = 0; i < protocols->count; i++) { Protocol *p = protocols->list[i]; if (class_isMetaClass(cls)) m = [p descriptionForClassMethod:aSelector]; else m = [p descriptionForInstanceMethod:aSelector]; if (m) { return m; } } if (cls->ISA()->version <= 4) break; protocols = protocols->next; } } } /* Then try the class implementations. */ for (cls = isa; cls; cls = cls->superclass) { void *iterator = 0; int i; struct objc_method_list *mlist; while ( (mlist = class_nextMethodList( cls, &iterator )) ) { for (i = 0; i < mlist->method_count; i++) if (mlist->method_list[i].method_name == aSelector) { m = (struct objc_method_description *)&mlist->method_list[i]; return m; } } } return 0; } + (struct objc_method_description *) descriptionForInstanceMethod:(SEL)aSelector { Class cls; /* Look in the protocols first. */ for (cls = self; cls; cls = cls->superclass) { if (cls->ISA()->version >= 3) { struct objc_protocol_list *protocols = (struct objc_protocol_list *)cls->protocols; while (protocols) { int i; for (i = 0; i < protocols->count; i++) { Protocol *p = protocols->list[i]; struct objc_method_description *m; if ((m = [p descriptionForInstanceMethod:aSelector])) return m; } if (cls->ISA()->version <= 4) break; protocols = protocols->next; } } } /* Then try the class implementations. */ for (cls = self; cls; cls = cls->superclass) { void *iterator = 0; int i; struct objc_method_list *mlist; while ( (mlist = class_nextMethodList( cls, &iterator )) ) { for (i = 0; i < mlist->method_count; i++) if (mlist->method_list[i].method_name == aSelector) { struct objc_method_description *m; m = (struct objc_method_description *)&mlist->method_list[i]; return m; } } } return 0; } /* Obsolete methods (for binary compatibility only). */ + (id)superClass { return [self superclass]; } - (id)superClass { return [self superclass]; } - (BOOL)isKindOfGivenName:(const char *)aClassName { return [self isKindOfClassNamed: aClassName]; } - (BOOL)isMemberOfGivenName:(const char *)aClassName { return [self isMemberOfClassNamed: aClassName]; } - (struct objc_method_description *) methodDescFor:(SEL)aSelector { return [self descriptionForMethod: aSelector]; } + (struct objc_method_description *) instanceMethodDescFor:(SEL)aSelector { return [self descriptionForInstanceMethod: aSelector]; } - (id)findClass:(const char *)aClassName { return objc_lookUpClass(aClassName); } - (id)shouldNotImplement:(SEL)aSelector { return [self error:_errShouldNotImp, sel_getName(aSelector)]; } @end #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/OldClasses.subproj/List.h ================================================ /* * Copyright (c) 1999-2002, 2005-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* List.h Copyright 1988-1996 NeXT Software, Inc. DEFINED AS: A common class HEADER FILES: objc/List.h */ #ifndef _OBJC_LIST_H_ #define _OBJC_LIST_H_ #if __OBJC__ && !__OBJC2__ && !__cplusplus && !__has_feature(objc_arc) #include #include DEPRECATED_ATTRIBUTE @interface List : Object { @public id *dataPtr DEPRECATED_ATTRIBUTE; /* data of the List object */ unsigned numElements DEPRECATED_ATTRIBUTE; /* Actual number of elements */ unsigned maxElements DEPRECATED_ATTRIBUTE; /* Total allocated elements */ } /* Creating, freeing */ - (id)free DEPRECATED_ATTRIBUTE; - (id)freeObjects DEPRECATED_ATTRIBUTE; - (id)copyFromZone:(void *)z DEPRECATED_ATTRIBUTE; /* Initializing */ - (id)init DEPRECATED_ATTRIBUTE; - (id)initCount:(unsigned)numSlots DEPRECATED_ATTRIBUTE; /* Comparing two lists */ - (BOOL)isEqual: anObject DEPRECATED_ATTRIBUTE; /* Managing the storage capacity */ - (unsigned)capacity DEPRECATED_ATTRIBUTE; - (id)setAvailableCapacity:(unsigned)numSlots DEPRECATED_ATTRIBUTE; /* Manipulating objects by index */ - (unsigned)count DEPRECATED_ATTRIBUTE; - (id)objectAt:(unsigned)index DEPRECATED_ATTRIBUTE; - (id)lastObject DEPRECATED_ATTRIBUTE; - (id)addObject:anObject DEPRECATED_ATTRIBUTE; - (id)insertObject:anObject at:(unsigned)index DEPRECATED_ATTRIBUTE; - (id)removeObjectAt:(unsigned)index DEPRECATED_ATTRIBUTE; - (id)removeLastObject DEPRECATED_ATTRIBUTE; - (id)replaceObjectAt:(unsigned)index with:newObject DEPRECATED_ATTRIBUTE; - (id)appendList: (List *)otherList DEPRECATED_ATTRIBUTE; /* Manipulating objects by id */ - (unsigned)indexOf:anObject DEPRECATED_ATTRIBUTE; - (id)addObjectIfAbsent:anObject DEPRECATED_ATTRIBUTE; - (id)removeObject:anObject DEPRECATED_ATTRIBUTE; - (id)replaceObject:anObject with:newObject DEPRECATED_ATTRIBUTE; /* Emptying the list */ - (id)empty DEPRECATED_ATTRIBUTE; /* Sending messages to elements of the list */ - (id)makeObjectsPerform:(SEL)aSelector DEPRECATED_ATTRIBUTE; - (id)makeObjectsPerform:(SEL)aSelector with:anObject DEPRECATED_ATTRIBUTE; /* * The following new... methods are now obsolete. They remain in this * interface file for backward compatibility only. Use Object's alloc method * and the init... methods defined in this class instead. */ + (id)new DEPRECATED_ATTRIBUTE; + (id)newCount:(unsigned)numSlots DEPRECATED_ATTRIBUTE; @end typedef struct { @defs(List); } NXListId DEPRECATED_ATTRIBUTE; #define NX_ADDRESS(x) (((NXListId *)(x))->dataPtr) #define NX_NOT_IN_LIST 0xffffffff #endif #endif /* _OBJC_LIST_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/OldClasses.subproj/List.m ================================================ /* * Copyright (c) 1999-2001, 2005-2006 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* List.m Copyright 1988-1996 NeXT Software, Inc. Written by: Bryan Yamamoto Responsibility: Bertrand Serlet */ #ifndef __OBJC2__ #include #include #include #include #define DATASIZE(count) ((count) * sizeof(id)) @implementation List + (id)initialize { [self setVersion: 1]; return self; } - (id)initCount:(unsigned)numSlots { maxElements = numSlots; if (maxElements) dataPtr = (id *)malloc(DATASIZE(maxElements)); return self; } + (id)newCount:(unsigned)numSlots { return [[self alloc] initCount:numSlots]; } + (id)new { return [self newCount:0]; } - (id)init { return [self initCount:0]; } - (id)free { free(dataPtr); return [super free]; } - (id)freeObjects { id element; while ((element = [self removeLastObject])) [element free]; return self; } - (id)copyFromZone:(void *)z { List *new = [[[self class] alloc] initCount: numElements]; new->numElements = numElements; bcopy ((const char*)dataPtr, (char*)new->dataPtr, DATASIZE(numElements)); return new; } - (BOOL) isEqual: anObject { List *other; if (! [anObject isKindOf: [self class]]) return NO; other = (List *) anObject; return (numElements == other->numElements) && (bcmp ((const char*)dataPtr, (const char*)other->dataPtr, DATASIZE(numElements)) == 0); } - (unsigned)capacity { return maxElements; } - (unsigned)count { return numElements; } - (id)objectAt:(unsigned)index { if (index >= numElements) return nil; return dataPtr[index]; } - (unsigned)indexOf:anObject { register id *this = dataPtr; register id *last = this + numElements; while (this < last) { if (*this == anObject) return this - dataPtr; this++; } return NX_NOT_IN_LIST; } - (id)lastObject { if (! numElements) return nil; return dataPtr[numElements - 1]; } - (id)setAvailableCapacity:(unsigned)numSlots { volatile id *tempDataPtr; if (numSlots < numElements) return nil; tempDataPtr = (id *) realloc (dataPtr, DATASIZE(numSlots)); dataPtr = (id *)tempDataPtr; maxElements = numSlots; return self; } - (id)insertObject:anObject at:(unsigned)index { register id *this, *last, *prev; if (! anObject) return nil; if (index > numElements) return nil; if ((numElements + 1) > maxElements) { volatile id *tempDataPtr; /* we double the capacity, also a good size for malloc */ maxElements += maxElements + 1; tempDataPtr = (id *) realloc (dataPtr, DATASIZE(maxElements)); dataPtr = (id*)tempDataPtr; } this = dataPtr + numElements; prev = this - 1; last = dataPtr + index; while (this > last) *this-- = *prev--; *last = anObject; numElements++; return self; } - (id)addObject:anObject { return [self insertObject:anObject at:numElements]; } - (id)addObjectIfAbsent:anObject { register id *this, *last; if (! anObject) return nil; this = dataPtr; last = dataPtr + numElements; while (this < last) { if (*this == anObject) return self; this++; } return [self insertObject:anObject at:numElements]; } - (id)removeObjectAt:(unsigned)index { register id *this, *last, *next; id retval; if (index >= numElements) return nil; this = dataPtr + index; last = dataPtr + numElements; next = this + 1; retval = *this; while (next < last) *this++ = *next++; numElements--; return retval; } - (id)removeObject:anObject { register id *this, *last; this = dataPtr; last = dataPtr + numElements; while (this < last) { if (*this == anObject) return [self removeObjectAt:this - dataPtr]; this++; } return nil; } - (id)removeLastObject { if (! numElements) return nil; return [self removeObjectAt: numElements - 1]; } - (id)empty { numElements = 0; return self; } - (id)replaceObject:anObject with:newObject { register id *this, *last; if (! newObject) return nil; this = dataPtr; last = dataPtr + numElements; while (this < last) { if (*this == anObject) { *this = newObject; return anObject; } this++; } return nil; } - (id)replaceObjectAt:(unsigned)index with:newObject { register id *this; id retval; if (! newObject) return nil; if (index >= numElements) return nil; this = dataPtr + index; retval = *this; *this = newObject; return retval; } - (id)makeObjectsPerform:(SEL)aSelector { unsigned count = numElements; while (count--) [dataPtr[count] perform: aSelector]; return self; } - (id)makeObjectsPerform:(SEL)aSelector with:anObject { unsigned count = numElements; while (count--) [dataPtr[count] perform: aSelector with: anObject]; return self; } -(id)appendList: (List *)otherList { unsigned i, count; for (i = 0, count = [otherList count]; i < count; i++) [self addObject: [otherList objectAt: i]]; return self; } @end #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Protocol.h ================================================ /* * Copyright (c) 1999-2003, 2006-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* Protocol.h Copyright 1991-1996 NeXT Software, Inc. */ #ifndef _OBJC_PROTOCOL_H_ #define _OBJC_PROTOCOL_H_ #if !__OBJC__ // typedef Protocol is here: #include #elif __OBJC2__ #include // All methods of class Protocol are unavailable. // Use the functions in objc/runtime.h instead. OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) @interface Protocol : NSObject @end #else #include OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) @interface Protocol : Object { @private char *protocol_name OBJC2_UNAVAILABLE; struct objc_protocol_list *protocol_list OBJC2_UNAVAILABLE; struct objc_method_description_list *instance_methods OBJC2_UNAVAILABLE; struct objc_method_description_list *class_methods OBJC2_UNAVAILABLE; } /* Obtaining attributes intrinsic to the protocol */ - (const char *)name OBJC2_UNAVAILABLE; /* Testing protocol conformance */ - (BOOL) conformsTo: (Protocol *)aProtocolObject OBJC2_UNAVAILABLE; /* Looking up information specific to a protocol */ - (struct objc_method_description *) descriptionForInstanceMethod:(SEL)aSel __OSX_DEPRECATED(10.0, 10.5, "use protocol_getMethodDescription instead") __IOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") __TVOS_DEPRECATED(9.0, 9.0, "use protocol_getMethodDescription instead") __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead") __BRIDGEOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead"); - (struct objc_method_description *) descriptionForClassMethod:(SEL)aSel __OSX_DEPRECATED(10.0, 10.5, "use protocol_getMethodDescription instead") __IOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") __TVOS_DEPRECATED(9.0, 9.0, "use protocol_getMethodDescription instead") __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead") __BRIDGEOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead"); @end #endif #endif /* _OBJC_PROTOCOL_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/Protocol.mm ================================================ /* * Copyright (c) 1999-2001, 2005-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* Protocol.h Copyright 1991-1996 NeXT Software, Inc. */ #include "objc-private.h" #undef id #undef Class #include #include #include #include #include "Protocol.h" #include "NSObject.h" // __IncompleteProtocol is used as the return type of objc_allocateProtocol(). // Old ABI uses NSObject as the superclass even though Protocol uses Object // because the R/R implementation for class Protocol is added at runtime // by CF, so __IncompleteProtocol would be left without an R/R implementation // otherwise, which would break ARC. @interface __IncompleteProtocol : NSObject @end @implementation __IncompleteProtocol #if __OBJC2__ // fixme hack - make __IncompleteProtocol a non-lazy class + (void) load { } #endif @end @implementation Protocol #if __OBJC2__ // fixme hack - make Protocol a non-lazy class + (void) load { } #endif - (BOOL) conformsTo: (Protocol *)aProtocolObj { return protocol_conformsToProtocol(self, aProtocolObj); } - (struct objc_method_description *) descriptionForInstanceMethod:(SEL)aSel { #if !__OBJC2__ return lookup_protocol_method((struct old_protocol *)self, aSel, YES/*required*/, YES/*instance*/, YES/*recursive*/); #else return method_getDescription(protocol_getMethod((struct protocol_t *)self, aSel, YES, YES, YES)); #endif } - (struct objc_method_description *) descriptionForClassMethod:(SEL)aSel { #if !__OBJC2__ return lookup_protocol_method((struct old_protocol *)self, aSel, YES/*required*/, NO/*instance*/, YES/*recursive*/); #else return method_getDescription(protocol_getMethod((struct protocol_t *)self, aSel, YES, NO, YES)); #endif } - (const char *)name { return protocol_getName(self); } - (BOOL)isEqual:other { #if __OBJC2__ // check isKindOf: Class cls; Class protoClass = objc_getClass("Protocol"); for (cls = object_getClass(other); cls; cls = cls->superclass) { if (cls == protoClass) break; } if (!cls) return NO; // check equality return protocol_isEqual(self, other); #else return [other isKindOf:[Protocol class]] && [self conformsTo: other] && [other conformsTo: self]; #endif } #if __OBJC2__ - (NSUInteger)hash { return 23; } #else - (unsigned)hash { return 23; } #endif @end ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/arm64-asm.h ================================================ /* * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 2018 Apple Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /******************************************************************** * * arm64-asm.h - asm tools for arm64/arm64_32 and ROP/JOP * ********************************************************************/ #if __arm64__ #if __LP64__ // true arm64 #define SUPPORT_TAGGED_POINTERS 1 #define PTR .quad #define PTRSIZE 8 #define PTRSHIFT 3 // 1< ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/hashtable2.h ================================================ /* * Copyright (c) 1999-2006 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* hashtable2.h Scalable hash table. Copyright 1989-1996 NeXT Software, Inc. */ #ifndef _OBJC_LITTLE_HASHTABLE_H_ #define _OBJC_LITTLE_HASHTABLE_H_ #ifndef _OBJC_PRIVATE_H_ # define OBJC_HASH_AVAILABILITY \ __OSX_DEPRECATED(10.0, 10.1, "NXHashTable is deprecated") \ __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE #else # define OBJC_HASH_AVAILABILITY #endif #include #include #include __BEGIN_DECLS /************************************************************************* * Hash tables of arbitrary data *************************************************************************/ /* This module allows hashing of arbitrary data. Such data must be pointers or integers, and client is responsible for allocating/deallocating this data. A deallocation call-back is provided. The objective C class HashTable is preferred when dealing with (key, values) associations because it is easier to use in that situation. As well-behaved scalable data structures, hash tables double in size when they start becoming full, thus guaranteeing both average constant time access and linear size. */ typedef struct { uintptr_t (* _Nonnull hash)(const void * _Nullable info, const void * _Nullable data); int (* _Nonnull isEqual)(const void * _Nullable info, const void * _Nullable data1, const void * _Nullable data2); void (* _Nonnull free)(const void * _Nullable info, void * _Nullable data); int style; /* reserved for future expansion; currently 0 */ } NXHashTablePrototype; /* the info argument allows a certain generality, such as freeing according to some owner information */ /* invariants assumed by the implementation: 1 - data1 = data2 => hash(data1) = hash(data2) when data varies over time, hash(data) must remain invariant e.g. if data hashes over a string key, the string must not be changed 2- isEqual (data1, data2) => data1= data2 */ typedef struct { const NXHashTablePrototype * _Nonnull prototype OBJC_HASH_AVAILABILITY; unsigned count OBJC_HASH_AVAILABILITY; unsigned nbBuckets OBJC_HASH_AVAILABILITY; void * _Nullable buckets OBJC_HASH_AVAILABILITY; const void * _Nullable info OBJC_HASH_AVAILABILITY; } NXHashTable OBJC_HASH_AVAILABILITY; /* private data structure; may change */ OBJC_EXPORT NXHashTable * _Nonnull NXCreateHashTableFromZone (NXHashTablePrototype prototype, unsigned capacity, const void * _Nullable info, void * _Nullable z) OBJC_HASH_AVAILABILITY; OBJC_EXPORT NXHashTable * _Nonnull NXCreateHashTable (NXHashTablePrototype prototype, unsigned capacity, const void * _Nullable info) OBJC_HASH_AVAILABILITY; /* if hash is 0, pointer hash is assumed */ /* if isEqual is 0, pointer equality is assumed */ /* if free is 0, elements are not freed */ /* capacity is only a hint; 0 creates a small table */ /* info allows call backs to be very general */ OBJC_EXPORT void NXFreeHashTable (NXHashTable * _Nonnull table) OBJC_HASH_AVAILABILITY; /* calls free for each data, and recovers table */ OBJC_EXPORT void NXEmptyHashTable (NXHashTable * _Nonnull table) OBJC_HASH_AVAILABILITY; /* does not deallocate table nor data; keeps current capacity */ OBJC_EXPORT void NXResetHashTable (NXHashTable * _Nonnull table) OBJC_HASH_AVAILABILITY; /* frees each entry; keeps current capacity */ OBJC_EXPORT BOOL NXCompareHashTables (NXHashTable * _Nonnull table1, NXHashTable * _Nonnull table2) OBJC_HASH_AVAILABILITY; /* Returns YES if the two sets are equal (each member of table1 in table2, and table have same size) */ OBJC_EXPORT NXHashTable * _Nonnull NXCopyHashTable (NXHashTable * _Nonnull table) OBJC_HASH_AVAILABILITY; /* makes a fresh table, copying data pointers, not data itself. */ OBJC_EXPORT unsigned NXCountHashTable (NXHashTable * _Nonnull table) OBJC_HASH_AVAILABILITY; /* current number of data in table */ OBJC_EXPORT int NXHashMember (NXHashTable * _Nonnull table, const void * _Nullable data) OBJC_HASH_AVAILABILITY; /* returns non-0 iff data is present in table. Example of use when the hashed data is a struct containing the key, and when the callee only has a key: MyStruct pseudo; pseudo.key = myKey; return NXHashMember (myTable, &pseudo) */ OBJC_EXPORT void * _Nullable NXHashGet (NXHashTable * _Nonnull table, const void * _Nullable data) OBJC_HASH_AVAILABILITY; /* return original table data or NULL. Example of use when the hashed data is a struct containing the key, and when the callee only has a key: MyStruct pseudo; MyStruct *original; pseudo.key = myKey; original = NXHashGet (myTable, &pseudo) */ OBJC_EXPORT void * _Nullable NXHashInsert (NXHashTable * _Nonnull table, const void * _Nullable data) OBJC_HASH_AVAILABILITY; /* previous data or NULL is returned. */ OBJC_EXPORT void * _Nullable NXHashInsertIfAbsent (NXHashTable * _Nonnull table, const void * _Nullable data) OBJC_HASH_AVAILABILITY; /* If data already in table, returns the one in table else adds argument to table and returns argument. */ OBJC_EXPORT void * _Nullable NXHashRemove (NXHashTable * _Nonnull table, const void * _Nullable data) OBJC_HASH_AVAILABILITY; /* previous data or NULL is returned */ /* Iteration over all elements of a table consists in setting up an iteration state and then to progress until all entries have been visited. An example of use for counting elements in a table is: unsigned count = 0; MyData *data; NXHashState state = NXInitHashState(table); while (NXNextHashState(table, &state, &data)) { count++; } */ typedef struct {int i; int j;} NXHashState OBJC_HASH_AVAILABILITY; /* callers should not rely on actual contents of the struct */ OBJC_EXPORT NXHashState NXInitHashState(NXHashTable * _Nonnull table) OBJC_HASH_AVAILABILITY; OBJC_EXPORT int NXNextHashState(NXHashTable * _Nonnull table, NXHashState * _Nonnull state, void * _Nullable * _Nonnull data) OBJC_HASH_AVAILABILITY; /* returns 0 when all elements have been visited */ /************************************************************************* * Conveniences for writing hash, isEqual and free functions * and common prototypes *************************************************************************/ OBJC_EXPORT uintptr_t NXPtrHash(const void * _Nullable info, const void * _Nullable data) OBJC_HASH_AVAILABILITY; /* scrambles the address bits; info unused */ OBJC_EXPORT uintptr_t NXStrHash(const void * _Nullable info, const void * _Nullable data) OBJC_HASH_AVAILABILITY; /* string hashing; info unused */ OBJC_EXPORT int NXPtrIsEqual(const void * _Nullable info, const void * _Nullable data1, const void * _Nullable data2) OBJC_HASH_AVAILABILITY; /* pointer comparison; info unused */ OBJC_EXPORT int NXStrIsEqual(const void * _Nullable info, const void * _Nullable data1, const void * _Nullable data2) OBJC_HASH_AVAILABILITY; /* string comparison; NULL ok; info unused */ OBJC_EXPORT void NXNoEffectFree(const void * _Nullable info, void * _Nullable data) OBJC_HASH_AVAILABILITY; /* no effect; info unused */ OBJC_EXPORT void NXReallyFree(const void * _Nullable info, void * _Nullable data) OBJC_HASH_AVAILABILITY; /* frees it; info unused */ /* The two following prototypes are useful for manipulating set of pointers or set of strings; For them free is defined as NXNoEffectFree */ OBJC_EXPORT const NXHashTablePrototype NXPtrPrototype OBJC_HASH_AVAILABILITY; /* prototype when data is a pointer (void *) */ OBJC_EXPORT const NXHashTablePrototype NXStrPrototype OBJC_HASH_AVAILABILITY; /* prototype when data is a string (char *) */ /* following prototypes help describe mappings where the key is the first element of a struct and is either a pointer or a string. For example NXStrStructKeyPrototype can be used to hash pointers to Example, where Example is: typedef struct { char *key; int data1; ... } Example For the following prototypes, free is defined as NXReallyFree. */ OBJC_EXPORT const NXHashTablePrototype NXPtrStructKeyPrototype OBJC_HASH_AVAILABILITY; OBJC_EXPORT const NXHashTablePrototype NXStrStructKeyPrototype OBJC_HASH_AVAILABILITY; #if !__OBJC2__ && !TARGET_OS_WIN32 /************************************************************************* * Unique strings and buffers *************************************************************************/ /* Unique strings allows C users to enjoy the benefits of Lisp's atoms: A unique string is a string that is allocated once for all (never de-allocated) and that has only one representant (thus allowing comparison with == instead of strcmp). A unique string should never be modified (and in fact some memory protection is done to ensure that). In order to more explicitly insist on the fact that the string has been uniqued, a synonym of (const char *) has been added, NXAtom. */ typedef const char *NXAtom OBJC_HASH_AVAILABILITY; OBJC_EXPORT NXAtom _Nullable NXUniqueString(const char * _Nullable buffer) OBJC_HASH_AVAILABILITY; /* assumes that buffer is \0 terminated, and returns a previously created string or a new string that is a copy of buffer. If NULL is passed returns NULL. Returned string should never be modified. To ensure this invariant, allocations are made in a special read only zone. */ OBJC_EXPORT NXAtom _Nonnull NXUniqueStringWithLength(const char * _Nullable buffer, int length) OBJC_HASH_AVAILABILITY; /* assumes that buffer is a non NULL buffer of at least length characters. Returns a previously created string or a new string that is a copy of buffer. If buffer contains \0, string will be truncated. As for NXUniqueString, returned string should never be modified. */ OBJC_EXPORT NXAtom _Nullable NXUniqueStringNoCopy(const char * _Nullable string) OBJC_HASH_AVAILABILITY; /* If there is already a unique string equal to string, returns the original. Otherwise, string is entered in the table, without making a copy. Argument should then never be modified. */ OBJC_EXPORT char * _Nullable NXCopyStringBuffer(const char * _Nullable buffer) OBJC_HASH_AVAILABILITY; /* given a buffer, allocates a new string copy of buffer. Buffer should be \0 terminated; returned string is \0 terminated. */ OBJC_EXPORT char * _Nullable NXCopyStringBufferFromZone(const char * _Nullable buffer, void * _Nullable z) OBJC_HASH_AVAILABILITY; /* given a buffer, allocates a new string copy of buffer. Buffer should be \0 terminated; returned string is \0 terminated. */ #endif __END_DECLS #endif /* _OBJC_LITTLE_HASHTABLE_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/hashtable2.mm ================================================ /* * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* hashtable2.m Copyright 1989-1996 NeXT Software, Inc. Created by Bertrand Serlet, Feb 89 */ #include "objc-private.h" #include "hashtable2.h" /* In order to improve efficiency, buckets contain a pointer to an array or directly the data when the array size is 1 */ typedef union { const void *one; const void **many; } oneOrMany; /* an optimization consists of storing directly data when count = 1 */ typedef struct { unsigned count; oneOrMany elements; } HashBucket; /* private data structure; may change */ /************************************************************************* * * Macros and utilities * *************************************************************************/ #define PTRSIZE sizeof(void *) #if !SUPPORT_ZONES # define DEFAULT_ZONE NULL # define ZONE_FROM_PTR(p) NULL # define ALLOCTABLE(z) ((NXHashTable *) malloc (sizeof (NXHashTable))) # define ALLOCBUCKETS(z,nb)((HashBucket *) calloc (nb, sizeof (HashBucket))) /* Return interior pointer so a table of classes doesn't look like objects */ # define ALLOCPAIRS(z,nb) (1+(const void **) calloc (nb+1, sizeof (void *))) # define FREEPAIRS(p) (free((void*)(-1+p))) #else # define DEFAULT_ZONE malloc_default_zone() # define ZONE_FROM_PTR(p) malloc_zone_from_ptr(p) # define ALLOCTABLE(z) ((NXHashTable *) malloc_zone_malloc ((malloc_zone_t *)z,sizeof (NXHashTable))) # define ALLOCBUCKETS(z,nb)((HashBucket *) malloc_zone_calloc ((malloc_zone_t *)z, nb, sizeof (HashBucket))) /* Return interior pointer so a table of classes doesn't look like objects */ # define ALLOCPAIRS(z,nb) (1+(const void **) malloc_zone_calloc ((malloc_zone_t *)z, nb+1, sizeof (void *))) # define FREEPAIRS(p) (free((void*)(-1+p))) #endif #if !SUPPORT_MOD /* nbBuckets must be a power of 2 */ # define BUCKETOF(table, data) (((HashBucket *)table->buckets)+((*table->prototype->hash)(table->info, data) & (table->nbBuckets-1))) # define GOOD_CAPACITY(c) (c <= 1 ? 1 : 1 << (log2u (c-1)+1)) # define MORE_CAPACITY(b) (b*2) #else /* iff necessary this modulo can be optimized since the nbBuckets is of the form 2**n-1 */ # define BUCKETOF(table, data) (((HashBucket *)table->buckets)+((*table->prototype->hash)(table->info, data) % table->nbBuckets)) # define GOOD_CAPACITY(c) (exp2m1u (log2u (c)+1)) # define MORE_CAPACITY(b) (b*2+1) #endif #define ISEQUAL(table, data1, data2) ((data1 == data2) || (*table->prototype->isEqual)(table->info, data1, data2)) /* beware of double evaluation */ /************************************************************************* * * Global data and bootstrap * *************************************************************************/ static int isEqualPrototype (const void *info, const void *data1, const void *data2) { NXHashTablePrototype *proto1 = (NXHashTablePrototype *) data1; NXHashTablePrototype *proto2 = (NXHashTablePrototype *) data2; return (proto1->hash == proto2->hash) && (proto1->isEqual == proto2->isEqual) && (proto1->free == proto2->free) && (proto1->style == proto2->style); }; static uintptr_t hashPrototype (const void *info, const void *data) { NXHashTablePrototype *proto = (NXHashTablePrototype *) data; return NXPtrHash(info, (void*)proto->hash) ^ NXPtrHash(info, (void*)proto->isEqual) ^ NXPtrHash(info, (void*)proto->free) ^ (uintptr_t) proto->style; }; void NXNoEffectFree (const void *info, void *data) {}; static NXHashTablePrototype protoPrototype = { hashPrototype, isEqualPrototype, NXNoEffectFree, 0 }; static NXHashTable *prototypes = NULL; /* table of all prototypes */ static void bootstrap (void) { free(malloc(8)); prototypes = ALLOCTABLE (DEFAULT_ZONE); prototypes->prototype = &protoPrototype; prototypes->count = 1; prototypes->nbBuckets = 1; /* has to be 1 so that the right bucket is 0 */ prototypes->buckets = ALLOCBUCKETS(DEFAULT_ZONE, 1); prototypes->info = NULL; ((HashBucket *) prototypes->buckets)[0].count = 1; ((HashBucket *) prototypes->buckets)[0].elements.one = &protoPrototype; }; int NXPtrIsEqual (const void *info, const void *data1, const void *data2) { return data1 == data2; }; /************************************************************************* * * On z'y va * *************************************************************************/ NXHashTable *NXCreateHashTable (NXHashTablePrototype prototype, unsigned capacity, const void *info) { return NXCreateHashTableFromZone(prototype, capacity, info, DEFAULT_ZONE); } NXHashTable *NXCreateHashTableFromZone (NXHashTablePrototype prototype, unsigned capacity, const void *info, void *z) { NXHashTable *table; NXHashTablePrototype *proto; table = ALLOCTABLE(z); if (! prototypes) bootstrap (); if (! prototype.hash) prototype.hash = NXPtrHash; if (! prototype.isEqual) prototype.isEqual = NXPtrIsEqual; if (! prototype.free) prototype.free = NXNoEffectFree; if (prototype.style) { _objc_inform ("*** NXCreateHashTable: invalid style\n"); return NULL; }; proto = (NXHashTablePrototype *)NXHashGet (prototypes, &prototype); if (! proto) { proto = (NXHashTablePrototype *) malloc(sizeof (NXHashTablePrototype)); bcopy ((const char*)&prototype, (char*)proto, sizeof (NXHashTablePrototype)); (void) NXHashInsert (prototypes, proto); proto = (NXHashTablePrototype *)NXHashGet (prototypes, &prototype); if (! proto) { _objc_inform ("*** NXCreateHashTable: bug\n"); return NULL; }; }; table->prototype = proto; table->count = 0; table->info = info; table->nbBuckets = GOOD_CAPACITY(capacity); table->buckets = ALLOCBUCKETS(z, table->nbBuckets); return table; } static void freeBucketPairs (void (*freeProc)(const void *info, void *data), HashBucket bucket, const void *info) { unsigned j = bucket.count; const void **pairs; if (j == 1) { (*freeProc) (info, (void *) bucket.elements.one); return; }; pairs = bucket.elements.many; while (j--) { (*freeProc) (info, (void *) *pairs); pairs ++; }; FREEPAIRS (bucket.elements.many); }; static void freeBuckets (NXHashTable *table, int freeObjects) { unsigned i = table->nbBuckets; HashBucket *buckets = (HashBucket *) table->buckets; while (i--) { if (buckets->count) { freeBucketPairs ((freeObjects) ? table->prototype->free : NXNoEffectFree, *buckets, table->info); buckets->count = 0; buckets->elements.one = NULL; }; buckets++; }; }; void NXFreeHashTable (NXHashTable *table) { freeBuckets (table, YES); free (table->buckets); free (table); }; void NXEmptyHashTable (NXHashTable *table) { freeBuckets (table, NO); table->count = 0; } void NXResetHashTable (NXHashTable *table) { freeBuckets (table, YES); table->count = 0; } BOOL NXCompareHashTables (NXHashTable *table1, NXHashTable *table2) { if (table1 == table2) return YES; if (NXCountHashTable (table1) != NXCountHashTable (table2)) return NO; else { void *data; NXHashState state = NXInitHashState (table1); while (NXNextHashState (table1, &state, &data)) { if (! NXHashMember (table2, data)) return NO; } return YES; } } NXHashTable *NXCopyHashTable (NXHashTable *table) { NXHashTable *newt; NXHashState state = NXInitHashState (table); void *data; __unused void *z = ZONE_FROM_PTR(table); newt = ALLOCTABLE(z); newt->prototype = table->prototype; newt->count = 0; newt->info = table->info; newt->nbBuckets = table->nbBuckets; newt->buckets = ALLOCBUCKETS(z, newt->nbBuckets); while (NXNextHashState (table, &state, &data)) NXHashInsert (newt, data); return newt; } unsigned NXCountHashTable (NXHashTable *table) { return table->count; } int NXHashMember (NXHashTable *table, const void *data) { HashBucket *bucket = BUCKETOF(table, data); unsigned j = bucket->count; const void **pairs; if (! j) return 0; if (j == 1) { return ISEQUAL(table, data, bucket->elements.one); }; pairs = bucket->elements.many; while (j--) { /* we don't cache isEqual because lists are short */ if (ISEQUAL(table, data, *pairs)) return 1; pairs ++; }; return 0; } void *NXHashGet (NXHashTable *table, const void *data) { HashBucket *bucket = BUCKETOF(table, data); unsigned j = bucket->count; const void **pairs; if (! j) return NULL; if (j == 1) { return ISEQUAL(table, data, bucket->elements.one) ? (void *) bucket->elements.one : NULL; }; pairs = bucket->elements.many; while (j--) { /* we don't cache isEqual because lists are short */ if (ISEQUAL(table, data, *pairs)) return (void *) *pairs; pairs ++; }; return NULL; } unsigned _NXHashCapacity (NXHashTable *table) { return table->nbBuckets; } void _NXHashRehashToCapacity (NXHashTable *table, unsigned newCapacity) { /* Rehash: we create a pseudo table pointing really to the old guys, extend self, copy the old pairs, and free the pseudo table */ NXHashTable *old; NXHashState state; void *aux; __unused void *z = ZONE_FROM_PTR(table); old = ALLOCTABLE(z); old->prototype = table->prototype; old->count = table->count; old->nbBuckets = table->nbBuckets; old->buckets = table->buckets; table->nbBuckets = newCapacity; table->count = 0; table->buckets = ALLOCBUCKETS(z, table->nbBuckets); state = NXInitHashState (old); while (NXNextHashState (old, &state, &aux)) (void) NXHashInsert (table, aux); freeBuckets (old, NO); if (old->count != table->count) _objc_inform("*** hashtable: count differs after rehashing; probably indicates a broken invariant: there are x and y such as isEqual(x, y) is TRUE but hash(x) != hash (y)\n"); free (old->buckets); free (old); } static void _NXHashRehash (NXHashTable *table) { _NXHashRehashToCapacity (table, MORE_CAPACITY(table->nbBuckets)); } void *NXHashInsert (NXHashTable *table, const void *data) { HashBucket *bucket = BUCKETOF(table, data); unsigned j = bucket->count; const void **pairs; const void **newt; __unused void *z = ZONE_FROM_PTR(table); if (! j) { bucket->count++; bucket->elements.one = data; table->count++; return NULL; }; if (j == 1) { if (ISEQUAL(table, data, bucket->elements.one)) { const void *old = bucket->elements.one; bucket->elements.one = data; return (void *) old; }; newt = ALLOCPAIRS(z, 2); newt[1] = bucket->elements.one; *newt = data; bucket->count++; bucket->elements.many = newt; table->count++; if (table->count > table->nbBuckets) _NXHashRehash (table); return NULL; }; pairs = bucket->elements.many; while (j--) { /* we don't cache isEqual because lists are short */ if (ISEQUAL(table, data, *pairs)) { const void *old = *pairs; *pairs = data; return (void *) old; }; pairs ++; }; /* we enlarge this bucket; and put new data in front */ newt = ALLOCPAIRS(z, bucket->count+1); if (bucket->count) bcopy ((const char*)bucket->elements.many, (char*)(newt+1), bucket->count * PTRSIZE); *newt = data; FREEPAIRS (bucket->elements.many); bucket->count++; bucket->elements.many = newt; table->count++; if (table->count > table->nbBuckets) _NXHashRehash (table); return NULL; } void *NXHashInsertIfAbsent (NXHashTable *table, const void *data) { HashBucket *bucket = BUCKETOF(table, data); unsigned j = bucket->count; const void **pairs; const void **newt; __unused void *z = ZONE_FROM_PTR(table); if (! j) { bucket->count++; bucket->elements.one = data; table->count++; return (void *) data; }; if (j == 1) { if (ISEQUAL(table, data, bucket->elements.one)) return (void *) bucket->elements.one; newt = ALLOCPAIRS(z, 2); newt[1] = bucket->elements.one; *newt = data; bucket->count++; bucket->elements.many = newt; table->count++; if (table->count > table->nbBuckets) _NXHashRehash (table); return (void *) data; }; pairs = bucket->elements.many; while (j--) { /* we don't cache isEqual because lists are short */ if (ISEQUAL(table, data, *pairs)) return (void *) *pairs; pairs ++; }; /* we enlarge this bucket; and put new data in front */ newt = ALLOCPAIRS(z, bucket->count+1); if (bucket->count) bcopy ((const char*)bucket->elements.many, (char*)(newt+1), bucket->count * PTRSIZE); *newt = data; FREEPAIRS (bucket->elements.many); bucket->count++; bucket->elements.many = newt; table->count++; if (table->count > table->nbBuckets) _NXHashRehash (table); return (void *) data; } void *NXHashRemove (NXHashTable *table, const void *data) { HashBucket *bucket = BUCKETOF(table, data); unsigned j = bucket->count; const void **pairs; const void **newt; __unused void *z = ZONE_FROM_PTR(table); if (! j) return NULL; if (j == 1) { if (! ISEQUAL(table, data, bucket->elements.one)) return NULL; data = bucket->elements.one; table->count--; bucket->count--; bucket->elements.one = NULL; return (void *) data; }; pairs = bucket->elements.many; if (j == 2) { if (ISEQUAL(table, data, pairs[0])) { bucket->elements.one = pairs[1]; data = pairs[0]; } else if (ISEQUAL(table, data, pairs[1])) { bucket->elements.one = pairs[0]; data = pairs[1]; } else return NULL; FREEPAIRS (pairs); table->count--; bucket->count--; return (void *) data; }; while (j--) { if (ISEQUAL(table, data, *pairs)) { data = *pairs; /* we shrink this bucket */ newt = (bucket->count-1) ? ALLOCPAIRS(z, bucket->count-1) : NULL; if (bucket->count-1 != j) bcopy ((const char*)bucket->elements.many, (char*)newt, PTRSIZE*(bucket->count-j-1)); if (j) bcopy ((const char*)(bucket->elements.many + bucket->count-j), (char*)(newt+bucket->count-j-1), PTRSIZE*j); FREEPAIRS (bucket->elements.many); table->count--; bucket->count--; bucket->elements.many = newt; return (void *) data; }; pairs ++; }; return NULL; } NXHashState NXInitHashState (NXHashTable *table) { NXHashState state; state.i = table->nbBuckets; state.j = 0; return state; }; int NXNextHashState (NXHashTable *table, NXHashState *state, void **data) { HashBucket *buckets = (HashBucket *) table->buckets; while (state->j == 0) { if (state->i == 0) return NO; state->i--; state->j = buckets[state->i].count; } state->j--; buckets += state->i; *data = (void *) ((buckets->count == 1) ? buckets->elements.one : buckets->elements.many[state->j]); return YES; }; /************************************************************************* * * Conveniences * *************************************************************************/ uintptr_t NXPtrHash (const void *info, const void *data) { return (((uintptr_t) data) >> 16) ^ ((uintptr_t) data); }; uintptr_t NXStrHash (const void *info, const void *data) { uintptr_t hash = 0; unsigned char *s = (unsigned char *) data; /* unsigned to avoid a sign-extend */ /* unroll the loop */ if (s) for (; ; ) { if (*s == '\0') break; hash ^= (uintptr_t) *s++; if (*s == '\0') break; hash ^= (uintptr_t) *s++ << 8; if (*s == '\0') break; hash ^= (uintptr_t) *s++ << 16; if (*s == '\0') break; hash ^= (uintptr_t) *s++ << 24; } return hash; }; int NXStrIsEqual (const void *info, const void *data1, const void *data2) { if (data1 == data2) return YES; if (! data1) return ! strlen ((char *) data2); if (! data2) return ! strlen ((char *) data1); if (((char *) data1)[0] != ((char *) data2)[0]) return NO; return (strcmp ((char *) data1, (char *) data2)) ? NO : YES; }; void NXReallyFree (const void *info, void *data) { free (data); }; /* All the following functions are really private, made non-static only for the benefit of shlibs */ static uintptr_t hashPtrStructKey (const void *info, const void *data) { return NXPtrHash(info, *((void **) data)); }; static int isEqualPtrStructKey (const void *info, const void *data1, const void *data2) { return NXPtrIsEqual (info, *((void **) data1), *((void **) data2)); }; static uintptr_t hashStrStructKey (const void *info, const void *data) { return NXStrHash(info, *((char **) data)); }; static int isEqualStrStructKey (const void *info, const void *data1, const void *data2) { return NXStrIsEqual (info, *((char **) data1), *((char **) data2)); }; const NXHashTablePrototype NXPtrPrototype = { NXPtrHash, NXPtrIsEqual, NXNoEffectFree, 0 }; const NXHashTablePrototype NXStrPrototype = { NXStrHash, NXStrIsEqual, NXNoEffectFree, 0 }; const NXHashTablePrototype NXPtrStructKeyPrototype = { hashPtrStructKey, isEqualPtrStructKey, NXReallyFree, 0 }; const NXHashTablePrototype NXStrStructKeyPrototype = { hashStrStructKey, isEqualStrStructKey, NXReallyFree, 0 }; /************************************************************************* * * Unique strings * *************************************************************************/ #if !__OBJC2__ && !TARGET_OS_WIN32 /* the implementation could be made faster at the expense of memory if the size of the strings were kept around */ static NXHashTable *uniqueStrings = NULL; /* this is based on most apps using a few K of strings, and an average string size of 15 using sqrt(2*dataAlloced*perChunkOverhead) */ #define CHUNK_SIZE 360 static int accessUniqueString = 0; static char *z = NULL; static size_t zSize = 0; mutex_t NXUniqueStringLock; static const char *CopyIntoReadOnly (const char *str) { size_t len = strlen (str) + 1; char *result; if (len > CHUNK_SIZE/2) { /* dont let big strings waste space */ result = (char *)malloc (len); bcopy (str, result, len); return result; } mutex_locker_t lock(NXUniqueStringLock); if (zSize < len) { zSize = CHUNK_SIZE *((len + CHUNK_SIZE - 1) / CHUNK_SIZE); /* not enough room, we try to allocate. If no room left, too bad */ z = (char *)malloc (zSize); }; result = z; bcopy (str, result, len); z += len; zSize -= len; return result; }; NXAtom NXUniqueString (const char *buffer) { const char *previous; if (! buffer) return buffer; accessUniqueString++; if (! uniqueStrings) uniqueStrings = NXCreateHashTable (NXStrPrototype, 0, NULL); previous = (const char *) NXHashGet (uniqueStrings, buffer); if (previous) return previous; previous = CopyIntoReadOnly (buffer); if (NXHashInsert (uniqueStrings, previous)) { _objc_inform ("*** NXUniqueString: invariant broken\n"); return NULL; }; return previous; }; NXAtom NXUniqueStringNoCopy (const char *string) { accessUniqueString++; if (! uniqueStrings) uniqueStrings = NXCreateHashTable (NXStrPrototype, 0, NULL); return (const char *) NXHashInsertIfAbsent (uniqueStrings, string); }; #define BUF_SIZE 256 NXAtom NXUniqueStringWithLength (const char *buffer, int length) { NXAtom atom; char *nullTermStr; char stackBuf[BUF_SIZE]; if (length+1 > BUF_SIZE) nullTermStr = (char *)malloc (length+1); else nullTermStr = stackBuf; bcopy (buffer, nullTermStr, length); nullTermStr[length] = '\0'; atom = NXUniqueString (nullTermStr); if (length+1 > BUF_SIZE) free (nullTermStr); return atom; }; char *NXCopyStringBufferFromZone (const char *str, void *zone) { #if !SUPPORT_ZONES return strdup(str); #else return strcpy ((char *) malloc_zone_malloc((malloc_zone_t *)zone, strlen (str) + 1), str); #endif }; char *NXCopyStringBuffer (const char *str) { return strdup(str); }; #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/isa.h ================================================ /* * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 2018 Apple Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /******************************************************************** * * isa.h - Definitions of isa fields for C and assembly code. * ********************************************************************/ #ifndef _OBJC_ISA_H_ #define _OBJC_ISA_H_ #include "objc-config.h" #if (!SUPPORT_NONPOINTER_ISA && !SUPPORT_PACKED_ISA && !SUPPORT_INDEXED_ISA) ||\ ( SUPPORT_NONPOINTER_ISA && SUPPORT_PACKED_ISA && !SUPPORT_INDEXED_ISA) ||\ ( SUPPORT_NONPOINTER_ISA && !SUPPORT_PACKED_ISA && SUPPORT_INDEXED_ISA) // good config #else # error bad config #endif #if SUPPORT_PACKED_ISA // extra_rc must be the MSB-most field (so it matches carry/overflow flags) // nonpointer must be the LSB (fixme or get rid of it) // shiftcls must occupy the same bits that a real class pointer would // bits + RC_ONE is equivalent to extra_rc + 1 // RC_HALF is the high bit of extra_rc (i.e. half of its range) // future expansion: // uintptr_t fast_rr : 1; // no r/r overrides // uintptr_t lock : 2; // lock for atomic property, @synch // uintptr_t extraBytes : 1; // allocated with extra bytes # if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8 # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7) # else # error unknown architecture for packed isa # endif // SUPPORT_PACKED_ISA #endif #if SUPPORT_INDEXED_ISA # if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__) // armv7k or arm64_32 # define ISA_INDEX_IS_NPI_BIT 0 # define ISA_INDEX_IS_NPI_MASK 0x00000001 # define ISA_INDEX_MASK 0x0001FFFC # define ISA_INDEX_SHIFT 2 # define ISA_INDEX_BITS 15 # define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS) # define ISA_INDEX_MAGIC_MASK 0x001E0001 # define ISA_INDEX_MAGIC_VALUE 0x001C0001 # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t indexcls : 15; \ uintptr_t magic : 4; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 7 # define RC_ONE (1ULL<<25) # define RC_HALF (1ULL<<6) # else # error unknown architecture for indexed isa # endif // SUPPORT_INDEXED_ISA #endif // _OBJC_ISA_H_ #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/llvm-AlignOf.h ================================================ //===--- AlignOf.h - Portable calculation of type alignment -----*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file defines the AlignOf function that computes alignments for // arbitrary types. // //===----------------------------------------------------------------------===// // Taken from llvmCore-3425.0.31. #ifndef LLVM_SUPPORT_ALIGNOF_H #define LLVM_SUPPORT_ALIGNOF_H #include namespace objc { template struct AlignmentCalcImpl { char x; T t; private: AlignmentCalcImpl() {} // Never instantiate. }; /// AlignOf - A templated class that contains an enum value representing /// the alignment of the template argument. For example, /// AlignOf::Alignment represents the alignment of type "int". The /// alignment calculated is the minimum alignment, and not necessarily /// the "desired" alignment returned by GCC's __alignof__ (for example). Note /// that because the alignment is an enum value, it can be used as a /// compile-time constant (e.g., for template instantiation). template struct AlignOf { enum { Alignment = static_cast(sizeof(AlignmentCalcImpl) - sizeof(T)) }; enum { Alignment_GreaterEqual_2Bytes = Alignment >= 2 ? 1 : 0 }; enum { Alignment_GreaterEqual_4Bytes = Alignment >= 4 ? 1 : 0 }; enum { Alignment_GreaterEqual_8Bytes = Alignment >= 8 ? 1 : 0 }; enum { Alignment_GreaterEqual_16Bytes = Alignment >= 16 ? 1 : 0 }; enum { Alignment_LessEqual_2Bytes = Alignment <= 2 ? 1 : 0 }; enum { Alignment_LessEqual_4Bytes = Alignment <= 4 ? 1 : 0 }; enum { Alignment_LessEqual_8Bytes = Alignment <= 8 ? 1 : 0 }; enum { Alignment_LessEqual_16Bytes = Alignment <= 16 ? 1 : 0 }; }; /// alignOf - A templated function that returns the minimum alignment of /// of a type. This provides no extra functionality beyond the AlignOf /// class besides some cosmetic cleanliness. Example usage: /// alignOf() returns the alignment of an int. template inline unsigned alignOf() { return AlignOf::Alignment; } /// \brief Helper for building an aligned character array type. /// /// This template is used to explicitly build up a collection of aligned /// character types. We have to build these up using a macro and explicit /// specialization to cope with old versions of MSVC and GCC where only an /// integer literal can be used to specify an alignment constraint. Once built /// up here, we can then begin to indirect between these using normal C++ /// template parameters. template struct AlignedCharArrayImpl; // MSVC requires special handling here. #ifndef _MSC_VER #if __has_feature(cxx_alignas) #define LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(x) \ template <> struct AlignedCharArrayImpl { \ char aligned alignas(x); \ } #elif defined(__GNUC__) #define LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(x) \ template <> struct AlignedCharArrayImpl { \ char aligned __attribute__((aligned(x))); \ } #else # error No supported align as directive. #endif LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(1); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(2); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(4); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(8); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(16); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(32); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(64); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(128); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(512); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(1024); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(2048); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(4096); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(8192); #undef LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT #else // _MSC_VER // We provide special variations of this template for the most common // alignments because __declspec(align(...)) doesn't actually work when it is // a member of a by-value function argument in MSVC, even if the alignment // request is something reasonably like 8-byte or 16-byte. template <> struct AlignedCharArrayImpl<1> { char aligned; }; template <> struct AlignedCharArrayImpl<2> { short aligned; }; template <> struct AlignedCharArrayImpl<4> { int aligned; }; template <> struct AlignedCharArrayImpl<8> { double aligned; }; #define LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(x) \ template <> struct AlignedCharArrayImpl { \ __declspec(align(x)) char aligned; \ } LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(16); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(32); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(64); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(128); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(512); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(1024); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(2048); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(4096); LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT(8192); // Any larger and MSVC complains. #undef LLVM_ALIGNEDCHARARRAY_TEMPLATE_ALIGNMENT #endif // _MSC_VER /// \brief This union template exposes a suitably aligned and sized character /// array member which can hold elements of any of up to four types. /// /// These types may be arrays, structs, or any other types. The goal is to /// produce a union type containing a character array which, when used, forms /// storage suitable to placement new any of these types over. Support for more /// than four types can be added at the cost of more boiler plate. template union AlignedCharArrayUnion { private: class AlignerImpl { T1 t1; T2 t2; T3 t3; T4 t4; AlignerImpl(); // Never defined or instantiated. }; union SizerImpl { char arr1[sizeof(T1)], arr2[sizeof(T2)], arr3[sizeof(T3)], arr4[sizeof(T4)]; }; public: /// \brief The character array buffer for use by clients. /// /// No other member of this union should be referenced. The exist purely to /// constrain the layout of this character array. char buffer[sizeof(SizerImpl)]; private: // Tests seem to indicate that both Clang and GCC will properly register the // alignment of a struct containing an aligned member, and this alignment // should carry over to the character array in the union. AlignedCharArrayImpl::Alignment> nonce_member; }; } // end namespace objc #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/llvm-DenseMap.h ================================================ //===- llvm/ADT/DenseMap.h - Dense probed hash table ------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file defines the DenseMap class. // //===----------------------------------------------------------------------===// // Taken from llvmCore-3425.0.31. #ifndef LLVM_ADT_DENSEMAP_H #define LLVM_ADT_DENSEMAP_H #include "llvm-type_traits.h" #include "llvm-MathExtras.h" #include "llvm-AlignOf.h" #include "llvm-DenseMapInfo.h" #include #include #include #include #include #include #include #include #include #include "objc-private.h" // From llvm/Support/Compiler.h #define LLVM_USE_RVALUE_REFERENCES 1 #define llvm_move(value) (::std::move(value)) #define MIN_BUCKETS 4 #define MIN_COMPACT 1024 namespace objc { template, bool IsConst = false> class DenseMapIterator; // ZeroValuesArePurgeable=true is used by the refcount table. // A key/value pair with value==0 is not required to be stored // in the refcount table; it could correctly be erased instead. // For performance, we do keep zero values in the table when the // true refcount decreases to 1: this makes any future retain faster. // For memory size, we allow rehashes and table insertions to // remove a zero value as if it were a tombstone. template class DenseMapBase { protected: typedef std::pair BucketT; public: typedef KeyT key_type; typedef ValueT mapped_type; typedef BucketT value_type; typedef DenseMapIterator iterator; typedef DenseMapIterator const_iterator; inline iterator begin() { // When the map is empty, avoid the overhead of AdvancePastEmptyBuckets(). return empty() ? end() : iterator(getBuckets(), getBucketsEnd()); } inline iterator end() { return iterator(getBucketsEnd(), getBucketsEnd(), true); } inline const_iterator begin() const { return empty() ? end() : const_iterator(getBuckets(), getBucketsEnd()); } inline const_iterator end() const { return const_iterator(getBucketsEnd(), getBucketsEnd(), true); } bool empty() const { return getNumEntries() == 0; } unsigned size() const { return getNumEntries(); } /// Grow the densemap so that it has at least Size buckets. Does not shrink void resize(size_t Size) { if (Size > getNumBuckets()) grow(Size); } void clear() { if (getNumEntries() == 0 && getNumTombstones() == 0) return; // If the capacity of the array is huge, and the # elements used is small, // shrink the array. if (getNumEntries() * 4 < getNumBuckets() && getNumBuckets() > MIN_BUCKETS) { shrink_and_clear(); return; } const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { if (!KeyInfoT::isEqual(P->first, EmptyKey)) { if (!KeyInfoT::isEqual(P->first, TombstoneKey)) { P->second.~ValueT(); decrementNumEntries(); } P->first = EmptyKey; } } assert(getNumEntries() == 0 && "Node count imbalance!"); setNumTombstones(0); } /// count - Return true if the specified key is in the map. bool count(const KeyT &Val) const { const BucketT *TheBucket; return LookupBucketFor(Val, TheBucket); } iterator find(const KeyT &Val) { BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) return iterator(TheBucket, getBucketsEnd(), true); return end(); } const_iterator find(const KeyT &Val) const { const BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) return const_iterator(TheBucket, getBucketsEnd(), true); return end(); } /// Alternate version of find() which allows a different, and possibly /// less expensive, key type. /// The DenseMapInfo is responsible for supplying methods /// getHashValue(LookupKeyT) and isEqual(LookupKeyT, KeyT) for each key /// type used. template iterator find_as(const LookupKeyT &Val) { BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) return iterator(TheBucket, getBucketsEnd(), true); return end(); } template const_iterator find_as(const LookupKeyT &Val) const { const BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) return const_iterator(TheBucket, getBucketsEnd(), true); return end(); } /// lookup - Return the entry for the specified key, or a default /// constructed value if no such entry exists. ValueT lookup(const KeyT &Val) const { const BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) return TheBucket->second; return ValueT(); } // Inserts key,value pair into the map if the key isn't already in the map. // If the key is already in the map, it returns false and doesn't update the // value. std::pair insert(const std::pair &KV) { BucketT *TheBucket; if (LookupBucketFor(KV.first, TheBucket)) return std::make_pair(iterator(TheBucket, getBucketsEnd(), true), false); // Already in map. // Otherwise, insert the new element. TheBucket = InsertIntoBucket(KV.first, KV.second, TheBucket); return std::make_pair(iterator(TheBucket, getBucketsEnd(), true), true); } /// insert - Range insertion of pairs. template void insert(InputIt I, InputIt E) { for (; I != E; ++I) insert(*I); } // Clear if empty. // Shrink if at least 15/16 empty and larger than MIN_COMPACT. void compact() { if (getNumEntries() == 0) { shrink_and_clear(); } else if (getNumBuckets() / 16 > getNumEntries() && getNumBuckets() > MIN_COMPACT) { grow(getNumEntries() * 2); } } bool erase(const KeyT &Val) { BucketT *TheBucket; if (!LookupBucketFor(Val, TheBucket)) return false; // not in map. TheBucket->second.~ValueT(); TheBucket->first = getTombstoneKey(); decrementNumEntries(); incrementNumTombstones(); compact(); return true; } void erase(iterator I) { BucketT *TheBucket = &*I; TheBucket->second.~ValueT(); TheBucket->first = getTombstoneKey(); decrementNumEntries(); incrementNumTombstones(); compact(); } value_type& FindAndConstruct(const KeyT &Key) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return *TheBucket; return *InsertIntoBucket(Key, ValueT(), TheBucket); } ValueT &operator[](const KeyT &Key) { return FindAndConstruct(Key).second; } #if LLVM_USE_RVALUE_REFERENCES value_type& FindAndConstruct(KeyT &&Key) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return *TheBucket; return *InsertIntoBucket(Key, ValueT(), TheBucket); } ValueT &operator[](KeyT &&Key) { return FindAndConstruct(Key).second; } #endif /// isPointerIntoBucketsArray - Return true if the specified pointer points /// somewhere into the DenseMap's array of buckets (i.e. either to a key or /// value in the DenseMap). bool isPointerIntoBucketsArray(const void *Ptr) const { return Ptr >= getBuckets() && Ptr < getBucketsEnd(); } /// getPointerIntoBucketsArray() - Return an opaque pointer into the buckets /// array. In conjunction with the previous method, this can be used to /// determine whether an insertion caused the DenseMap to reallocate. const void *getPointerIntoBucketsArray() const { return getBuckets(); } protected: DenseMapBase() {} void destroyAll() { if (getNumBuckets() == 0) // Nothing to do. return; const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { if (!KeyInfoT::isEqual(P->first, EmptyKey) && !KeyInfoT::isEqual(P->first, TombstoneKey)) P->second.~ValueT(); P->first.~KeyT(); } #ifndef NDEBUG memset((void*)getBuckets(), 0x5a, sizeof(BucketT)*getNumBuckets()); #endif } void initEmpty() { setNumEntries(0); setNumTombstones(0); assert((getNumBuckets() & (getNumBuckets()-1)) == 0 && "# initial buckets must be a power of two!"); const KeyT EmptyKey = getEmptyKey(); for (BucketT *B = getBuckets(), *E = getBucketsEnd(); B != E; ++B) new (&B->first) KeyT(EmptyKey); } void moveFromOldBuckets(BucketT *OldBucketsBegin, BucketT *OldBucketsEnd) { initEmpty(); // Insert all the old elements. const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); for (BucketT *B = OldBucketsBegin, *E = OldBucketsEnd; B != E; ++B) { if (!KeyInfoT::isEqual(B->first, EmptyKey) && !KeyInfoT::isEqual(B->first, TombstoneKey) && !(ZeroValuesArePurgeable && B->second == 0)) { // Insert the key/value into the new table. BucketT *DestBucket; bool FoundVal = LookupBucketFor(B->first, DestBucket); (void)FoundVal; // silence warning. assert(!FoundVal && "Key already in new map?"); DestBucket->first = llvm_move(B->first); new (&DestBucket->second) ValueT(llvm_move(B->second)); incrementNumEntries(); // Free the value. B->second.~ValueT(); } B->first.~KeyT(); } #ifndef NDEBUG if (OldBucketsBegin != OldBucketsEnd) memset((void*)OldBucketsBegin, 0x5a, sizeof(BucketT) * (OldBucketsEnd - OldBucketsBegin)); #endif } template void copyFrom(const DenseMapBase& other) { assert(getNumBuckets() == other.getNumBuckets()); setNumEntries(other.getNumEntries()); setNumTombstones(other.getNumTombstones()); if (isPodLike::value && isPodLike::value) memcpy(getBuckets(), other.getBuckets(), getNumBuckets() * sizeof(BucketT)); else for (size_t i = 0; i < getNumBuckets(); ++i) { new (&getBuckets()[i].first) KeyT(other.getBuckets()[i].first); if (!KeyInfoT::isEqual(getBuckets()[i].first, getEmptyKey()) && !KeyInfoT::isEqual(getBuckets()[i].first, getTombstoneKey())) new (&getBuckets()[i].second) ValueT(other.getBuckets()[i].second); } } void swap(DenseMapBase& RHS) { std::swap(getNumEntries(), RHS.getNumEntries()); std::swap(getNumTombstones(), RHS.getNumTombstones()); } static unsigned getHashValue(const KeyT &Val) { return KeyInfoT::getHashValue(Val); } template static unsigned getHashValue(const LookupKeyT &Val) { return KeyInfoT::getHashValue(Val); } static const KeyT getEmptyKey() { return KeyInfoT::getEmptyKey(); } static const KeyT getTombstoneKey() { return KeyInfoT::getTombstoneKey(); } private: unsigned getNumEntries() const { return static_cast(this)->getNumEntries(); } void setNumEntries(unsigned Num) { static_cast(this)->setNumEntries(Num); } void incrementNumEntries() { setNumEntries(getNumEntries() + 1); } void decrementNumEntries() { setNumEntries(getNumEntries() - 1); } unsigned getNumTombstones() const { return static_cast(this)->getNumTombstones(); } void setNumTombstones(unsigned Num) { static_cast(this)->setNumTombstones(Num); } void incrementNumTombstones() { setNumTombstones(getNumTombstones() + 1); } void decrementNumTombstones() { setNumTombstones(getNumTombstones() - 1); } const BucketT *getBuckets() const { return static_cast(this)->getBuckets(); } BucketT *getBuckets() { return static_cast(this)->getBuckets(); } unsigned getNumBuckets() const { return static_cast(this)->getNumBuckets(); } BucketT *getBucketsEnd() { return getBuckets() + getNumBuckets(); } const BucketT *getBucketsEnd() const { return getBuckets() + getNumBuckets(); } void grow(unsigned AtLeast) { static_cast(this)->grow(AtLeast); } void shrink_and_clear() { static_cast(this)->shrink_and_clear(); } BucketT *InsertIntoBucket(const KeyT &Key, const ValueT &Value, BucketT *TheBucket) { TheBucket = InsertIntoBucketImpl(Key, TheBucket); TheBucket->first = Key; new (&TheBucket->second) ValueT(Value); return TheBucket; } #if LLVM_USE_RVALUE_REFERENCES BucketT *InsertIntoBucket(const KeyT &Key, ValueT &&Value, BucketT *TheBucket) { TheBucket = InsertIntoBucketImpl(Key, TheBucket); TheBucket->first = Key; new (&TheBucket->second) ValueT(std::move(Value)); return TheBucket; } BucketT *InsertIntoBucket(KeyT &&Key, ValueT &&Value, BucketT *TheBucket) { TheBucket = InsertIntoBucketImpl(Key, TheBucket); TheBucket->first = std::move(Key); new (&TheBucket->second) ValueT(std::move(Value)); return TheBucket; } #endif BucketT *InsertIntoBucketImpl(const KeyT &Key, BucketT *TheBucket) { // If the load of the hash table is more than 3/4, grow the table. // If fewer than 1/8 of the buckets are empty (meaning that many are // filled with tombstones), rehash the table without growing. // // The later case is tricky. For example, if we had one empty bucket with // tons of tombstones, failing lookups (e.g. for insertion) would have to // probe almost the entire table until it found the empty bucket. If the // table completely filled with tombstones, no lookup would ever succeed, // causing infinite loops in lookup. unsigned NewNumEntries = getNumEntries() + 1; unsigned NumBuckets = getNumBuckets(); if (NewNumEntries*4 >= NumBuckets*3) { this->grow(NumBuckets * 2); LookupBucketFor(Key, TheBucket); NumBuckets = getNumBuckets(); } if (NumBuckets-(NewNumEntries+getNumTombstones()) <= NumBuckets/8) { this->grow(NumBuckets); LookupBucketFor(Key, TheBucket); } assert(TheBucket); // Only update the state after we've grown our bucket space appropriately // so that when growing buckets we have self-consistent entry count. // If we are writing over a tombstone or zero value, remember this. if (KeyInfoT::isEqual(TheBucket->first, getEmptyKey())) { // Replacing an empty bucket. incrementNumEntries(); } else if (KeyInfoT::isEqual(TheBucket->first, getTombstoneKey())) { // Replacing a tombstone. incrementNumEntries(); decrementNumTombstones(); } else if (ZeroValuesArePurgeable && TheBucket->second == 0) { // Purging a zero. No accounting changes. TheBucket->second.~ValueT(); } else { // Updating an existing entry. No accounting changes. } return TheBucket; } /// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in /// FoundBucket. If the bucket contains the key and a value, this returns /// true, otherwise it returns a bucket with an empty marker or tombstone /// or zero value and returns false. template bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const { const BucketT *BucketsPtr = getBuckets(); const unsigned NumBuckets = getNumBuckets(); if (NumBuckets == 0) { FoundBucket = 0; return false; } // FoundTombstone - Keep track of whether we find a tombstone or zero value while probing. const BucketT *FoundTombstone = 0; const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); assert(!KeyInfoT::isEqual(Val, EmptyKey) && !KeyInfoT::isEqual(Val, TombstoneKey) && "Empty/Tombstone value shouldn't be inserted into map!"); unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); unsigned ProbeAmt = 1; while (1) { const BucketT *ThisBucket = BucketsPtr + BucketNo; // Found Val's bucket? If so, return it. if (KeyInfoT::isEqual(Val, ThisBucket->first)) { FoundBucket = ThisBucket; return true; } // If we found an empty bucket, the key doesn't exist in the set. // Insert it and return the default value. if (KeyInfoT::isEqual(ThisBucket->first, EmptyKey)) { // If we've already seen a tombstone while probing, fill it in instead // of the empty bucket we eventually probed to. if (FoundTombstone) ThisBucket = FoundTombstone; FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket; return false; } // If this is a tombstone, remember it. If Val ends up not in the map, we // prefer to return it than something that would require more probing. // Ditto for zero values. if (KeyInfoT::isEqual(ThisBucket->first, TombstoneKey) && !FoundTombstone) FoundTombstone = ThisBucket; // Remember the first tombstone found. if (ZeroValuesArePurgeable && ThisBucket->second == 0 && !FoundTombstone) FoundTombstone = ThisBucket; // Otherwise, it's a hash collision or a tombstone, continue quadratic // probing. if (ProbeAmt > NumBuckets) { // No empty buckets in table. Die. _objc_fatal("Hash table corrupted. This is probably a memory error " "somewhere. (table at %p, buckets at %p (%zu bytes), " "%u buckets, %u entries, %u tombstones, " "data %p %p %p %p)", this, BucketsPtr, malloc_size(BucketsPtr), NumBuckets, getNumEntries(), getNumTombstones(), ((void**)BucketsPtr)[0], ((void**)BucketsPtr)[1], ((void**)BucketsPtr)[2], ((void**)BucketsPtr)[3]); } BucketNo += ProbeAmt++; BucketNo&= (NumBuckets-1); } } template bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) { const BucketT *ConstFoundBucket; bool Result = const_cast(this) ->LookupBucketFor(Val, ConstFoundBucket); FoundBucket = const_cast(ConstFoundBucket); return Result; } public: /// Return the approximate size (in bytes) of the actual map. /// This is just the raw memory used by DenseMap. /// If entries are pointers to objects, the size of the referenced objects /// are not included. size_t getMemorySize() const { return getNumBuckets() * sizeof(BucketT); } }; template > class DenseMap : public DenseMapBase, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> { // Lift some types from the dependent base class into this class for // simplicity of referring to them. typedef DenseMapBase BaseT; typedef typename BaseT::BucketT BucketT; friend class DenseMapBase; BucketT *Buckets; unsigned NumEntries; unsigned NumTombstones; unsigned NumBuckets; public: explicit DenseMap(unsigned NumInitBuckets = 0) { init(NumInitBuckets); } DenseMap(const DenseMap &other) { init(0); copyFrom(other); } #if LLVM_USE_RVALUE_REFERENCES DenseMap(DenseMap &&other) { init(0); swap(other); } #endif template DenseMap(const InputIt &I, const InputIt &E) { init(NextPowerOf2(std::distance(I, E))); this->insert(I, E); } ~DenseMap() { this->destroyAll(); operator delete(Buckets); } void swap(DenseMap& RHS) { std::swap(Buckets, RHS.Buckets); std::swap(NumEntries, RHS.NumEntries); std::swap(NumTombstones, RHS.NumTombstones); std::swap(NumBuckets, RHS.NumBuckets); } DenseMap& operator=(const DenseMap& other) { copyFrom(other); return *this; } #if LLVM_USE_RVALUE_REFERENCES DenseMap& operator=(DenseMap &&other) { this->destroyAll(); operator delete(Buckets); init(0); swap(other); return *this; } #endif void copyFrom(const DenseMap& other) { this->destroyAll(); operator delete(Buckets); if (allocateBuckets(other.NumBuckets)) { this->BaseT::copyFrom(other); } else { NumEntries = 0; NumTombstones = 0; } } void init(unsigned InitBuckets) { if (allocateBuckets(InitBuckets)) { this->BaseT::initEmpty(); } else { NumEntries = 0; NumTombstones = 0; } } void grow(unsigned AtLeast) { unsigned OldNumBuckets = NumBuckets; BucketT *OldBuckets = Buckets; allocateBuckets(std::max(MIN_BUCKETS, NextPowerOf2(AtLeast))); assert(Buckets); if (!OldBuckets) { this->BaseT::initEmpty(); return; } this->moveFromOldBuckets(OldBuckets, OldBuckets+OldNumBuckets); // Free the old table. operator delete(OldBuckets); } void shrink_and_clear() { unsigned OldNumEntries = NumEntries; this->destroyAll(); // Reduce the number of buckets. unsigned NewNumBuckets = 0; if (OldNumEntries) NewNumBuckets = std::max(MIN_BUCKETS, 1 << (Log2_32_Ceil(OldNumEntries) + 1)); if (NewNumBuckets == NumBuckets) { this->BaseT::initEmpty(); return; } operator delete(Buckets); init(NewNumBuckets); } private: unsigned getNumEntries() const { return NumEntries; } void setNumEntries(unsigned Num) { NumEntries = Num; } unsigned getNumTombstones() const { return NumTombstones; } void setNumTombstones(unsigned Num) { NumTombstones = Num; } BucketT *getBuckets() const { return Buckets; } unsigned getNumBuckets() const { return NumBuckets; } bool allocateBuckets(unsigned Num) { NumBuckets = Num; if (NumBuckets == 0) { Buckets = 0; return false; } Buckets = static_cast(operator new(sizeof(BucketT)*NumBuckets)); return true; } }; template > class SmallDenseMap : public DenseMapBase, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> { // Lift some types from the dependent base class into this class for // simplicity of referring to them. typedef DenseMapBase BaseT; typedef typename BaseT::BucketT BucketT; friend class DenseMapBase; unsigned Small : 1; unsigned NumEntries : 31; unsigned NumTombstones; struct LargeRep { BucketT *Buckets; unsigned NumBuckets; }; /// A "union" of an inline bucket array and the struct representing /// a large bucket. This union will be discriminated by the 'Small' bit. AlignedCharArrayUnion storage; public: explicit SmallDenseMap(unsigned NumInitBuckets = 0) { init(NumInitBuckets); } SmallDenseMap(const SmallDenseMap &other) { init(0); copyFrom(other); } #if LLVM_USE_RVALUE_REFERENCES SmallDenseMap(SmallDenseMap &&other) { init(0); swap(other); } #endif template SmallDenseMap(const InputIt &I, const InputIt &E) { init(NextPowerOf2(std::distance(I, E))); this->insert(I, E); } ~SmallDenseMap() { this->destroyAll(); deallocateBuckets(); } void swap(SmallDenseMap& RHS) { unsigned TmpNumEntries = RHS.NumEntries; RHS.NumEntries = NumEntries; NumEntries = TmpNumEntries; std::swap(NumTombstones, RHS.NumTombstones); const KeyT EmptyKey = this->getEmptyKey(); const KeyT TombstoneKey = this->getTombstoneKey(); if (Small && RHS.Small) { // If we're swapping inline bucket arrays, we have to cope with some of // the tricky bits of DenseMap's storage system: the buckets are not // fully initialized. Thus we swap every key, but we may have // a one-directional move of the value. for (unsigned i = 0, e = InlineBuckets; i != e; ++i) { BucketT *LHSB = &getInlineBuckets()[i], *RHSB = &RHS.getInlineBuckets()[i]; bool hasLHSValue = (!KeyInfoT::isEqual(LHSB->first, EmptyKey) && !KeyInfoT::isEqual(LHSB->first, TombstoneKey)); bool hasRHSValue = (!KeyInfoT::isEqual(RHSB->first, EmptyKey) && !KeyInfoT::isEqual(RHSB->first, TombstoneKey)); if (hasLHSValue && hasRHSValue) { // Swap together if we can... std::swap(*LHSB, *RHSB); continue; } // Swap separately and handle any assymetry. std::swap(LHSB->first, RHSB->first); if (hasLHSValue) { new (&RHSB->second) ValueT(llvm_move(LHSB->second)); LHSB->second.~ValueT(); } else if (hasRHSValue) { new (&LHSB->second) ValueT(llvm_move(RHSB->second)); RHSB->second.~ValueT(); } } return; } if (!Small && !RHS.Small) { std::swap(getLargeRep()->Buckets, RHS.getLargeRep()->Buckets); std::swap(getLargeRep()->NumBuckets, RHS.getLargeRep()->NumBuckets); return; } SmallDenseMap &SmallSide = Small ? *this : RHS; SmallDenseMap &LargeSide = Small ? RHS : *this; // First stash the large side's rep and move the small side across. LargeRep TmpRep = llvm_move(*LargeSide.getLargeRep()); LargeSide.getLargeRep()->~LargeRep(); LargeSide.Small = true; // This is similar to the standard move-from-old-buckets, but the bucket // count hasn't actually rotated in this case. So we have to carefully // move construct the keys and values into their new locations, but there // is no need to re-hash things. for (unsigned i = 0, e = InlineBuckets; i != e; ++i) { BucketT *NewB = &LargeSide.getInlineBuckets()[i], *OldB = &SmallSide.getInlineBuckets()[i]; new (&NewB->first) KeyT(llvm_move(OldB->first)); OldB->first.~KeyT(); if (!KeyInfoT::isEqual(NewB->first, EmptyKey) && !KeyInfoT::isEqual(NewB->first, TombstoneKey)) { new (&NewB->second) ValueT(llvm_move(OldB->second)); OldB->second.~ValueT(); } } // The hard part of moving the small buckets across is done, just move // the TmpRep into its new home. SmallSide.Small = false; new (SmallSide.getLargeRep()) LargeRep(llvm_move(TmpRep)); } SmallDenseMap& operator=(const SmallDenseMap& other) { copyFrom(other); return *this; } #if LLVM_USE_RVALUE_REFERENCES SmallDenseMap& operator=(SmallDenseMap &&other) { this->destroyAll(); deallocateBuckets(); init(0); swap(other); return *this; } #endif void copyFrom(const SmallDenseMap& other) { this->destroyAll(); deallocateBuckets(); Small = true; if (other.getNumBuckets() > InlineBuckets) { Small = false; allocateBuckets(other.getNumBuckets()); } this->BaseT::copyFrom(other); } void init(unsigned InitBuckets) { Small = true; if (InitBuckets > InlineBuckets) { Small = false; new (getLargeRep()) LargeRep(allocateBuckets(InitBuckets)); } this->BaseT::initEmpty(); } void grow(unsigned AtLeast) { if (AtLeast > InlineBuckets) AtLeast = std::max(MIN_BUCKETS, NextPowerOf2(AtLeast)); if (Small) { if (AtLeast <= InlineBuckets) return; // Nothing to do. // First move the inline buckets into a temporary storage. AlignedCharArrayUnion TmpStorage; BucketT *TmpBegin = reinterpret_cast(TmpStorage.buffer); BucketT *TmpEnd = TmpBegin; // Loop over the buckets, moving non-empty, non-tombstones into the // temporary storage. Have the loop move the TmpEnd forward as it goes. const KeyT EmptyKey = this->getEmptyKey(); const KeyT TombstoneKey = this->getTombstoneKey(); for (BucketT *P = getBuckets(), *E = P + InlineBuckets; P != E; ++P) { if (!KeyInfoT::isEqual(P->first, EmptyKey) && !KeyInfoT::isEqual(P->first, TombstoneKey)) { assert(size_t(TmpEnd - TmpBegin) < InlineBuckets && "Too many inline buckets!"); new (&TmpEnd->first) KeyT(llvm_move(P->first)); new (&TmpEnd->second) ValueT(llvm_move(P->second)); ++TmpEnd; P->second.~ValueT(); } P->first.~KeyT(); } // Now make this map use the large rep, and move all the entries back // into it. Small = false; new (getLargeRep()) LargeRep(allocateBuckets(AtLeast)); this->moveFromOldBuckets(TmpBegin, TmpEnd); return; } LargeRep OldRep = llvm_move(*getLargeRep()); getLargeRep()->~LargeRep(); if (AtLeast <= InlineBuckets) { Small = true; } else { new (getLargeRep()) LargeRep(allocateBuckets(AtLeast)); } this->moveFromOldBuckets(OldRep.Buckets, OldRep.Buckets+OldRep.NumBuckets); // Free the old table. operator delete(OldRep.Buckets); } void shrink_and_clear() { unsigned OldSize = this->size(); this->destroyAll(); // Reduce the number of buckets. unsigned NewNumBuckets = 0; if (OldSize) { NewNumBuckets = 1 << (Log2_32_Ceil(OldSize) + 1); if (NewNumBuckets > InlineBuckets && NewNumBuckets < MIN_BUCKETS) NewNumBuckets = MIN_BUCKETS; } if ((Small && NewNumBuckets <= InlineBuckets) || (!Small && NewNumBuckets == getLargeRep()->NumBuckets)) { this->BaseT::initEmpty(); return; } deallocateBuckets(); init(NewNumBuckets); } private: unsigned getNumEntries() const { return NumEntries; } void setNumEntries(unsigned Num) { assert(Num < INT_MAX && "Cannot support more than INT_MAX entries"); NumEntries = Num; } unsigned getNumTombstones() const { return NumTombstones; } void setNumTombstones(unsigned Num) { NumTombstones = Num; } const BucketT *getInlineBuckets() const { assert(Small); // Note that this cast does not violate aliasing rules as we assert that // the memory's dynamic type is the small, inline bucket buffer, and the // 'storage.buffer' static type is 'char *'. return reinterpret_cast(storage.buffer); } BucketT *getInlineBuckets() { return const_cast( const_cast(this)->getInlineBuckets()); } const LargeRep *getLargeRep() const { assert(!Small); // Note, same rule about aliasing as with getInlineBuckets. return reinterpret_cast(storage.buffer); } LargeRep *getLargeRep() { return const_cast( const_cast(this)->getLargeRep()); } const BucketT *getBuckets() const { return Small ? getInlineBuckets() : getLargeRep()->Buckets; } BucketT *getBuckets() { return const_cast( const_cast(this)->getBuckets()); } unsigned getNumBuckets() const { return Small ? InlineBuckets : getLargeRep()->NumBuckets; } void deallocateBuckets() { if (Small) return; operator delete(getLargeRep()->Buckets); getLargeRep()->~LargeRep(); } LargeRep allocateBuckets(unsigned Num) { assert(Num > InlineBuckets && "Must allocate more buckets than are inline"); LargeRep Rep = { static_cast(operator new(sizeof(BucketT) * Num)), Num }; return Rep; } }; template class DenseMapIterator { typedef std::pair Bucket; typedef DenseMapIterator ConstIterator; friend class DenseMapIterator; public: typedef ptrdiff_t difference_type; typedef typename conditional::type value_type; typedef value_type *pointer; typedef value_type &reference; typedef std::forward_iterator_tag iterator_category; private: pointer Ptr, End; public: DenseMapIterator() : Ptr(0), End(0) {} DenseMapIterator(pointer Pos, pointer E, bool NoAdvance = false) : Ptr(Pos), End(E) { if (!NoAdvance) AdvancePastEmptyBuckets(); } // If IsConst is true this is a converting constructor from iterator to // const_iterator and the default copy constructor is used. // Otherwise this is a copy constructor for iterator. DenseMapIterator(const DenseMapIterator& I) : Ptr(I.Ptr), End(I.End) {} reference operator*() const { return *Ptr; } pointer operator->() const { return Ptr; } bool operator==(const ConstIterator &RHS) const { return Ptr == RHS.operator->(); } bool operator!=(const ConstIterator &RHS) const { return Ptr != RHS.operator->(); } inline DenseMapIterator& operator++() { // Preincrement ++Ptr; AdvancePastEmptyBuckets(); return *this; } DenseMapIterator operator++(int) { // Postincrement DenseMapIterator tmp = *this; ++*this; return tmp; } private: void AdvancePastEmptyBuckets() { const KeyT Empty = KeyInfoT::getEmptyKey(); const KeyT Tombstone = KeyInfoT::getTombstoneKey(); while (Ptr != End && (KeyInfoT::isEqual(Ptr->first, Empty) || KeyInfoT::isEqual(Ptr->first, Tombstone))) ++Ptr; } }; } // end namespace objc #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/llvm-DenseMapInfo.h ================================================ //===- llvm/ADT/DenseMapInfo.h - Type traits for DenseMap -------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file defines DenseMapInfo traits for DenseMap. // //===----------------------------------------------------------------------===// // Taken from llvmCore-3425.0.31. #ifndef LLVM_ADT_DENSEMAPINFO_H #define LLVM_ADT_DENSEMAPINFO_H #include "objc-private.h" #include "llvm-type_traits.h" namespace objc { template struct DenseMapInfo { //static inline T getEmptyKey(); //static inline T getTombstoneKey(); //static unsigned getHashValue(const T &Val); //static bool isEqual(const T &LHS, const T &RHS); }; // Provide DenseMapInfo for all pointers. template struct DenseMapInfo { static inline T* getEmptyKey() { uintptr_t Val = static_cast(-1); return reinterpret_cast(Val); } static inline T* getTombstoneKey() { uintptr_t Val = static_cast(-2); return reinterpret_cast(Val); } static unsigned getHashValue(const T *PtrVal) { return ptr_hash((uintptr_t)PtrVal); } static bool isEqual(const T *LHS, const T *RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for disguised pointers. template struct DenseMapInfo> { static inline DisguisedPtr getEmptyKey() { return DisguisedPtr((T*)(uintptr_t)-1); } static inline DisguisedPtr getTombstoneKey() { return DisguisedPtr((T*)(uintptr_t)-2); } static unsigned getHashValue(const T *PtrVal) { return ptr_hash((uintptr_t)PtrVal); } static bool isEqual(const DisguisedPtr &LHS, const DisguisedPtr &RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for cstrings. template<> struct DenseMapInfo { static inline const char* getEmptyKey() { return reinterpret_cast((intptr_t)-1); } static inline const char* getTombstoneKey() { return reinterpret_cast((intptr_t)-2); } static unsigned getHashValue(const char* const &Val) { return _objc_strhash(Val); } static bool isEqual(const char* const &LHS, const char* const &RHS) { return 0 == strcmp(LHS, RHS); } }; // Provide DenseMapInfo for chars. template<> struct DenseMapInfo { static inline char getEmptyKey() { return ~0; } static inline char getTombstoneKey() { return ~0 - 1; } static unsigned getHashValue(const char& Val) { return Val * 37U; } static bool isEqual(const char &LHS, const char &RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for unsigned ints. template<> struct DenseMapInfo { static inline unsigned getEmptyKey() { return ~0U; } static inline unsigned getTombstoneKey() { return ~0U - 1; } static unsigned getHashValue(const unsigned& Val) { return Val * 37U; } static bool isEqual(const unsigned& LHS, const unsigned& RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for unsigned longs. template<> struct DenseMapInfo { static inline unsigned long getEmptyKey() { return ~0UL; } static inline unsigned long getTombstoneKey() { return ~0UL - 1L; } static unsigned getHashValue(const unsigned long& Val) { return (unsigned)(Val * 37UL); } static bool isEqual(const unsigned long& LHS, const unsigned long& RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for unsigned long longs. template<> struct DenseMapInfo { static inline unsigned long long getEmptyKey() { return ~0ULL; } static inline unsigned long long getTombstoneKey() { return ~0ULL - 1ULL; } static unsigned getHashValue(const unsigned long long& Val) { return (unsigned)(Val * 37ULL); } static bool isEqual(const unsigned long long& LHS, const unsigned long long& RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for ints. template<> struct DenseMapInfo { static inline int getEmptyKey() { return 0x7fffffff; } static inline int getTombstoneKey() { return -0x7fffffff - 1; } static unsigned getHashValue(const int& Val) { return (unsigned)(Val * 37U); } static bool isEqual(const int& LHS, const int& RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for longs. template<> struct DenseMapInfo { static inline long getEmptyKey() { return (1UL << (sizeof(long) * 8 - 1)) - 1UL; } static inline long getTombstoneKey() { return getEmptyKey() - 1L; } static unsigned getHashValue(const long& Val) { return (unsigned)(Val * 37UL); } static bool isEqual(const long& LHS, const long& RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for long longs. template<> struct DenseMapInfo { static inline long long getEmptyKey() { return 0x7fffffffffffffffLL; } static inline long long getTombstoneKey() { return -0x7fffffffffffffffLL-1; } static unsigned getHashValue(const long long& Val) { return (unsigned)(Val * 37ULL); } static bool isEqual(const long long& LHS, const long long& RHS) { return LHS == RHS; } }; // Provide DenseMapInfo for all pairs whose members have info. template struct DenseMapInfo > { typedef std::pair Pair; typedef DenseMapInfo FirstInfo; typedef DenseMapInfo SecondInfo; static inline Pair getEmptyKey() { return std::make_pair(FirstInfo::getEmptyKey(), SecondInfo::getEmptyKey()); } static inline Pair getTombstoneKey() { return std::make_pair(FirstInfo::getTombstoneKey(), SecondInfo::getTombstoneKey()); } static unsigned getHashValue(const Pair& PairVal) { uint64_t key = (uint64_t)FirstInfo::getHashValue(PairVal.first) << 32 | (uint64_t)SecondInfo::getHashValue(PairVal.second); key += ~(key << 32); key ^= (key >> 22); key += ~(key << 13); key ^= (key >> 8); key += (key << 3); key ^= (key >> 15); key += ~(key << 27); key ^= (key >> 31); return (unsigned)key; } static bool isEqual(const Pair &LHS, const Pair &RHS) { return FirstInfo::isEqual(LHS.first, RHS.first) && SecondInfo::isEqual(LHS.second, RHS.second); } }; } // end namespace objc #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/llvm-MathExtras.h ================================================ //===-- llvm/Support/MathExtras.h - Useful math functions -------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file contains some functions that are useful for math stuff. // //===----------------------------------------------------------------------===// // Taken from llvmCore-3425.0.31. #ifndef LLVM_SUPPORT_MATHEXTRAS_H #define LLVM_SUPPORT_MATHEXTRAS_H namespace objc { // NOTE: The following support functions use the _32/_64 extensions instead of // type overloading so that signed and unsigned integers can be used without // ambiguity. /// Hi_32 - This function returns the high 32 bits of a 64 bit value. inline uint32_t Hi_32(uint64_t Value) { return static_cast(Value >> 32); } /// Lo_32 - This function returns the low 32 bits of a 64 bit value. inline uint32_t Lo_32(uint64_t Value) { return static_cast(Value); } /// isInt - Checks if an integer fits into the given bit width. template inline bool isInt(int64_t x) { return N >= 64 || (-(INT64_C(1)<<(N-1)) <= x && x < (INT64_C(1)<<(N-1))); } // Template specializations to get better code for common cases. template<> inline bool isInt<8>(int64_t x) { return static_cast(x) == x; } template<> inline bool isInt<16>(int64_t x) { return static_cast(x) == x; } template<> inline bool isInt<32>(int64_t x) { return static_cast(x) == x; } /// isShiftedInt - Checks if a signed integer is an N bit number shifted /// left by S. template inline bool isShiftedInt(int64_t x) { return isInt(x) && (x % (1< inline bool isUInt(uint64_t x) { return N >= 64 || x < (UINT64_C(1)< inline bool isUInt<8>(uint64_t x) { return static_cast(x) == x; } template<> inline bool isUInt<16>(uint64_t x) { return static_cast(x) == x; } template<> inline bool isUInt<32>(uint64_t x) { return static_cast(x) == x; } /// isShiftedUInt - Checks if a unsigned integer is an N bit number shifted /// left by S. template inline bool isShiftedUInt(uint64_t x) { return isUInt(x) && (x % (1<> (64 - N))); } /// isIntN - Checks if an signed integer fits into the given (dynamic) /// bit width. inline bool isIntN(unsigned N, int64_t x) { return N >= 64 || (-(INT64_C(1)<<(N-1)) <= x && x < (INT64_C(1)<<(N-1))); } /// isMask_32 - This function returns true if the argument is a sequence of ones /// starting at the least significant bit with the remainder zero (32 bit /// version). Ex. isMask_32(0x0000FFFFU) == true. inline bool isMask_32(uint32_t Value) { return Value && ((Value + 1) & Value) == 0; } /// isMask_64 - This function returns true if the argument is a sequence of ones /// starting at the least significant bit with the remainder zero (64 bit /// version). inline bool isMask_64(uint64_t Value) { return Value && ((Value + 1) & Value) == 0; } /// isShiftedMask_32 - This function returns true if the argument contains a /// sequence of ones with the remainder zero (32 bit version.) /// Ex. isShiftedMask_32(0x0000FF00U) == true. inline bool isShiftedMask_32(uint32_t Value) { return isMask_32((Value - 1) | Value); } /// isShiftedMask_64 - This function returns true if the argument contains a /// sequence of ones with the remainder zero (64 bit version.) inline bool isShiftedMask_64(uint64_t Value) { return isMask_64((Value - 1) | Value); } /// isPowerOf2_32 - This function returns true if the argument is a power of /// two > 0. Ex. isPowerOf2_32(0x00100000U) == true (32 bit edition.) inline bool isPowerOf2_32(uint32_t Value) { return Value && !(Value & (Value - 1)); } /// isPowerOf2_64 - This function returns true if the argument is a power of two /// > 0 (64 bit edition.) inline bool isPowerOf2_64(uint64_t Value) { return Value && !(Value & (Value - int64_t(1L))); } /// CountLeadingZeros_32 - this function performs the platform optimal form of /// counting the number of zeros from the most significant bit to the first one /// bit. Ex. CountLeadingZeros_32(0x00F000FF) == 8. /// Returns 32 if the word is zero. inline unsigned CountLeadingZeros_32(uint32_t Value) { unsigned Count; // result #if __GNUC__ >= 4 // PowerPC is defined for __builtin_clz(0) #if !defined(__ppc__) && !defined(__ppc64__) if (!Value) return 32; #endif Count = __builtin_clz(Value); #else if (!Value) return 32; Count = 0; // bisection method for count leading zeros for (unsigned Shift = 32 >> 1; Shift; Shift >>= 1) { uint32_t Tmp = Value >> Shift; if (Tmp) { Value = Tmp; } else { Count |= Shift; } } #endif return Count; } /// CountLeadingOnes_32 - this function performs the operation of /// counting the number of ones from the most significant bit to the first zero /// bit. Ex. CountLeadingOnes_32(0xFF0FFF00) == 8. /// Returns 32 if the word is all ones. inline unsigned CountLeadingOnes_32(uint32_t Value) { return CountLeadingZeros_32(~Value); } /// CountLeadingZeros_64 - This function performs the platform optimal form /// of counting the number of zeros from the most significant bit to the first /// one bit (64 bit edition.) /// Returns 64 if the word is zero. inline unsigned CountLeadingZeros_64(uint64_t Value) { unsigned Count; // result #if __GNUC__ >= 4 // PowerPC is defined for __builtin_clzll(0) #if !defined(__ppc__) && !defined(__ppc64__) if (!Value) return 64; #endif Count = __builtin_clzll(Value); #else if (sizeof(long) == sizeof(int64_t)) { if (!Value) return 64; Count = 0; // bisection method for count leading zeros for (unsigned Shift = 64 >> 1; Shift; Shift >>= 1) { uint64_t Tmp = Value >> Shift; if (Tmp) { Value = Tmp; } else { Count |= Shift; } } } else { // get hi portion uint32_t Hi = Hi_32(Value); // if some bits in hi portion if (Hi) { // leading zeros in hi portion plus all bits in lo portion Count = CountLeadingZeros_32(Hi); } else { // get lo portion uint32_t Lo = Lo_32(Value); // same as 32 bit value Count = CountLeadingZeros_32(Lo)+32; } } #endif return Count; } /// CountLeadingOnes_64 - This function performs the operation /// of counting the number of ones from the most significant bit to the first /// zero bit (64 bit edition.) /// Returns 64 if the word is all ones. inline unsigned CountLeadingOnes_64(uint64_t Value) { return CountLeadingZeros_64(~Value); } /// CountTrailingZeros_32 - this function performs the platform optimal form of /// counting the number of zeros from the least significant bit to the first one /// bit. Ex. CountTrailingZeros_32(0xFF00FF00) == 8. /// Returns 32 if the word is zero. inline unsigned CountTrailingZeros_32(uint32_t Value) { #if __GNUC__ >= 4 return Value ? __builtin_ctz(Value) : 32; #else static const unsigned Mod37BitPosition[] = { 32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4, 7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5, 20, 8, 19, 18 }; return Mod37BitPosition[(-Value & Value) % 37]; #endif } /// CountTrailingOnes_32 - this function performs the operation of /// counting the number of ones from the least significant bit to the first zero /// bit. Ex. CountTrailingOnes_32(0x00FF00FF) == 8. /// Returns 32 if the word is all ones. inline unsigned CountTrailingOnes_32(uint32_t Value) { return CountTrailingZeros_32(~Value); } /// CountTrailingZeros_64 - This function performs the platform optimal form /// of counting the number of zeros from the least significant bit to the first /// one bit (64 bit edition.) /// Returns 64 if the word is zero. inline unsigned CountTrailingZeros_64(uint64_t Value) { #if __GNUC__ >= 4 return Value ? __builtin_ctzll(Value) : 64; #else static const unsigned Mod67Position[] = { 64, 0, 1, 39, 2, 15, 40, 23, 3, 12, 16, 59, 41, 19, 24, 54, 4, 64, 13, 10, 17, 62, 60, 28, 42, 30, 20, 51, 25, 44, 55, 47, 5, 32, 65, 38, 14, 22, 11, 58, 18, 53, 63, 9, 61, 27, 29, 50, 43, 46, 31, 37, 21, 57, 52, 8, 26, 49, 45, 36, 56, 7, 48, 35, 6, 34, 33, 0 }; return Mod67Position[(-Value & Value) % 67]; #endif } /// CountTrailingOnes_64 - This function performs the operation /// of counting the number of ones from the least significant bit to the first /// zero bit (64 bit edition.) /// Returns 64 if the word is all ones. inline unsigned CountTrailingOnes_64(uint64_t Value) { return CountTrailingZeros_64(~Value); } /// CountPopulation_32 - this function counts the number of set bits in a value. /// Ex. CountPopulation(0xF000F000) = 8 /// Returns 0 if the word is zero. inline unsigned CountPopulation_32(uint32_t Value) { #if __GNUC__ >= 4 return __builtin_popcount(Value); #else uint32_t v = Value - ((Value >> 1) & 0x55555555); v = (v & 0x33333333) + ((v >> 2) & 0x33333333); return ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; #endif } /// CountPopulation_64 - this function counts the number of set bits in a value, /// (64 bit edition.) inline unsigned CountPopulation_64(uint64_t Value) { #if __GNUC__ >= 4 return __builtin_popcountll(Value); #else uint64_t v = Value - ((Value >> 1) & 0x5555555555555555ULL); v = (v & 0x3333333333333333ULL) + ((v >> 2) & 0x3333333333333333ULL); v = (v + (v >> 4)) & 0x0F0F0F0F0F0F0F0FULL; return unsigned((uint64_t)(v * 0x0101010101010101ULL) >> 56); #endif } /// Log2_32 - This function returns the floor log base 2 of the specified value, /// -1 if the value is zero. (32 bit edition.) /// Ex. Log2_32(32) == 5, Log2_32(1) == 0, Log2_32(0) == -1, Log2_32(6) == 2 inline unsigned Log2_32(uint32_t Value) { return 31 - CountLeadingZeros_32(Value); } /// Log2_64 - This function returns the floor log base 2 of the specified value, /// -1 if the value is zero. (64 bit edition.) inline unsigned Log2_64(uint64_t Value) { return 63 - CountLeadingZeros_64(Value); } /// Log2_32_Ceil - This function returns the ceil log base 2 of the specified /// value, 32 if the value is zero. (32 bit edition). /// Ex. Log2_32_Ceil(32) == 5, Log2_32_Ceil(1) == 0, Log2_32_Ceil(6) == 3 inline unsigned Log2_32_Ceil(uint32_t Value) { return 32-CountLeadingZeros_32(Value-1); } /// Log2_64_Ceil - This function returns the ceil log base 2 of the specified /// value, 64 if the value is zero. (64 bit edition.) inline unsigned Log2_64_Ceil(uint64_t Value) { return 64-CountLeadingZeros_64(Value-1); } /// GreatestCommonDivisor64 - Return the greatest common divisor of the two /// values using Euclid's algorithm. inline uint64_t GreatestCommonDivisor64(uint64_t A, uint64_t B) { while (B) { uint64_t T = B; B = A % B; A = T; } return A; } /// BitsToDouble - This function takes a 64-bit integer and returns the bit /// equivalent double. inline double BitsToDouble(uint64_t Bits) { union { uint64_t L; double D; } T; T.L = Bits; return T.D; } /// BitsToFloat - This function takes a 32-bit integer and returns the bit /// equivalent float. inline float BitsToFloat(uint32_t Bits) { union { uint32_t I; float F; } T; T.I = Bits; return T.F; } /// DoubleToBits - This function takes a double and returns the bit /// equivalent 64-bit integer. Note that copying doubles around /// changes the bits of NaNs on some hosts, notably x86, so this /// routine cannot be used if these bits are needed. inline uint64_t DoubleToBits(double Double) { union { uint64_t L; double D; } T; T.D = Double; return T.L; } /// FloatToBits - This function takes a float and returns the bit /// equivalent 32-bit integer. Note that copying floats around /// changes the bits of NaNs on some hosts, notably x86, so this /// routine cannot be used if these bits are needed. inline uint32_t FloatToBits(float Float) { union { uint32_t I; float F; } T; T.F = Float; return T.I; } /// Platform-independent wrappers for the C99 isnan() function. int IsNAN(float f); int IsNAN(double d); /// Platform-independent wrappers for the C99 isinf() function. int IsInf(float f); int IsInf(double d); /// MinAlign - A and B are either alignments or offsets. Return the minimum /// alignment that may be assumed after adding the two together. inline uint64_t MinAlign(uint64_t A, uint64_t B) { // The largest power of 2 that divides both A and B. return (A | B) & -(A | B); } /// NextPowerOf2 - Returns the next power of two (in 64-bits) /// that is strictly greater than A. Returns zero on overflow. inline uint64_t NextPowerOf2(uint64_t A) { A |= (A >> 1); A |= (A >> 2); A |= (A >> 4); A |= (A >> 8); A |= (A >> 16); A |= (A >> 32); return A + 1; } /// NextPowerOf2 - Returns the next power of two (in 32-bits) /// that is strictly greater than A. Returns zero on overflow. inline uint32_t NextPowerOf2(uint32_t A) { A |= (A >> 1); A |= (A >> 2); A |= (A >> 4); A |= (A >> 8); A |= (A >> 16); return A + 1; } /// Returns the next integer (mod 2**64) that is greater than or equal to /// \p Value and is a multiple of \p Align. \p Align must be non-zero. /// /// Examples: /// \code /// RoundUpToAlignment(5, 8) = 8 /// RoundUpToAlignment(17, 8) = 24 /// RoundUpToAlignment(~0LL, 8) = 0 /// \endcode inline uint64_t RoundUpToAlignment(uint64_t Value, uint64_t Align) { return ((Value + Align - 1) / Align) * Align; } /// Returns the offset to the next integer (mod 2**64) that is greater than /// or equal to \p Value and is a multiple of \p Align. \p Align must be /// non-zero. inline uint64_t OffsetToAlignment(uint64_t Value, uint64_t Align) { return RoundUpToAlignment(Value, Align) - Value; } /// abs64 - absolute value of a 64-bit int. Not all environments support /// "abs" on whatever their name for the 64-bit int type is. The absolute /// value of the largest negative number is undefined, as with "abs". inline int64_t abs64(int64_t x) { return (x < 0) ? -x : x; } /// SignExtend32 - Sign extend B-bit number x to 32-bit int. /// Usage int32_t r = SignExtend32<5>(x); template inline int32_t SignExtend32(uint32_t x) { return int32_t(x << (32 - B)) >> (32 - B); } /// \brief Sign extend number in the bottom B bits of X to a 32-bit int. /// Requires 0 < B <= 32. inline int32_t SignExtend32(uint32_t X, unsigned B) { return int32_t(X << (32 - B)) >> (32 - B); } /// SignExtend64 - Sign extend B-bit number x to 64-bit int. /// Usage int64_t r = SignExtend64<5>(x); template inline int64_t SignExtend64(uint64_t x) { return int64_t(x << (64 - B)) >> (64 - B); } /// \brief Sign extend number in the bottom B bits of X to a 64-bit int. /// Requires 0 < B <= 64. inline int64_t SignExtend64(uint64_t X, unsigned B) { return int64_t(X << (64 - B)) >> (64 - B); } } // End llvm namespace #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/llvm-type_traits.h ================================================ //===- llvm/Support/type_traits.h - Simplfied type traits -------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file provides a template class that determines if a type is a class or // not. The basic mechanism, based on using the pointer to member function of // a zero argument to a function was "boosted" from the boost type_traits // library. See http://www.boost.org/ for all the gory details. // //===----------------------------------------------------------------------===// // Taken from llvmCore-3425.0.31. #ifndef LLVM_SUPPORT_TYPE_TRAITS_H #define LLVM_SUPPORT_TYPE_TRAITS_H #include #include #ifndef __has_feature #define LLVM_DEFINED_HAS_FEATURE #define __has_feature(x) 0 #endif // This is actually the conforming implementation which works with abstract // classes. However, enough compilers have trouble with it that most will use // the one in boost/type_traits/object_traits.hpp. This implementation actually // works with VC7.0, but other interactions seem to fail when we use it. namespace objc { namespace dont_use { // These two functions should never be used. They are helpers to // the is_class template below. They cannot be located inside // is_class because doing so causes at least GCC to think that // the value of the "value" enumerator is not constant. Placing // them out here (for some strange reason) allows the sizeof // operator against them to magically be constant. This is // important to make the is_class::value idiom zero cost. it // evaluates to a constant 1 or 0 depending on whether the // parameter T is a class or not (respectively). template char is_class_helper(void(T::*)()); template double is_class_helper(...); } template struct is_class { // is_class<> metafunction due to Paul Mensonides (leavings@attbi.com). For // more details: // http://groups.google.com/groups?hl=en&selm=000001c1cc83%24e154d5e0%247772e50c%40c161550a&rnum=1 public: static const bool value = sizeof(char) == sizeof(dont_use::is_class_helper(0)); }; /// isPodLike - This is a type trait that is used to determine whether a given /// type can be copied around with memcpy instead of running ctors etc. template struct isPodLike { #if __has_feature(is_trivially_copyable) // If the compiler supports the is_trivially_copyable trait use it, as it // matches the definition of isPodLike closely. static const bool value = __is_trivially_copyable(T); #else // If we don't know anything else, we can (at least) assume that all non-class // types are PODs. static const bool value = !is_class::value; #endif }; // std::pair's are pod-like if their elements are. template struct isPodLike > { static const bool value = isPodLike::value && isPodLike::value; }; template struct integral_constant { typedef T value_type; static const value_type value = v; typedef integral_constant type; operator value_type() { return value; } }; typedef integral_constant true_type; typedef integral_constant false_type; /// \brief Metafunction that determines whether the two given types are /// equivalent. template struct is_same : public false_type {}; template struct is_same : public true_type {}; /// \brief Metafunction that removes const qualification from a type. template struct remove_const { typedef T type; }; template struct remove_const { typedef T type; }; /// \brief Metafunction that removes volatile qualification from a type. template struct remove_volatile { typedef T type; }; template struct remove_volatile { typedef T type; }; /// \brief Metafunction that removes both const and volatile qualification from /// a type. template struct remove_cv { typedef typename remove_const::type>::type type; }; /// \brief Helper to implement is_integral metafunction. template struct is_integral_impl : false_type {}; template <> struct is_integral_impl< bool> : true_type {}; template <> struct is_integral_impl< char> : true_type {}; template <> struct is_integral_impl< signed char> : true_type {}; template <> struct is_integral_impl : true_type {}; template <> struct is_integral_impl< wchar_t> : true_type {}; template <> struct is_integral_impl< short> : true_type {}; template <> struct is_integral_impl : true_type {}; template <> struct is_integral_impl< int> : true_type {}; template <> struct is_integral_impl : true_type {}; template <> struct is_integral_impl< long> : true_type {}; template <> struct is_integral_impl : true_type {}; template <> struct is_integral_impl< long long> : true_type {}; template <> struct is_integral_impl : true_type {}; /// \brief Metafunction that determines whether the given type is an integral /// type. template struct is_integral : is_integral_impl {}; /// \brief Metafunction to remove reference from a type. template struct remove_reference { typedef T type; }; template struct remove_reference { typedef T type; }; /// \brief Metafunction that determines whether the given type is a pointer /// type. template struct is_pointer : false_type {}; template struct is_pointer : true_type {}; template struct is_pointer : true_type {}; template struct is_pointer : true_type {}; template struct is_pointer : true_type {}; /// \brief Metafunction that determines whether the given type is either an /// integral type or an enumeration type. /// /// Note that this accepts potentially more integral types than we whitelist /// above for is_integral because it is based on merely being convertible /// implicitly to an integral type. template class is_integral_or_enum { // Provide an overload which can be called with anything implicitly // convertible to an unsigned long long. This should catch integer types and // enumeration types at least. We blacklist classes with conversion operators // below. static double check_int_convertible(unsigned long long); static char check_int_convertible(...); typedef typename remove_reference::type UnderlyingT; static UnderlyingT &nonce_instance; public: static const bool value = (!is_class::value && !is_pointer::value && !is_same::value && !is_same::value && sizeof(char) != sizeof(check_int_convertible(nonce_instance))); }; // enable_if_c - Enable/disable a template based on a metafunction template struct enable_if_c { typedef T type; }; template struct enable_if_c { }; // enable_if - Enable/disable a template based on a metafunction template struct enable_if : public enable_if_c { }; namespace dont_use { template char base_of_helper(const volatile Base*); template double base_of_helper(...); } /// is_base_of - Metafunction to determine whether one type is a base class of /// (or identical to) another type. template struct is_base_of { static const bool value = is_class::value && is_class::value && sizeof(char) == sizeof(dont_use::base_of_helper((Derived*)0)); }; // remove_pointer - Metafunction to turn Foo* into Foo. Defined in // C++0x [meta.trans.ptr]. template struct remove_pointer { typedef T type; }; template struct remove_pointer { typedef T type; }; template struct remove_pointer { typedef T type; }; template struct remove_pointer { typedef T type; }; template struct remove_pointer { typedef T type; }; template struct conditional { typedef T type; }; template struct conditional { typedef F type; }; } #ifdef LLVM_DEFINED_HAS_FEATURE #undef __has_feature #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/maptable.h ================================================ /* * Copyright (c) 1999-2003, 2006-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* maptable.h Scalable hash table of mappings. Bertrand, August 1990 Copyright 1990-1996 NeXT Software, Inc. */ #ifndef _OBJC_MAPTABLE_H_ #define _OBJC_MAPTABLE_H_ #ifndef _OBJC_PRIVATE_H_ # define OBJC_MAP_AVAILABILITY \ __OSX_DEPRECATED(10.0, 10.1, "NXMapTable is deprecated") \ __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE #else # define OBJC_MAP_AVAILABILITY #endif #include __BEGIN_DECLS /*************** Definitions ***************/ /* This module allows hashing of arbitrary associations [key -> value]. Keys and values must be pointers or integers, and client is responsible for allocating/deallocating this data. A deallocation call-back is provided. NX_MAPNOTAKEY (-1) is used internally as a marker, and therefore keys must always be different from -1. As well-behaved scalable data structures, hash tables double in size when they start becoming full, thus guaranteeing both average constant time access and linear size. */ typedef struct _NXMapTable { /* private data structure; may change */ const struct _NXMapTablePrototype * _Nonnull prototype; unsigned count; unsigned nbBucketsMinusOne; void * _Nullable buckets; } NXMapTable OBJC_MAP_AVAILABILITY; typedef struct _NXMapTablePrototype { unsigned (* _Nonnull hash)(NXMapTable * _Nonnull, const void * _Nullable key); int (* _Nonnull isEqual)(NXMapTable * _Nonnull, const void * _Nullable key1, const void * _Nullable key2); void (* _Nonnull free)(NXMapTable * _Nonnull, void * _Nullable key, void * _Nullable value); int style; /* reserved for future expansion; currently 0 */ } NXMapTablePrototype OBJC_MAP_AVAILABILITY; /* invariants assumed by the implementation: A - key != -1 B - key1 == key2 => hash(key1) == hash(key2) when key varies over time, hash(key) must remain invariant e.g. if string key, the string must not be changed C - isEqual(key1, key2) => key1 == key2 */ #define NX_MAPNOTAKEY ((void * _Nonnull)(-1)) /*************** Functions ***************/ OBJC_EXPORT NXMapTable * _Nonnull NXCreateMapTableFromZone(NXMapTablePrototype prototype, unsigned capacity, void * _Nullable z) OBJC_MAP_AVAILABILITY; OBJC_EXPORT NXMapTable * _Nonnull NXCreateMapTable(NXMapTablePrototype prototype, unsigned capacity) OBJC_MAP_AVAILABILITY; /* capacity is only a hint; 0 creates a small table */ OBJC_EXPORT void NXFreeMapTable(NXMapTable * _Nonnull table) OBJC_MAP_AVAILABILITY; /* call free for each pair, and recovers table */ OBJC_EXPORT void NXResetMapTable(NXMapTable * _Nonnull table) OBJC_MAP_AVAILABILITY; /* free each pair; keep current capacity */ OBJC_EXPORT BOOL NXCompareMapTables(NXMapTable * _Nonnull table1, NXMapTable * _Nonnull table2) OBJC_MAP_AVAILABILITY; /* Returns YES if the two sets are equal (each member of table1 in table2, and table have same size) */ OBJC_EXPORT unsigned NXCountMapTable(NXMapTable * _Nonnull table) OBJC_MAP_AVAILABILITY; /* current number of data in table */ OBJC_EXPORT void * _Nullable NXMapMember(NXMapTable * _Nonnull table, const void * _Nullable key, void * _Nullable * _Nonnull value) OBJC_MAP_AVAILABILITY; /* return original table key or NX_MAPNOTAKEY. If key is found, value is set */ OBJC_EXPORT void * _Nullable NXMapGet(NXMapTable * _Nonnull table, const void * _Nullable key) OBJC_MAP_AVAILABILITY; /* return original corresponding value or NULL. When NULL need be stored as value, NXMapMember can be used to test for presence */ OBJC_EXPORT void * _Nullable NXMapInsert(NXMapTable * _Nonnull table, const void * _Nullable key, const void * _Nullable value) OBJC_MAP_AVAILABILITY; /* override preexisting pair; Return previous value or NULL. */ OBJC_EXPORT void * _Nullable NXMapRemove(NXMapTable * _Nonnull table, const void * _Nullable key) OBJC_MAP_AVAILABILITY; /* previous value or NULL is returned */ /* Iteration over all elements of a table consists in setting up an iteration state and then to progress until all entries have been visited. An example of use for counting elements in a table is: unsigned count = 0; const MyKey *key; const MyValue *value; NXMapState state = NXInitMapState(table); while(NXNextMapState(table, &state, &key, &value)) { count++; } */ typedef struct {int index;} NXMapState OBJC_MAP_AVAILABILITY; /* callers should not rely on actual contents of the struct */ OBJC_EXPORT NXMapState NXInitMapState(NXMapTable * _Nonnull table) OBJC_MAP_AVAILABILITY; OBJC_EXPORT int NXNextMapState(NXMapTable * _Nonnull table, NXMapState * _Nonnull state, const void * _Nullable * _Nonnull key, const void * _Nullable * _Nonnull value) OBJC_MAP_AVAILABILITY; /* returns 0 when all elements have been visited */ /*************** Conveniences ***************/ OBJC_EXPORT const NXMapTablePrototype NXPtrValueMapPrototype OBJC_MAP_AVAILABILITY; /* hashing is pointer/integer hashing; isEqual is identity; free is no-op. */ OBJC_EXPORT const NXMapTablePrototype NXStrValueMapPrototype OBJC_MAP_AVAILABILITY; /* hashing is string hashing; isEqual is strcmp; free is no-op. */ OBJC_EXPORT const NXMapTablePrototype NXObjectMapPrototype OBJC2_UNAVAILABLE; /* for objects; uses methods: hash, isEqual:, free, all for key. */ __END_DECLS #endif /* _OBJC_MAPTABLE_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/maptable.mm ================================================ /* * Copyright (c) 1999-2003, 2005-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* maptable.m Copyright 1990-1996 NeXT Software, Inc. Created by Bertrand Serlet, August 1990 */ #include #include #include #include "objc-private.h" #include "maptable.h" #include "hashtable2.h" /****** Macros and utilities ****************************/ #if defined(DEBUG) #define INLINE #else #define INLINE inline #endif typedef struct _MapPair { const void *key; const void *value; } MapPair; static INLINE unsigned xorHash(unsigned hash) { unsigned xored = (hash & 0xffff) ^ (hash >> 16); return ((xored * 65521) + hash); } static INLINE unsigned bucketOf(NXMapTable *table, const void *key) { unsigned hash = (table->prototype->hash)(table, key); return hash & table->nbBucketsMinusOne; } static INLINE int isEqual(NXMapTable *table, const void *key1, const void *key2) { return (key1 == key2) ? 1 : (table->prototype->isEqual)(table, key1, key2); } static INLINE unsigned nextIndex(NXMapTable *table, unsigned index) { return (index + 1) & table->nbBucketsMinusOne; } static INLINE void *allocBuckets(void *z, unsigned nb) { MapPair *pairs = 1+(MapPair *)malloc_zone_malloc((malloc_zone_t *)z, ((nb+1) * sizeof(MapPair))); MapPair *pair = pairs; while (nb--) { pair->key = NX_MAPNOTAKEY; pair->value = NULL; pair++; } return pairs; } static INLINE void freeBuckets(void *p) { free(-1+(MapPair *)p); } /***** Global data and bootstrap **********************/ static int isEqualPrototype (const void *info, const void *data1, const void *data2) { NXHashTablePrototype *proto1 = (NXHashTablePrototype *) data1; NXHashTablePrototype *proto2 = (NXHashTablePrototype *) data2; return (proto1->hash == proto2->hash) && (proto1->isEqual == proto2->isEqual) && (proto1->free == proto2->free) && (proto1->style == proto2->style); }; static uintptr_t hashPrototype (const void *info, const void *data) { NXHashTablePrototype *proto = (NXHashTablePrototype *) data; return NXPtrHash(info, (void*)proto->hash) ^ NXPtrHash(info, (void*)proto->isEqual) ^ NXPtrHash(info, (void*)proto->free) ^ (uintptr_t) proto->style; }; static NXHashTablePrototype protoPrototype = { hashPrototype, isEqualPrototype, NXNoEffectFree, 0 }; static NXHashTable *prototypes = NULL; /* table of all prototypes */ /**** Fundamentals Operations **************/ NXMapTable *NXCreateMapTableFromZone(NXMapTablePrototype prototype, unsigned capacity, void *z) { NXMapTable *table = (NXMapTable *)malloc_zone_malloc((malloc_zone_t *)z, sizeof(NXMapTable)); NXMapTablePrototype *proto; if (! prototypes) prototypes = NXCreateHashTable(protoPrototype, 0, NULL); if (! prototype.hash || ! prototype.isEqual || ! prototype.free || prototype.style) { _objc_inform("*** NXCreateMapTable: invalid creation parameters\n"); return NULL; } proto = (NXMapTablePrototype *)NXHashGet(prototypes, &prototype); if (! proto) { proto = (NXMapTablePrototype *)malloc(sizeof(NXMapTablePrototype)); *proto = prototype; (void)NXHashInsert(prototypes, proto); } table->prototype = proto; table->count = 0; table->nbBucketsMinusOne = exp2u(log2u(capacity)+1) - 1; table->buckets = allocBuckets(z, table->nbBucketsMinusOne + 1); return table; } NXMapTable *NXCreateMapTable(NXMapTablePrototype prototype, unsigned capacity) { return NXCreateMapTableFromZone(prototype, capacity, malloc_default_zone()); } void NXFreeMapTable(NXMapTable *table) { NXResetMapTable(table); freeBuckets(table->buckets); free(table); } void NXResetMapTable(NXMapTable *table) { MapPair *pairs = (MapPair *)table->buckets; void (*freeProc)(struct _NXMapTable *, void *, void *) = table->prototype->free; unsigned index = table->nbBucketsMinusOne + 1; while (index--) { if (pairs->key != NX_MAPNOTAKEY) { freeProc(table, (void *)pairs->key, (void *)pairs->value); pairs->key = NX_MAPNOTAKEY; pairs->value = NULL; } pairs++; } table->count = 0; } BOOL NXCompareMapTables(NXMapTable *table1, NXMapTable *table2) { if (table1 == table2) return YES; if (table1->count != table2->count) return NO; else { const void *key; const void *value; NXMapState state = NXInitMapState(table1); while (NXNextMapState(table1, &state, &key, &value)) { if (NXMapMember(table2, key, (void**)&value) == NX_MAPNOTAKEY) return NO; } return YES; } } unsigned NXCountMapTable(NXMapTable *table) { return table->count; } #if __x86_64__ extern "C" void __NXMAPTABLE_CORRUPTED__ (const void *table, const void *buckets, uint64_t count, uint64_t nbBucketsMinusOne, uint64_t badkeys, uint64_t index, uint64_t index2, uint64_t pairIndexes, const void *key1, const void *value1, const void *key2, const void *value2, const void *key3, const void *value3); static int _mapStrIsEqual(NXMapTable *table, const void *key1, const void *key2); asm("\n .text" "\n .private_extern ___NXMAPTABLE_CORRUPTED__" "\n ___NXMAPTABLE_CORRUPTED__:" // push a frame for the unwinder to see "\n pushq %rbp" "\n mov %rsp, %rbp" // push register parameters to the stack in reverse order "\n pushq %r9" "\n pushq %r8" "\n pushq %rcx" "\n pushq %rdx" "\n pushq %rsi" "\n pushq %rdi" // pop the pushed register parameters into their destinations "\n popq %rax" // table "\n popq %rbx" // buckets "\n popq %rcx" // count "\n popq %rdx" // nbBucketsMinusOne "\n popq %rdi" // badkeys "\n popq %rsi" // index // read stack parameters into their destinations "\n mov 0*8+16(%rbp), %r8" // index2 "\n mov 1*8+16(%rbp), %r9" // pairIndexes "\n mov 2*8+16(%rbp), %r10" // key1 "\n mov 3*8+16(%rbp), %r11" // value1 "\n mov 4*8+16(%rbp), %r12" // key2 "\n mov 5*8+16(%rbp), %r13" // value2 "\n mov 6*8+16(%rbp), %r14" // key3 "\n mov 7*8+16(%rbp), %r15" // value3 "\n ud2"); #endif // Look for a particular case of data corruption (rdar://36373000) // and investigate it further before crashing. static void validateKey(NXMapTable *table, MapPair *pair, unsigned index, unsigned index2) { #if __x86_64__ # define BADKEY ((void * _Nonnull)(0xfffffffffffffffeULL)) if (pair->key != BADKEY || table->prototype->isEqual != _mapStrIsEqual) { return; } _objc_inform_now_and_on_crash ("NXMapTable %p (%p) has invalid key/value pair %p->%p (%p)", table, table->buckets, pair->key, pair->value, pair); _objc_inform_now_and_on_crash ("table %p, buckets %p, count %u, nbNucketsMinusOne %u, " "prototype %p (hash %p, isEqual %p, free %p)", table, table->buckets, table->count, table->nbBucketsMinusOne, table->prototype, table->prototype->hash, table->prototype->isEqual, table->prototype->free); // Count the number of bad keys in the table. MapPair *pairs = (MapPair *)table->buckets; unsigned badKeys = 0; for (unsigned i = 0; i < table->nbBucketsMinusOne+1; i++) { if (pairs[i].key == BADKEY) badKeys++; } _objc_inform_now_and_on_crash("%u invalid keys in table", badKeys); // Record some additional key pairs for posterity. unsigned pair2Index = nextIndex(table, index); unsigned pair3Index = nextIndex(table, pair2Index); MapPair *pair2 = pairs + pair2Index; MapPair *pair3 = pairs + pair3Index; uint64_t pairIndexes = ((uint64_t)pair2Index << 32) | pair3Index; // Save a bunch of values to registers so we can see them in the crash log. __NXMAPTABLE_CORRUPTED__ (// rax, rbx, rcx, rdx table, table->buckets, table->count, table->nbBucketsMinusOne, // rdi, rsi, skip rbp, skip rsp badKeys, index, // r8, r9, r10, r11 index2, pairIndexes, pair->key, pair->value, // r12, r13, r14, r15 pair2->key, pair2->value, pair3->key, pair3->value); #endif } static INLINE void *_NXMapMember(NXMapTable *table, const void *key, void **value) { MapPair *pairs = (MapPair *)table->buckets; unsigned index = bucketOf(table, key); MapPair *pair = pairs + index; if (pair->key == NX_MAPNOTAKEY) return NX_MAPNOTAKEY; validateKey(table, pair, index, index); if (isEqual(table, pair->key, key)) { *value = (void *)pair->value; return (void *)pair->key; } else { unsigned index2 = index; while ((index2 = nextIndex(table, index2)) != index) { pair = pairs + index2; if (pair->key == NX_MAPNOTAKEY) return NX_MAPNOTAKEY; validateKey(table, pair, index, index2); if (isEqual(table, pair->key, key)) { *value = (void *)pair->value; return (void *)pair->key; } } return NX_MAPNOTAKEY; } } void *NXMapMember(NXMapTable *table, const void *key, void **value) { return _NXMapMember(table, key, value); } void *NXMapGet(NXMapTable *table, const void *key) { void *value; return (_NXMapMember(table, key, &value) != NX_MAPNOTAKEY) ? value : NULL; } static void _NXMapRehash(NXMapTable *table) { MapPair *pairs = (MapPair *)table->buckets; MapPair *pair = pairs; unsigned numBuckets = table->nbBucketsMinusOne + 1; unsigned index = numBuckets; unsigned oldCount = table->count; table->nbBucketsMinusOne = 2 * numBuckets - 1; table->count = 0; table->buckets = allocBuckets(malloc_zone_from_ptr(table), table->nbBucketsMinusOne + 1); while (index--) { if (pair->key != NX_MAPNOTAKEY) { (void)NXMapInsert(table, pair->key, pair->value); } pair++; } if (oldCount != table->count) _objc_inform("*** maptable: count differs after rehashing; probably indicates a broken invariant: there are x and y such as isEqual(x, y) is TRUE but hash(x) != hash (y)\n"); freeBuckets(pairs); } void *NXMapInsert(NXMapTable *table, const void *key, const void *value) { MapPair *pairs = (MapPair *)table->buckets; unsigned index = bucketOf(table, key); MapPair *pair = pairs + index; if (key == NX_MAPNOTAKEY) { _objc_inform("*** NXMapInsert: invalid key: -1\n"); return NULL; } unsigned numBuckets = table->nbBucketsMinusOne + 1; if (pair->key == NX_MAPNOTAKEY) { pair->key = key; pair->value = value; table->count++; if (table->count * 4 > numBuckets * 3) _NXMapRehash(table); return NULL; } if (isEqual(table, pair->key, key)) { const void *old = pair->value; if (old != value) pair->value = value;/* avoid writing unless needed! */ return (void *)old; } else if (table->count == numBuckets) { /* no room: rehash and retry */ _NXMapRehash(table); return NXMapInsert(table, key, value); } else { unsigned index2 = index; while ((index2 = nextIndex(table, index2)) != index) { pair = pairs + index2; if (pair->key == NX_MAPNOTAKEY) { pair->key = key; pair->value = value; table->count++; if (table->count * 4 > numBuckets * 3) _NXMapRehash(table); return NULL; } if (isEqual(table, pair->key, key)) { const void *old = pair->value; if (old != value) pair->value = value;/* avoid writing unless needed! */ return (void *)old; } } /* no room: can't happen! */ _objc_inform("**** NXMapInsert: bug\n"); return NULL; } } static int mapRemove = 0; void *NXMapRemove(NXMapTable *table, const void *key) { MapPair *pairs = (MapPair *)table->buckets; unsigned index = bucketOf(table, key); MapPair *pair = pairs + index; unsigned chain = 1; /* number of non-nil pairs in a row */ int found = 0; const void *old = NULL; if (pair->key == NX_MAPNOTAKEY) return NULL; mapRemove ++; /* compute chain */ { unsigned index2 = index; if (isEqual(table, pair->key, key)) {found ++; old = pair->value; } while ((index2 = nextIndex(table, index2)) != index) { pair = pairs + index2; if (pair->key == NX_MAPNOTAKEY) break; if (isEqual(table, pair->key, key)) {found ++; old = pair->value; } chain++; } } if (! found) return NULL; if (found != 1) _objc_inform("**** NXMapRemove: incorrect table\n"); /* remove then reinsert */ { MapPair buffer[16]; MapPair *aux = (chain > 16) ? (MapPair *)malloc(sizeof(MapPair)*(chain-1)) : buffer; unsigned auxnb = 0; int nb = chain; unsigned index2 = index; while (nb--) { pair = pairs + index2; if (! isEqual(table, pair->key, key)) aux[auxnb++] = *pair; pair->key = NX_MAPNOTAKEY; pair->value = NULL; index2 = nextIndex(table, index2); } table->count -= chain; if (auxnb != chain-1) _objc_inform("**** NXMapRemove: bug\n"); while (auxnb--) NXMapInsert(table, aux[auxnb].key, aux[auxnb].value); if (chain > 16) free(aux); } return (void *)old; } NXMapState NXInitMapState(NXMapTable *table) { NXMapState state; state.index = table->nbBucketsMinusOne + 1; return state; } int NXNextMapState(NXMapTable *table, NXMapState *state, const void **key, const void **value) { MapPair *pairs = (MapPair *)table->buckets; while (state->index--) { MapPair *pair = pairs + state->index; if (pair->key != NX_MAPNOTAKEY) { *key = pair->key; *value = pair->value; return YES; } } return NO; } /*********************************************************************** * NXMapKeyCopyingInsert * Like NXMapInsert, but strdups the key if necessary. * Used to prevent stale pointers when bundles are unloaded. **********************************************************************/ void *NXMapKeyCopyingInsert(NXMapTable *table, const void *key, const void *value) { void *realKey; void *realValue = NULL; if ((realKey = NXMapMember(table, key, &realValue)) != NX_MAPNOTAKEY) { // key DOES exist in table - use table's key for insertion } else { // key DOES NOT exist in table - copy the new key before insertion realKey = (void *)strdupIfMutable((char *)key); } return NXMapInsert(table, realKey, value); } /*********************************************************************** * NXMapKeyFreeingRemove * Like NXMapRemove, but frees the existing key if necessary. * Used to prevent stale pointers when bundles are unloaded. **********************************************************************/ void *NXMapKeyFreeingRemove(NXMapTable *table, const void *key) { void *realKey; void *realValue = NULL; if ((realKey = NXMapMember(table, key, &realValue)) != NX_MAPNOTAKEY) { // key DOES exist in table - remove pair and free key realValue = NXMapRemove(table, realKey); // free the key from the table, not necessarily the one given freeIfMutable((char *)realKey); return realValue; } else { // key DOES NOT exist in table - nothing to do return NULL; } } /**** Conveniences *************************************/ static unsigned _mapPtrHash(NXMapTable *table, const void *key) { #ifdef __LP64__ return (unsigned)(((uintptr_t)key) >> 3); #else return ((uintptr_t)key) >> 2; #endif } static unsigned _mapStrHash(NXMapTable *table, const void *key) { unsigned hash = 0; unsigned char *s = (unsigned char *)key; /* unsigned to avoid a sign-extend */ /* unroll the loop */ if (s) for (; ; ) { if (*s == '\0') break; hash ^= *s++; if (*s == '\0') break; hash ^= *s++ << 8; if (*s == '\0') break; hash ^= *s++ << 16; if (*s == '\0') break; hash ^= *s++ << 24; } return xorHash(hash); } static int _mapPtrIsEqual(NXMapTable *table, const void *key1, const void *key2) { return key1 == key2; } static int _mapStrIsEqual(NXMapTable *table, const void *key1, const void *key2) { if (key1 == key2) return YES; if (! key1) return ! strlen ((char *) key2); if (! key2) return ! strlen ((char *) key1); if (((char *) key1)[0] != ((char *) key2)[0]) return NO; return (strcmp((char *) key1, (char *) key2)) ? NO : YES; } static void _mapNoFree(NXMapTable *table, void *key, void *value) {} const NXMapTablePrototype NXPtrValueMapPrototype = { _mapPtrHash, _mapPtrIsEqual, _mapNoFree, 0 }; const NXMapTablePrototype NXStrValueMapPrototype = { _mapStrHash, _mapStrIsEqual, _mapNoFree, 0 }; #if !__OBJC2__ && !TARGET_OS_WIN32 /* This only works with class Object, which is unavailable. */ /* Method prototypes */ @interface DoesNotExist + (id)class; + (id)initialize; - (id)description; - (const char *)UTF8String; - (unsigned long)hash; - (BOOL)isEqual:(id)object; - (void)free; @end static unsigned _mapObjectHash(NXMapTable *table, const void *key) { return [(id)key hash]; } static int _mapObjectIsEqual(NXMapTable *table, const void *key1, const void *key2) { return [(id)key1 isEqual:(id)key2]; } static void _mapObjectFree(NXMapTable *table, void *key, void *value) { [(id)key free]; } const NXMapTablePrototype NXObjectMapPrototype = { _mapObjectHash, _mapObjectIsEqual, _mapObjectFree, 0 }; #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/message.h ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_MESSAGE_H #define _OBJC_MESSAGE_H #include #include #ifndef OBJC_SUPER #define OBJC_SUPER /// Specifies the superclass of an instance. struct objc_super { /// Specifies an instance of a class. __unsafe_unretained _Nonnull id receiver; /// Specifies the particular superclass of the instance to message. #if !defined(__cplusplus) && !__OBJC2__ /* For compatibility with old objc-runtime.h header */ __unsafe_unretained _Nonnull Class class; #else __unsafe_unretained _Nonnull Class super_class; #endif /* super_class is the first class to search */ }; #endif /* Basic Messaging Primitives * * On some architectures, use objc_msgSend_stret for some struct return types. * On some architectures, use objc_msgSend_fpret for some float return types. * On some architectures, use objc_msgSend_fp2ret for some float return types. * * These functions must be cast to an appropriate function pointer type * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); #else /** * Sends a message with a simple return value to an instance of a class. * * @param self A pointer to the instance of the class that is to receive the message. * @param op The selector of the method that handles the message. * @param ... * A variable argument list containing the arguments to the method. * * @return The return value of the method. * * @note When it encounters a method call, the compiler generates a call to one of the * functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret. * Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; * other messages are sent using \c objc_msgSend. Methods that have data structures as return values * are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret. */ OBJC_EXPORT id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Sends a message with a simple return value to the superclass of an instance of a class. * * @param super A pointer to an \c objc_super data structure. Pass values identifying the * context the message was sent to, including the instance of the class that is to receive the * message and the superclass at which to start searching for the method implementation. * @param op A pointer of type SEL. Pass the selector of the method that will handle the message. * @param ... * A variable argument list containing the arguments to the method. * * @return The return value of the method identified by \e op. * * @see objc_msgSend */ OBJC_EXPORT id _Nullable objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); #endif /* Struct-returning Messaging Primitives * * Use these functions to call methods that return structs on the stack. * On some architectures, some structures are returned in registers. * Consult your local function call ABI documentation for details. * * These functions must be cast to an appropriate function pointer type * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES OBJC_EXPORT void objc_msgSend_stret(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; OBJC_EXPORT void objc_msgSendSuper_stret(void /* struct objc_super *super, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #else /** * Sends a message with a data-structure return value to an instance of a class. * * @see objc_msgSend */ OBJC_EXPORT void objc_msgSend_stret(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; /** * Sends a message with a data-structure return value to the superclass of an instance of a class. * * @see objc_msgSendSuper */ OBJC_EXPORT void objc_msgSendSuper_stret(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #endif /* Floating-point-returning Messaging Primitives * * Use these functions to call methods that return floating-point values * on the stack. * Consult your local function call ABI documentation for details. * * arm: objc_msgSend_fpret not used * i386: objc_msgSend_fpret used for `float`, `double`, `long double`. * x86-64: objc_msgSend_fpret used for `long double`. * * arm: objc_msgSend_fp2ret not used * i386: objc_msgSend_fp2ret not used * x86-64: objc_msgSend_fp2ret used for `_Complex long double`. * * These functions must be cast to an appropriate function pointer type * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES # if defined(__i386__) OBJC_EXPORT void objc_msgSend_fpret(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0, 2.0); # elif defined(__x86_64__) OBJC_EXPORT void objc_msgSend_fpret(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_msgSend_fp2ret(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); # endif // !OBJC_OLD_DISPATCH_PROTOTYPES #else // OBJC_OLD_DISPATCH_PROTOTYPES # if defined(__i386__) /** * Sends a message with a floating-point return value to an instance of a class. * * @see objc_msgSend * @note On the i386 platform, the ABI for functions returning a floating-point value is * incompatible with that for functions returning an integral type. On the i386 platform, therefore, * you must use \c objc_msgSend_fpret for functions returning non-integral type. For \c float or * \c long \c double return types, cast the function to an appropriate function pointer type first. */ OBJC_EXPORT double objc_msgSend_fpret(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0, 2.0); /* Use objc_msgSendSuper() for fp-returning messages to super. */ /* See also objc_msgSendv_fpret() below. */ # elif defined(__x86_64__) /** * Sends a message with a floating-point return value to an instance of a class. * * @see objc_msgSend */ OBJC_EXPORT long double objc_msgSend_fpret(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); # if __STDC_VERSION__ >= 199901L OBJC_EXPORT _Complex long double objc_msgSend_fp2ret(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); # else OBJC_EXPORT void objc_msgSend_fp2ret(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); # endif /* Use objc_msgSendSuper() for fp-returning messages to super. */ /* See also objc_msgSendv_fpret() below. */ # endif // OBJC_OLD_DISPATCH_PROTOTYPES #endif /* Direct Method Invocation Primitives * Use these functions to call the implementation of a given Method. * This is faster than calling method_getImplementation() and method_getName(). * * The receiver must not be nil. * * These functions must be cast to an appropriate function pointer type * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES OBJC_EXPORT void method_invoke(void /* id receiver, Method m, ... */ ) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void method_invoke_stret(void /* id receiver, Method m, ... */ ) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #else OBJC_EXPORT id _Nullable method_invoke(id _Nullable receiver, Method _Nonnull m, ...) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void method_invoke_stret(id _Nullable receiver, Method _Nonnull m, ...) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #endif /* Message Forwarding Primitives * Use these functions to forward a message as if the receiver did not * respond to it. * * The receiver must not be nil. * * class_getMethodImplementation() may return (IMP)_objc_msgForward. * class_getMethodImplementation_stret() may return (IMP)_objc_msgForward_stret * * These functions must be cast to an appropriate function pointer type * before being called. * * Before Mac OS X 10.6, _objc_msgForward must not be called directly * but may be compared to other IMP values. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES OBJC_EXPORT void _objc_msgForward(void /* id receiver, SEL sel, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void _objc_msgForward_stret(void /* id receiver, SEL sel, ... */ ) OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #else OBJC_EXPORT id _Nullable _objc_msgForward(id _Nonnull receiver, SEL _Nonnull sel, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void _objc_msgForward_stret(id _Nonnull receiver, SEL _Nonnull sel, ...) OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #endif /* Variable-argument Messaging Primitives * * Use these functions to call methods with a list of arguments, such * as the one passed to forward:: . * * The contents of the argument list are architecture-specific. * Consult your local function call ABI documentation for details. * * These functions must be cast to an appropriate function pointer type * before being called, except for objc_msgSendv_stret() which must not * be cast to a struct-returning type. */ typedef void* marg_list; OBJC_EXPORT id _Nullable objc_msgSendv(id _Nullable self, SEL _Nonnull op, size_t arg_size, marg_list _Nonnull arg_frame) OBJC2_UNAVAILABLE; OBJC_EXPORT void objc_msgSendv_stret(void * _Nonnull stretAddr, id _Nullable self, SEL _Nonnull op, size_t arg_size, marg_list _Nullable arg_frame) OBJC2_UNAVAILABLE; /* Note that objc_msgSendv_stret() does not return a structure type, * and should not be cast to do so. This is unlike objc_msgSend_stret() * and objc_msgSendSuper_stret(). */ #if defined(__i386__) OBJC_EXPORT double objc_msgSendv_fpret(id _Nullable self, SEL _Nonnull op, unsigned arg_size, marg_list _Nullable arg_frame) OBJC2_UNAVAILABLE; #endif /* The following marg_list macros are of marginal utility. They * are included for compatibility with the old objc-class.h header. */ #if !__OBJC2__ #define marg_prearg_size 0 #define marg_malloc(margs, method) \ do { \ margs = (marg_list *)malloc (marg_prearg_size + ((7 + method_getSizeOfArguments(method)) & ~7)); \ } while (0) #define marg_free(margs) \ do { \ free(margs); \ } while (0) #define marg_adjustedOffset(method, offset) \ (marg_prearg_size + offset) #define marg_getRef(margs, offset, type) \ ( (type *)((char *)margs + marg_adjustedOffset(method,offset) ) ) #define marg_getValue(margs, offset, type) \ ( *marg_getRef(margs, offset, type) ) #define marg_setValue(margs, offset, type, value) \ ( marg_getValue(margs, offset, type) = (value) ) #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-abi.h ================================================ /* * Copyright (c) 2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_ABI_H #define _OBJC_ABI_H /* * WARNING DANGER HAZARD BEWARE EEK * * Everything in this file is for Apple Internal use only. * These will change in arbitrary OS updates and in unpredictable ways. * When your program breaks, you get to keep both pieces. */ /* * objc-abi.h: Declarations for functions used by compiler codegen. */ #include #include #include #include #include /* Linker metadata symbols */ // NSObject was in Foundation/CF on macOS < 10.8. #if TARGET_OS_OSX #if __OBJC2__ OBJC_EXPORT const char __objc_nsobject_class_10_5 __asm__("$ld$hide$os10.5$_OBJC_CLASS_$_NSObject"); OBJC_EXPORT const char __objc_nsobject_class_10_6 __asm__("$ld$hide$os10.6$_OBJC_CLASS_$_NSObject"); OBJC_EXPORT const char __objc_nsobject_class_10_7 __asm__("$ld$hide$os10.7$_OBJC_CLASS_$_NSObject"); OBJC_EXPORT const char __objc_nsobject_metaclass_10_5 __asm__("$ld$hide$os10.5$_OBJC_METACLASS_$_NSObject"); OBJC_EXPORT const char __objc_nsobject_metaclass_10_6 __asm__("$ld$hide$os10.6$_OBJC_METACLASS_$_NSObject"); OBJC_EXPORT const char __objc_nsobject_metaclass_10_7 __asm__("$ld$hide$os10.7$_OBJC_METACLASS_$_NSObject"); OBJC_EXPORT const char __objc_nsobject_isa_10_5 __asm__("$ld$hide$os10.5$_OBJC_IVAR_$_NSObject.isa"); OBJC_EXPORT const char __objc_nsobject_isa_10_6 __asm__("$ld$hide$os10.6$_OBJC_IVAR_$_NSObject.isa"); OBJC_EXPORT const char __objc_nsobject_isa_10_7 __asm__("$ld$hide$os10.7$_OBJC_IVAR_$_NSObject.isa"); #else OBJC_EXPORT const char __objc_nsobject_class_10_5 __asm__("$ld$hide$os10.5$.objc_class_name_NSObject"); OBJC_EXPORT const char __objc_nsobject_class_10_6 __asm__("$ld$hide$os10.6$.objc_class_name_NSObject"); OBJC_EXPORT const char __objc_nsobject_class_10_7 __asm__("$ld$hide$os10.7$.objc_class_name_NSObject"); #endif #endif /* Runtime startup. */ // Old static initializer. Used by old crt1.o and old bug workarounds. OBJC_EXPORT void _objcInit(void) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /* Images */ // Description of an Objective-C image. // __DATA,__objc_imageinfo stores one of these. typedef struct objc_image_info { uint32_t version; // currently 0 uint32_t flags; #if __cplusplus >= 201103L private: enum : uint32_t { IsReplacement = 1<<0, // used for Fix&Continue, now ignored SupportsGC = 1<<1, // image supports GC RequiresGC = 1<<2, // image requires GC OptimizedByDyld = 1<<3, // image is from an optimized shared cache CorrectedSynthesize = 1<<4, // used for an old workaround, now ignored IsSimulated = 1<<5, // image compiled for a simulator platform HasCategoryClassProperties = 1<<6, // class properties in category_t SwiftVersionMaskShift = 8, SwiftVersionMask = 0xff << SwiftVersionMaskShift // Swift ABI version }; public: enum : uint32_t { SwiftVersion1 = 1, SwiftVersion1_2 = 2, SwiftVersion2 = 3, SwiftVersion3 = 4 }; public: bool isReplacement() const { return flags & IsReplacement; } bool supportsGC() const { return flags & SupportsGC; } bool requiresGC() const { return flags & RequiresGC; } bool optimizedByDyld() const { return flags & OptimizedByDyld; } bool hasCategoryClassProperties() const { return flags & HasCategoryClassProperties; } bool containsSwift() const { return (flags & SwiftVersionMask) != 0; } uint32_t swiftVersion() const { return (flags & SwiftVersionMask) >> SwiftVersionMaskShift; } #endif } objc_image_info; /* IsReplacement: Once used for Fix&Continue in old OS X object files (not final linked images) Not currently used. SupportsGC: App: GC is required. Framework: GC is supported but not required. RequiresGC: Framework: GC is required. OptimizedByDyld: Assorted metadata precooked in the dyld shared cache. Never set for images outside the shared cache file itself. CorrectedSynthesize: Once used on old iOS to mark images that did not have a particular miscompile. Not used by the runtime. IsSimulated: Image was compiled for a simulator platform. Not used by the runtime. HasClassProperties: New ABI: category_t.classProperties fields are present. Old ABI: Set by some compilers. Not used by the runtime. */ /* Properties */ // Read or write an object property. Not all object properties use these. OBJC_EXPORT id _Nullable objc_getProperty(id _Nullable self, SEL _Nonnull _cmd, ptrdiff_t offset, BOOL atomic) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_setProperty(id _Nullable self, SEL _Nonnull _cmd, ptrdiff_t offset, id _Nullable newValue, BOOL atomic, signed char shouldCopy) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_setProperty_atomic(id _Nullable self, SEL _Nonnull _cmd, id _Nullable newValue, ptrdiff_t offset) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_setProperty_nonatomic(id _Nullable self, SEL _Nonnull _cmd, id _Nullable newValue, ptrdiff_t offset) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_setProperty_atomic_copy(id _Nullable self, SEL _Nonnull _cmd, id _Nullable newValue, ptrdiff_t offset) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_setProperty_nonatomic_copy(id _Nullable self, SEL _Nonnull _cmd, id _Nullable newValue, ptrdiff_t offset) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); // Read or write a non-object property. Not all uses are C structs, // and not all C struct properties use this. OBJC_EXPORT void objc_copyStruct(void * _Nonnull dest, const void * _Nonnull src, ptrdiff_t size, BOOL atomic, BOOL hasStrong) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // Perform a copy of a C++ object using striped locks. Used by non-POD C++ typed atomic properties. OBJC_EXPORT void objc_copyCppObjectAtomic(void * _Nonnull dest, const void * _Nonnull src, void (* _Nonnull copyHelper) (void * _Nonnull dest, const void * _Nonnull source)) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); /* Classes. */ #if __OBJC2__ OBJC_EXPORT IMP _Nonnull _objc_empty_vtable OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); #endif OBJC_EXPORT struct objc_cache _objc_empty_cache OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /* Messages */ #if __OBJC2__ // objc_msgSendSuper2() takes the current search class, not its superclass. OBJC_EXPORT id _Nullable objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_msgSendSuper2_stret(struct objc_super * _Nonnull super, SEL _Nonnull op,...) OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; // objc_msgSend_noarg() may be faster for methods with no additional arguments. OBJC_EXPORT id _Nullable objc_msgSend_noarg(id _Nullable self, SEL _Nonnull _cmd) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); #endif #if __OBJC2__ // Debug messengers. Messengers used by the compiler have a debug flavor that // may perform extra sanity checking. // Old objc_msgSendSuper() does not have a debug version; this is OBJC2 only. // *_fixup() do not have debug versions; use non-fixup only for debug mode. OBJC_EXPORT id _Nullable objc_msgSend_debug(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable objc_msgSendSuper2_debug(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_msgSend_stret_debug(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; OBJC_EXPORT void objc_msgSendSuper2_stret_debug(struct objc_super * _Nonnull super, SEL _Nonnull op,...) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; # if defined(__i386__) OBJC_EXPORT double objc_msgSend_fpret_debug(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); # elif defined(__x86_64__) OBJC_EXPORT long double objc_msgSend_fpret_debug(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); # if __STDC_VERSION__ >= 199901L OBJC_EXPORT _Complex long double objc_msgSend_fp2ret_debug(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); # else OBJC_EXPORT void objc_msgSend_fp2ret_debug(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); # endif # endif #endif #if __OBJC2__ // Lookup messengers. // These are not callable C functions. Do not call them directly. // The caller should set the method parameters, call objc_msgLookup(), // then immediately call the returned IMP. // // Generic ABI: // - Callee-saved registers are preserved. // - Receiver and selector registers may be modified. These values must // be passed to the called IMP. Other parameter registers are preserved. // - Caller-saved non-parameter registers are not preserved. Some of // these registers are used to pass data from objc_msgLookup() to // the called IMP and must not be disturbed by the caller. // - Red zone is not preserved. // See each architecture's implementation for details. OBJC_EXPORT void objc_msgLookup(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT void objc_msgLookupSuper2(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT void objc_msgLookup_stret(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0) OBJC_ARM64_UNAVAILABLE; OBJC_EXPORT void objc_msgLookupSuper2_stret(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0) OBJC_ARM64_UNAVAILABLE; # if defined(__i386__) OBJC_EXPORT void objc_msgLookup_fpret(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); # elif defined(__x86_64__) OBJC_EXPORT void objc_msgLookup_fpret(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT void objc_msgLookup_fp2ret(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); # endif #endif #if (TARGET_OS_OSX || TARGET_OS_SIMULATOR) && defined(__x86_64__) // objc_msgSend_fixup() was used for vtable-dispatchable call sites. // The symbols remain exported on macOS for binary compatibility. // The symbols can probably be removed from iOS simulator but we haven't tried. OBJC_EXPORT void objc_msgSend_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSend_stret_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSendSuper2_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSendSuper2_stret_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSend_fpret_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSend_fp2ret_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); #endif /* C++-compatible exception handling. */ #if __OBJC2__ // Vtable for C++ exception typeinfo for Objective-C types. OBJC_EXPORT const void * _Nullable objc_ehtype_vtable[] OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // C++ exception typeinfo for type `id`. OBJC_EXPORT struct objc_typeinfo OBJC_EHTYPE_id OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // Exception personality function for Objective-C and Objective-C++ code. struct _Unwind_Exception; struct _Unwind_Context; OBJC_EXPORT int __objc_personality_v0(int version, int actions, uint64_t exceptionClass, struct _Unwind_Exception * _Nonnull exceptionObject, struct _Unwind_Context * _Nonnull context) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); #endif /* ARC */ OBJC_EXPORT id _Nullable objc_retainBlock(id _Nullable) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); /* Non-pointer isa */ #if __OBJC2__ // Extract class pointer from an isa field. #if TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC // No simulators use nonpointer isa yet. #elif __LP64__ # define OBJC_HAVE_NONPOINTER_ISA 1 # define OBJC_HAVE_PACKED_NONPOINTER_ISA 1 // Packed-isa version. This one is used directly by Swift code. // (Class)(isa & (uintptr_t)&objc_absolute_packed_isa_class_mask) == class ptr OBJC_EXPORT const struct { char c; } objc_absolute_packed_isa_class_mask OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); #elif (__ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)) # define OBJC_HAVE_NONPOINTER_ISA 1 # define OBJC_HAVE_INDEXED_NONPOINTER_ISA 1 // Indexed-isa version. // if (isa & (uintptr_t)&objc_absolute_indexed_isa_magic_mask == (uintptr_t)&objc_absolute_indexed_isa_magic_value) { // uintptr_t index = (isa & (uintptr_t)&objc_absolute_indexed_isa_index_mask) >> (uintptr_t)&objc_absolute_indexed_isa_index_shift; // cls = objc_indexed_classes[index]; // } else // cls = (Class)isa; // } OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_magic_mask OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_magic_value OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_index_mask OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_index_shift OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); #endif #endif /* Object class */ // This symbol might be required for binary compatibility, so we // declare it here where TAPI will see it. #if __OBJC__ && __OBJC2__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" #if !defined(OBJC_DECLARE_SYMBOLS) __OSX_AVAILABLE(10.0) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE #endif OBJC_ROOT_CLASS @interface Object { Class isa; } @end #pragma clang diagnostic pop #endif // _OBJC_ABI_H #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-accessors.mm ================================================ /* * Copyright (c) 2006-2008 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include "objc-private.h" #include "runtime.h" // stub interface declarations to make compiler happy. @interface __NSCopyable - (id)copyWithZone:(void *)zone; @end @interface __NSMutableCopyable - (id)mutableCopyWithZone:(void *)zone; @end StripedMap PropertyLocks; StripedMap StructLocks; StripedMap CppObjectLocks; #define MUTABLE_COPY 2 id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) { if (offset == 0) { return object_getClass(self); } // Retain release world id *slot = (id*) ((char*)self + offset); if (!atomic) return *slot; // Atomic retain release world spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); id value = objc_retain(*slot); slotlock.unlock(); // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock. return objc_autoreleaseReturnValue(value); } static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) __attribute__((always_inline)); static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) { if (offset == 0) { object_setClass(self, newValue); return; } id oldValue; id *slot = (id*) ((char*)self + offset); if (copy) { newValue = [newValue copyWithZone:nil]; } else if (mutableCopy) { newValue = [newValue mutableCopyWithZone:nil]; } else { if (*slot == newValue) return; newValue = objc_retain(newValue); } if (!atomic) { oldValue = *slot; *slot = newValue; } else { spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } objc_release(oldValue); } void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY); bool mutableCopy = (shouldCopy == MUTABLE_COPY); reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy); } void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, true, false, false); } void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, false, false, false); } void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, true, true, false); } void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, false, true, false); } // This entry point was designed wrong. When used as a getter, src needs to be locked so that // if simultaneously used for a setter then there would be contention on src. // So we need two locks - one of which will be contended. void objc_copyStruct(void *dest, const void *src, ptrdiff_t size, BOOL atomic, BOOL hasStrong __unused) { spinlock_t *srcLock = nil; spinlock_t *dstLock = nil; if (atomic) { srcLock = &StructLocks[src]; dstLock = &StructLocks[dest]; spinlock_t::lockTwo(srcLock, dstLock); } memmove(dest, src, size); if (atomic) { spinlock_t::unlockTwo(srcLock, dstLock); } } void objc_copyCppObjectAtomic(void *dest, const void *src, void (*copyHelper) (void *dest, const void *source)) { spinlock_t *srcLock = &CppObjectLocks[src]; spinlock_t *dstLock = &CppObjectLocks[dest]; spinlock_t::lockTwo(srcLock, dstLock); // let C++ code perform the actual copy. copyHelper(dest, src); spinlock_t::unlockTwo(srcLock, dstLock); } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-api.h ================================================ /* * Copyright (c) 1999-2006 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ // Copyright 1988-1996 NeXT Software, Inc. #ifndef _OBJC_OBJC_API_H_ #define _OBJC_OBJC_API_H_ #include #include #include #include #ifndef __has_feature # define __has_feature(x) 0 #endif #ifndef __has_extension # define __has_extension __has_feature #endif #ifndef __has_attribute # define __has_attribute(x) 0 #endif #if !__has_feature(nullability) # ifndef _Nullable # define _Nullable # endif # ifndef _Nonnull # define _Nonnull # endif # ifndef _Null_unspecified # define _Null_unspecified # endif #endif #ifndef __APPLE_BLEACH_SDK__ # if __has_feature(attribute_availability_bridgeos) # ifndef __BRIDGEOS_AVAILABLE # define __BRIDGEOS_AVAILABLE(_vers) __OS_AVAILABILITY(bridgeos,introduced=_vers) # endif # ifndef __BRIDGEOS_DEPRECATED # define __BRIDGEOS_DEPRECATED(_start, _dep, _msg) __BRIDGEOS_AVAILABLE(_start) __OS_AVAILABILITY_MSG(bridgeos,deprecated=_dep,_msg) # endif # ifndef __BRIDGEOS_UNAVAILABLE # define __BRIDGEOS_UNAVAILABLE __OS_AVAILABILITY(bridgeos,unavailable) # endif # else # ifndef __BRIDGEOS_AVAILABLE # define __BRIDGEOS_AVAILABLE(_vers) # endif # ifndef __BRIDGEOS_DEPRECATED # define __BRIDGEOS_DEPRECATED(_start, _dep, _msg) # endif # ifndef __BRIDGEOS_UNAVAILABLE # define __BRIDGEOS_UNAVAILABLE # endif # endif #endif /* * OBJC_API_VERSION 0 or undef: Tiger and earlier API only * OBJC_API_VERSION 2: Leopard and later API available */ #if !defined(OBJC_API_VERSION) # if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_5 # define OBJC_API_VERSION 0 # else # define OBJC_API_VERSION 2 # endif #endif /* * OBJC_NO_GC 1: GC is not supported * OBJC_NO_GC undef: GC is supported. This SDK no longer supports this mode. * * OBJC_NO_GC_API undef: Libraries must export any symbols that * dual-mode code may links to. * OBJC_NO_GC_API 1: Libraries need not export GC-related symbols. */ #if defined(__OBJC_GC__) # error Objective-C garbage collection is not supported. #elif TARGET_OS_OSX /* GC is unsupported. GC API symbols are exported. */ # define OBJC_NO_GC 1 # undef OBJC_NO_GC_API #else /* GC is unsupported. GC API symbols are not exported. */ # define OBJC_NO_GC 1 # define OBJC_NO_GC_API 1 #endif /* NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER == 1 * marks -[NSObject init] as a designated initializer. */ #if !defined(NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER) # define NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER 1 #endif /* OBJC_OLD_DISPATCH_PROTOTYPES == 0 enforces the rule that the dispatch * functions must be cast to an appropriate function pointer type. */ #if !defined(OBJC_OLD_DISPATCH_PROTOTYPES) # if __swift__ // Existing Swift code expects IMP to be Comparable. // Variadic IMP is comparable via OpaquePointer; non-variadic IMP isn't. # define OBJC_OLD_DISPATCH_PROTOTYPES 1 # else # define OBJC_OLD_DISPATCH_PROTOTYPES 1 # endif #endif /* OBJC_AVAILABLE: shorthand for all-OS availability */ #if !defined(OBJC_AVAILABLE) # define OBJC_AVAILABLE(x, i, t, w, b) \ __OSX_AVAILABLE(x) __IOS_AVAILABLE(i) __TVOS_AVAILABLE(t) \ __WATCHOS_AVAILABLE(w) __BRIDGEOS_AVAILABLE(b) #endif /* OBJC_ISA_AVAILABILITY: `isa` will be deprecated or unavailable * in the future */ #if !defined(OBJC_ISA_AVAILABILITY) # if __OBJC2__ # define OBJC_ISA_AVAILABILITY __attribute__((deprecated)) # else # define OBJC_ISA_AVAILABILITY /* still available */ # endif #endif /* OBJC2_UNAVAILABLE: unavailable in objc 2.0, deprecated in Leopard */ #if !defined(OBJC2_UNAVAILABLE) # if __OBJC2__ # define OBJC2_UNAVAILABLE UNAVAILABLE_ATTRIBUTE # else /* plain C code also falls here, but this is close enough */ # define OBJC2_UNAVAILABLE \ __OSX_DEPRECATED(10.5, 10.5, "not available in __OBJC2__") \ __IOS_DEPRECATED(2.0, 2.0, "not available in __OBJC2__") \ __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE # endif #endif /* OBJC_UNAVAILABLE: unavailable, with a message where supported */ #if !defined(OBJC_UNAVAILABLE) # if __has_extension(attribute_unavailable_with_message) # define OBJC_UNAVAILABLE(_msg) __attribute__((unavailable(_msg))) # else # define OBJC_UNAVAILABLE(_msg) __attribute__((unavailable)) # endif #endif /* OBJC_DEPRECATED: deprecated, with a message where supported */ #if !defined(OBJC_DEPRECATED) # if __has_extension(attribute_deprecated_with_message) # define OBJC_DEPRECATED(_msg) __attribute__((deprecated(_msg))) # else # define OBJC_DEPRECATED(_msg) __attribute__((deprecated)) # endif #endif /* OBJC_ARC_UNAVAILABLE: unavailable with -fobjc-arc */ #if !defined(OBJC_ARC_UNAVAILABLE) # if __has_feature(objc_arc) # define OBJC_ARC_UNAVAILABLE OBJC_UNAVAILABLE("not available in automatic reference counting mode") # else # define OBJC_ARC_UNAVAILABLE # endif #endif /* OBJC_SWIFT_UNAVAILABLE: unavailable in Swift */ #if !defined(OBJC_SWIFT_UNAVAILABLE) # if __has_feature(attribute_availability_swift) # define OBJC_SWIFT_UNAVAILABLE(_msg) __attribute__((availability(swift, unavailable, message=_msg))) # else # define OBJC_SWIFT_UNAVAILABLE(_msg) # endif #endif /* OBJC_ARM64_UNAVAILABLE: unavailable on arm64 (i.e. stret dispatch) */ #if !defined(OBJC_ARM64_UNAVAILABLE) # if defined(__arm64__) # define OBJC_ARM64_UNAVAILABLE OBJC_UNAVAILABLE("not available in arm64") # else # define OBJC_ARM64_UNAVAILABLE # endif #endif /* OBJC_GC_UNAVAILABLE: unavailable with -fobjc-gc or -fobjc-gc-only */ #if !defined(OBJC_GC_UNAVAILABLE) # define OBJC_GC_UNAVAILABLE #endif #if !defined(OBJC_EXTERN) # if defined(__cplusplus) # define OBJC_EXTERN extern "C" # else # define OBJC_EXTERN extern # endif #endif #if !defined(OBJC_VISIBLE) # if TARGET_OS_WIN32 # if defined(BUILDING_OBJC) # define OBJC_VISIBLE __declspec(dllexport) # else # define OBJC_VISIBLE __declspec(dllimport) # endif # else # define OBJC_VISIBLE __attribute__((visibility("default"))) # endif #endif #if !defined(OBJC_EXPORT) # define OBJC_EXPORT OBJC_EXTERN OBJC_VISIBLE #endif #if !defined(OBJC_IMPORT) # define OBJC_IMPORT extern #endif #if !defined(OBJC_ROOT_CLASS) # if __has_attribute(objc_root_class) # define OBJC_ROOT_CLASS __attribute__((objc_root_class)) # else # define OBJC_ROOT_CLASS # endif #endif #ifndef __DARWIN_NULL #define __DARWIN_NULL NULL #endif #if !defined(OBJC_INLINE) # define OBJC_INLINE __inline #endif // Declares an enum type or option bits type as appropriate for each language. #if (__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum)) #define OBJC_ENUM(_type, _name) enum _name : _type _name; enum _name : _type #if (__cplusplus) #define OBJC_OPTIONS(_type, _name) _type _name; enum : _type #else #define OBJC_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type #endif #else #define OBJC_ENUM(_type, _name) _type _name; enum #define OBJC_OPTIONS(_type, _name) _type _name; enum #endif #if !defined(OBJC_RETURNS_RETAINED) # if __OBJC__ && __has_attribute(ns_returns_retained) # define OBJC_RETURNS_RETAINED __attribute__((ns_returns_retained)) # else # define OBJC_RETURNS_RETAINED # endif #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-auto.h ================================================ /* * Copyright (c) 2004-2007 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_AUTO_H_ #define _OBJC_AUTO_H_ #include #include #include #include #include #include #include #include #include // Define OBJC_SILENCE_GC_DEPRECATIONS=1 to temporarily // silence deprecation warnings for GC functions. #if OBJC_SILENCE_GC_DEPRECATIONS # define OBJC_GC_DEPRECATED(message) #elif __has_extension(attribute_deprecated_with_message) # define OBJC_GC_DEPRECATED(message) __attribute__((deprecated(message ". Define OBJC_SILENCE_GC_DEPRECATIONS=1 to temporarily silence this diagnostic."))) #else # define OBJC_GC_DEPRECATED(message) __attribute__((deprecated)) #endif enum { OBJC_RATIO_COLLECTION = (0 << 0), OBJC_GENERATIONAL_COLLECTION = (1 << 0), OBJC_FULL_COLLECTION = (2 << 0), OBJC_EXHAUSTIVE_COLLECTION = (3 << 0), OBJC_COLLECT_IF_NEEDED = (1 << 3), OBJC_WAIT_UNTIL_DONE = (1 << 4) }; enum { OBJC_CLEAR_RESIDENT_STACK = (1 << 0) }; #if !defined(OBJC_NO_GC) || \ (OBJC_DECLARE_SYMBOLS && !defined(OBJC_NO_GC_API)) /* Out-of-line declarations */ OBJC_EXPORT void objc_collect(unsigned long options) __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_collectingEnabled(void) __OSX_DEPRECATED(10.5, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT malloc_zone_t *objc_collectableZone(void) __OSX_DEPRECATED(10.7, 10.8, "it always returns nil") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_setCollectionThreshold(size_t threshold) __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_setCollectionRatio(size_t ratio) __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT id objc_assign_strongCast(id val, id *dest) __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_global(id val, id *dest) __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_threadlocal(id val, id *dest) __OSX_DEPRECATED(10.7, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_ivar(id value, id dest, ptrdiff_t offset) __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void *objc_memmove_collectable(void *dst, const void *src, size_t size) __OSX_DEPRECATED(10.4, 10.8, "use memmove instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_read_weak(id *location) __OSX_DEPRECATED(10.5, 10.8, "use a simple read instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_weak(id value, id *location) __OSX_DEPRECATED(10.5, 10.8, "use a simple assignment instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_registerThreadWithCollector(void) __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_unregisterThreadWithCollector(void) __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_assertRegisteredThreadWithCollector(void) __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_clear_stack(unsigned long options) __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_is_finalized(void *ptr) __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_finalizeOnMainThread(Class cls) __OSX_DEPRECATED(10.5, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_collecting_enabled(void) __OSX_DEPRECATED(10.4, 10.5, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_set_collection_threshold(size_t threshold) __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_set_collection_ratio(size_t ratio) __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_start_collector_thread(void) __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_startCollectorThread(void) __OSX_DEPRECATED(10.5, 10.7, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_allocate_object(Class cls, int extra) __OSX_DEPRECATED(10.4, 10.4, "use class_createInstance instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; /* !defined(OBJC_NO_GC) */ #else /* defined(OBJC_NO_GC) */ /* Inline declarations */ OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_collect(unsigned long options __unused) { } OBJC_GC_DEPRECATED("it always returns NO") static OBJC_INLINE BOOL objc_collectingEnabled(void) { return NO; } #if TARGET_OS_OSX OBJC_GC_DEPRECATED("it always returns nil") static OBJC_INLINE malloc_zone_t *objc_collectableZone(void) { return nil; } #endif OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_setCollectionThreshold(size_t threshold __unused) { } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_setCollectionRatio(size_t ratio __unused) { } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_startCollectorThread(void) { } #if __has_feature(objc_arc) /* Covers for GC memory operations are unavailable in ARC */ #else OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtr instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); } OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtrBarrier instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); } OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtr instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); } OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtrBarrier instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtr instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); } OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtrBarrier instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_strongCast(id val, id *dest) { return (*dest = val); } OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_global(id val, id *dest) { return (*dest = val); } OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_threadlocal(id val, id *dest) { return (*dest = val); } OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_ivar(id val, id dest, ptrdiff_t offset) { return (*(id*)((intptr_t)(char *)dest+offset) = val); } OBJC_GC_DEPRECATED("use a simple read instead, or convert to zeroing __weak") static OBJC_INLINE id objc_read_weak(id *location) { return *location; } OBJC_GC_DEPRECATED("use a simple assignment instead, or convert to zeroing __weak") static OBJC_INLINE id objc_assign_weak(id value, id *location) { return (*location = value); } /* MRC */ #endif OBJC_GC_DEPRECATED("use memmove instead") static OBJC_INLINE void *objc_memmove_collectable(void *dst, const void *src, size_t size) { return memmove(dst, src, size); } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_finalizeOnMainThread(Class cls __unused) { } OBJC_GC_DEPRECATED("it always returns NO") static OBJC_INLINE BOOL objc_is_finalized(void *ptr __unused) { return NO; } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_clear_stack(unsigned long options __unused) { } OBJC_GC_DEPRECATED("it always returns NO") static OBJC_INLINE BOOL objc_collecting_enabled(void) { return NO; } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_set_collection_threshold(size_t threshold __unused) { } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_set_collection_ratio(size_t ratio __unused) { } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_start_collector_thread(void) { } #if __has_feature(objc_arc) extern id objc_allocate_object(Class cls, int extra) UNAVAILABLE_ATTRIBUTE; #else OBJC_EXPORT id class_createInstance(Class cls, size_t extraBytes) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); OBJC_GC_DEPRECATED("use class_createInstance instead") static OBJC_INLINE id objc_allocate_object(Class cls, int extra) { return class_createInstance(cls, (size_t)extra); } #endif OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_registerThreadWithCollector() { } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_unregisterThreadWithCollector() { } OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_assertRegisteredThreadWithCollector() { } /* defined(OBJC_NO_GC) */ #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-auto.mm ================================================ /* * Copyright (c) 2004-2007 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #define OBJC_DECLARE_SYMBOLS 1 #include "objc-private.h" #include "objc-auto.h" // GC is no longer supported. #if OBJC_NO_GC_API // No GC and no GC symbols needed. We're done here. # if SUPPORT_GC_COMPAT # error inconsistent config settings # endif #else // No GC but we do need to export GC symbols. # if !SUPPORT_GC_COMPAT # error inconsistent config settings # endif void objc_collect(unsigned long options __unused) { } BOOL objc_collectingEnabled(void) { return NO; } void objc_setCollectionThreshold(size_t threshold __unused) { } void objc_setCollectionRatio(size_t ratio __unused) { } void objc_startCollectorThread(void) { } #if TARGET_OS_WIN32 BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) { void *original = InterlockedCompareExchangePointer((void * volatile *)objectLocation, (void *)replacement, (void *)predicate); return (original == predicate); } BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) { void *original = InterlockedCompareExchangePointer((void * volatile *)objectLocation, (void *)replacement, (void *)predicate); return (original == predicate); } #else BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); } BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); } #endif BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); } BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); } BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } id objc_assign_strongCast(id val, id *dest) { return (*dest = val); } id objc_assign_global(id val, id *dest) { return (*dest = val); } id objc_assign_threadlocal(id val, id *dest) { return (*dest = val); } id objc_assign_ivar(id val, id dest, ptrdiff_t offset) { return (*(id*)((char *)dest+offset) = val); } id objc_read_weak(id *location) { return *location; } id objc_assign_weak(id value, id *location) { return (*location = value); } void *objc_memmove_collectable(void *dst, const void *src, size_t size) { return memmove(dst, src, size); } void objc_finalizeOnMainThread(Class cls __unused) { } BOOL objc_is_finalized(void *ptr __unused) { return NO; } void objc_clear_stack(unsigned long options __unused) { } BOOL objc_collecting_enabled(void) { return NO; } void objc_set_collection_threshold(size_t threshold __unused) { } void objc_set_collection_ratio(size_t ratio __unused) { } void objc_start_collector_thread(void) { } id objc_allocate_object(Class cls, int extra) { return class_createInstance(cls, extra); } void objc_registerThreadWithCollector() { } void objc_unregisterThreadWithCollector() { } void objc_assertRegisteredThreadWithCollector() { } malloc_zone_t* objc_collect_init(int(*callback)() __unused) { return nil; } malloc_zone_t* objc_collectableZone() { return nil; } BOOL objc_isAuto(id object __unused) { return NO; } BOOL objc_dumpHeap(char *filename __unused, unsigned long length __unused) { return NO; } // not OBJC_NO_GC_API #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-block-trampolines.h ================================================ /* * Copyright (c) 2018 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_TRAMPOLINES_H #define _OBJC_TRAMPOLINES_H /* * WARNING DANGER HAZARD BEWARE EEK * * Everything in this file is for Apple Internal use only. * These will change in arbitrary OS updates and in unpredictable ways. * When your program breaks, you get to keep both pieces. */ /* * objc-block-trampolines.h: Symbols for IMP block trampolines */ #include OBJC_EXPORT const char _objc_blockTrampolineImpl OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); OBJC_EXPORT const char _objc_blockTrampolineStart OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); OBJC_EXPORT const char _objc_blockTrampolineLast OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); OBJC_EXPORT const char _objc_blockTrampolineImpl_stret OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) OBJC_ARM64_UNAVAILABLE; OBJC_EXPORT const char _objc_blockTrampolineStart_stret OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) OBJC_ARM64_UNAVAILABLE; OBJC_EXPORT const char _objc_blockTrampolineLast_stret OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) OBJC_ARM64_UNAVAILABLE; #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-block-trampolines.mm ================================================ /* * Copyright (c) 2010 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-block-trampolines.m * Author: b.bum * **********************************************************************/ /*********************************************************************** * Imports. **********************************************************************/ #include "objc-private.h" #include "runtime.h" #include #include #include #include // fixme C++ compilers don't implemement memory_order_consume efficiently. // Use memory_order_relaxed and cross our fingers. #define MEMORY_ORDER_CONSUME std::memory_order_relaxed // 8 bytes of text and data per trampoline on all architectures. #define SLOT_SIZE 8 // The trampolines are defined in assembly files in libobjc-trampolines.dylib. // We can't link to libobjc-trampolines.dylib directly because // for security reasons it isn't in the dyld shared cache. // Trampoline addresses are lazily looked up. // All of them are hidden behind a single atomic pointer for lock-free init. #ifdef __PTRAUTH_INTRINSICS__ # define TrampolinePtrauth __ptrauth(ptrauth_key_function_pointer, 1, 0x3af1) #else # define TrampolinePtrauth #endif class TrampolinePointerWrapper { struct TrampolinePointers { class TrampolineAddress { const void * TrampolinePtrauth storage; public: TrampolineAddress(void *dylib, const char *name) { #define PREFIX "_objc_blockTrampoline" char symbol[strlen(PREFIX) + strlen(name) + 1]; strcpy(symbol, PREFIX); strcat(symbol, name); // dlsym() from a text segment returns a signed pointer // Authenticate it manually and let the compiler re-sign it. storage = ptrauth_auth_data(dlsym(dylib, symbol), ptrauth_key_function_pointer, 0); if (!storage) { _objc_fatal("couldn't dlsym %s", symbol); } } uintptr_t address() { return (uintptr_t)(void*)storage; } }; TrampolineAddress impl; // trampoline header code TrampolineAddress start; // first trampoline #if DEBUG // These symbols are only used in assertions. // fixme might be able to move the assertions to libobjc-trampolines itself TrampolineAddress last; // start of the last trampoline // We don't use the address after the last trampoline because that // address might be in a different section, and then dlsym() would not // sign it as a function pointer. # if SUPPORT_STRET TrampolineAddress impl_stret; TrampolineAddress start_stret; TrampolineAddress last_stret; # endif #endif uintptr_t textSegment; uintptr_t textSegmentSize; void check() { #if DEBUG assert(impl.address() == textSegment + PAGE_MAX_SIZE); assert(impl.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE assert(impl.address() + PAGE_MAX_SIZE == last.address() + SLOT_SIZE); assert(last.address()+8 < textSegment + textSegmentSize); assert((last.address() - start.address()) % SLOT_SIZE == 0); # if SUPPORT_STRET assert(impl_stret.address() == textSegment + 2*PAGE_MAX_SIZE); assert(impl_stret.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE assert(impl_stret.address() + PAGE_MAX_SIZE == last_stret.address() + SLOT_SIZE); assert(start.address() - impl.address() == start_stret.address() - impl_stret.address()); assert(last_stret.address() + SLOT_SIZE < textSegment + textSegmentSize); assert((last_stret.address() - start_stret.address()) % SLOT_SIZE == 0); # endif #endif } TrampolinePointers(void *dylib) : impl(dylib, "Impl") , start(dylib, "Start") #if DEBUG , last(dylib, "Last") # if SUPPORT_STRET , impl_stret(dylib, "Impl_stret") , start_stret(dylib, "Start_stret") , last_stret(dylib, "Last_stret") # endif #endif { const auto *mh = dyld_image_header_containing_address((void *)impl.address()); unsigned long size = 0; textSegment = (uintptr_t) getsegmentdata((headerType *)mh, "__TEXT", &size); textSegmentSize = size; check(); } }; std::atomic trampolines{nil}; TrampolinePointers *get() { return trampolines.load(MEMORY_ORDER_CONSUME); } public: void Initialize() { if (get()) return; // This code may be called concurrently. // In the worst case we perform extra dyld operations. void *dylib = dlopen("/usr/lib/libobjc-trampolines.dylib", RTLD_NOW | RTLD_LOCAL | RTLD_FIRST); if (!dylib) { _objc_fatal("couldn't dlopen libobjc-trampolines.dylib"); } auto t = new TrampolinePointers(dylib); TrampolinePointers *old = nil; if (! trampolines.compare_exchange_strong(old, t, memory_order_release)) { delete t; // Lost an initialization race. } } uintptr_t textSegment() { return get()->textSegment; } uintptr_t textSegmentSize() { return get()->textSegmentSize; } uintptr_t impl() { return get()->impl.address(); } uintptr_t start() { return get()->start.address(); } }; static TrampolinePointerWrapper Trampolines; // argument mode identifier typedef enum { ReturnValueInRegisterArgumentMode, #if SUPPORT_STRET ReturnValueOnStackArgumentMode, #endif ArgumentModeCount } ArgumentMode; // We must take care with our data layout on architectures that support // multiple page sizes. // // The trampoline template in __TEXT is sized and aligned with PAGE_MAX_SIZE. // On some platforms this requires additional linker flags. // // When we allocate a page group, we use PAGE_MAX_SIZE size. // This allows trampoline code to find its data by subtracting PAGE_MAX_SIZE. // // When we allocate a page group, we use the process's page alignment. // This simplifies allocation because we don't need to force greater than // default alignment when running with small pages, but it also means // the trampoline code MUST NOT look for its data by masking with PAGE_MAX_MASK. struct TrampolineBlockPageGroup { TrampolineBlockPageGroup *nextPageGroup; // linked list of all pages TrampolineBlockPageGroup *nextAvailablePage; // linked list of pages with available slots uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available // Payload data: block pointers and free list. // Bytes parallel with trampoline header code are the fields above or unused // uint8_t payloads[PAGE_MAX_SIZE - sizeof(TrampolineBlockPageGroup)] // Code: Mach-O header, then trampoline header followed by trampolines. // On platforms with struct return we have non-stret trampolines and // stret trampolines. The stret and non-stret trampolines at a given // index share the same data page. // uint8_t macho[PAGE_MAX_SIZE]; // uint8_t trampolines[ArgumentModeCount][PAGE_MAX_SIZE]; // Per-trampoline block data format: // initial value is 0 while page data is filled sequentially // when filled, value is reference to Block_copy()d block // when empty, value is index of next available slot OR 0 if never used yet union Payload { id block; uintptr_t nextAvailable; // free list }; static uintptr_t headerSize() { return (uintptr_t) (Trampolines.start() - Trampolines.impl()); } static uintptr_t slotSize() { return SLOT_SIZE; } static uintptr_t startIndex() { // headerSize is assumed to be slot-aligned return headerSize() / slotSize(); } static uintptr_t endIndex() { return (uintptr_t)PAGE_MAX_SIZE / slotSize(); } static bool validIndex(uintptr_t index) { return (index >= startIndex() && index < endIndex()); } Payload *payload(uintptr_t index) { assert(validIndex(index)); return (Payload *)((char *)this + index*slotSize()); } uintptr_t trampolinesForMode(int aMode) { // Skip over data page and Mach-O page. return (uintptr_t)this + PAGE_MAX_SIZE * (2 + aMode); } IMP trampoline(int aMode, uintptr_t index) { assert(validIndex(index)); char *base = (char *)trampolinesForMode(aMode); char *imp = base + index*slotSize(); #if __arm__ imp++; // trampoline is Thumb instructions #endif #if __has_feature(ptrauth_calls) imp = ptrauth_sign_unauthenticated(imp, ptrauth_key_function_pointer, 0); #endif return (IMP)imp; } uintptr_t indexForTrampoline(uintptr_t tramp) { for (int aMode = 0; aMode < ArgumentModeCount; aMode++) { uintptr_t base = trampolinesForMode(aMode); uintptr_t start = base + startIndex() * slotSize(); uintptr_t end = base + endIndex() * slotSize(); if (tramp >= start && tramp < end) { return (uintptr_t)(tramp - base) / slotSize(); } } return 0; } static void check() { assert(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup)); assert(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0); } }; static TrampolineBlockPageGroup *HeadPageGroup; #pragma mark Utility Functions #if !__OBJC2__ #define runtimeLock classLock #endif #pragma mark Trampoline Management Functions static TrampolineBlockPageGroup *_allocateTrampolinesAndData() { runtimeLock.assertLocked(); vm_address_t dataAddress; TrampolineBlockPageGroup::check(); // Our final mapping will look roughly like this: // r/w data // r/o text mapped from libobjc-trampolines.dylib // with fixed offsets from the text to the data embedded in the text. // // More precisely it will look like this: // 1 page r/w data // 1 page libobjc-trampolines.dylib Mach-O header // N pages trampoline code, one for each ArgumentMode // M pages for the rest of libobjc-trampolines' TEXT segment. // The kernel requires that we remap the entire TEXT segment every time. // We assume that our code begins on the second TEXT page, but are robust // against other additions to the end of the TEXT segment. assert(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil); auto textSource = Trampolines.textSegment(); auto textSourceSize = Trampolines.textSegmentSize(); auto dataSize = PAGE_MAX_SIZE; // Allocate a single contiguous region big enough to hold data+text. kern_return_t result; result = vm_allocate(mach_task_self(), &dataAddress, dataSize + textSourceSize, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_FOUNDATION)); if (result != KERN_SUCCESS) { _objc_fatal("vm_allocate trampolines failed (%d)", result); } // Remap libobjc-trampolines' TEXT segment atop all // but the first of the pages we just allocated: vm_address_t textDest = dataAddress + dataSize; vm_prot_t currentProtection, maxProtection; result = vm_remap(mach_task_self(), &textDest, textSourceSize, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, mach_task_self(), textSource, TRUE, ¤tProtection, &maxProtection, VM_INHERIT_SHARE); if (result != KERN_SUCCESS) { _objc_fatal("vm_remap trampolines failed (%d)", result); } TrampolineBlockPageGroup *pageGroup = (TrampolineBlockPageGroup *) dataAddress; pageGroup->nextAvailable = pageGroup->startIndex(); pageGroup->nextPageGroup = nil; pageGroup->nextAvailablePage = nil; if (HeadPageGroup) { TrampolineBlockPageGroup *lastPageGroup = HeadPageGroup; while(lastPageGroup->nextPageGroup) { lastPageGroup = lastPageGroup->nextPageGroup; } lastPageGroup->nextPageGroup = pageGroup; HeadPageGroup->nextAvailablePage = pageGroup; } else { HeadPageGroup = pageGroup; } return pageGroup; } static TrampolineBlockPageGroup * getOrAllocatePageGroupWithNextAvailable() { runtimeLock.assertLocked(); if (!HeadPageGroup) return _allocateTrampolinesAndData(); // make sure head page is filled first if (HeadPageGroup->nextAvailable != HeadPageGroup->endIndex()) return HeadPageGroup; if (HeadPageGroup->nextAvailablePage) // check if there is a page w/a hole return HeadPageGroup->nextAvailablePage; return _allocateTrampolinesAndData(); // tack on a new one } static TrampolineBlockPageGroup * pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex) { runtimeLock.assertLocked(); // Authenticate as a function pointer, returning an un-signed address. uintptr_t trampAddress = (uintptr_t)ptrauth_auth_data((const char *)anImp, ptrauth_key_function_pointer, 0); for (TrampolineBlockPageGroup *pageGroup = HeadPageGroup; pageGroup; pageGroup = pageGroup->nextPageGroup) { uintptr_t index = pageGroup->indexForTrampoline(trampAddress); if (index) { if (outIndex) *outIndex = index; return pageGroup; } } return nil; } static ArgumentMode argumentModeForBlock(id block) { ArgumentMode aMode = ReturnValueInRegisterArgumentMode; #if SUPPORT_STRET if (_Block_has_signature(block) && _Block_use_stret(block)) aMode = ReturnValueOnStackArgumentMode; #else assert(! (_Block_has_signature(block) && _Block_use_stret(block))); #endif return aMode; } // `block` must already have been copied IMP _imp_implementationWithBlockNoCopy(id block) { runtimeLock.assertLocked(); TrampolineBlockPageGroup *pageGroup = getOrAllocatePageGroupWithNextAvailable(); uintptr_t index = pageGroup->nextAvailable; assert(index >= pageGroup->startIndex() && index < pageGroup->endIndex()); TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); uintptr_t nextAvailableIndex = payload->nextAvailable; if (nextAvailableIndex == 0) { // First time through (unused slots are zero). Fill sequentially. // If the page is now full this will now be endIndex(), handled below. nextAvailableIndex = index + 1; } pageGroup->nextAvailable = nextAvailableIndex; if (nextAvailableIndex == pageGroup->endIndex()) { // PageGroup is now full (free list or wilderness exhausted) // Remove from available page linked list TrampolineBlockPageGroup *iterator = HeadPageGroup; while(iterator && (iterator->nextAvailablePage != pageGroup)) { iterator = iterator->nextAvailablePage; } if (iterator) { iterator->nextAvailablePage = pageGroup->nextAvailablePage; pageGroup->nextAvailablePage = nil; } } payload->block = block; return pageGroup->trampoline(argumentModeForBlock(block), index); } #pragma mark Public API IMP imp_implementationWithBlock(id block) { // Block object must be copied outside runtimeLock // because it performs arbitrary work. block = Block_copy(block); // Trampolines must be initialized outside runtimeLock // because it calls dlopen(). Trampolines.Initialize(); mutex_locker_t lock(runtimeLock); return _imp_implementationWithBlockNoCopy(block); } id imp_getBlock(IMP anImp) { uintptr_t index; TrampolineBlockPageGroup *pageGroup; if (!anImp) return nil; mutex_locker_t lock(runtimeLock); pageGroup = pageAndIndexContainingIMP(anImp, &index); if (!pageGroup) { return nil; } TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); if (payload->nextAvailable <= TrampolineBlockPageGroup::endIndex()) { // unallocated return nil; } return payload->block; } BOOL imp_removeBlock(IMP anImp) { if (!anImp) return NO; id block; { mutex_locker_t lock(runtimeLock); uintptr_t index; TrampolineBlockPageGroup *pageGroup = pageAndIndexContainingIMP(anImp, &index); if (!pageGroup) { return NO; } TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); block = payload->block; // block is released below, outside the lock payload->nextAvailable = pageGroup->nextAvailable; pageGroup->nextAvailable = index; // make sure this page is on available linked list TrampolineBlockPageGroup *pageGroupIterator = HeadPageGroup; // see if page is the next available page for any existing pages while (pageGroupIterator->nextAvailablePage && pageGroupIterator->nextAvailablePage != pageGroup) { pageGroupIterator = pageGroupIterator->nextAvailablePage; } if (! pageGroupIterator->nextAvailablePage) { // if iteration stopped because nextAvail was nil // add to end of list. pageGroupIterator->nextAvailablePage = pageGroup; pageGroup->nextAvailablePage = nil; } } // do this AFTER dropping the lock Block_release(block); return YES; } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-blocktramps-arm.s ================================================ #if __arm__ #include #include .syntax unified .text .globl __objc_blockTrampolineImpl .globl __objc_blockTrampolineStart .globl __objc_blockTrampolineLast // Trampoline machinery assumes the trampolines are Thumb function pointers #if !__thumb2__ # error sorry #endif .thumb // Exported symbols are not marked as functions. // The trampoline construction code assumes that the Thumb bit is not set. .thumb_func L__objc_blockTrampolineImpl_func .align PAGE_MAX_SHIFT __objc_blockTrampolineImpl: L__objc_blockTrampolineImpl_func: /* r0 == self r12 == pc of trampoline's first instruction + PC bias lr == original return address */ mov r1, r0 // _cmd = self // Trampoline's data is one page before the trampoline text. // Also correct PC bias of 4 bytes. sub r12, # 2*PAGE_MAX_SIZE ldr r0, [r12, #-4] // self = block object ldr pc, [r0, #12] // tail call block->invoke // not reached // Align trampolines to 8 bytes .align 3 .macro TrampolineEntry mov r12, pc b L__objc_blockTrampolineImpl_func .align 3 .endmacro .macro TrampolineEntryX16 TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry .endmacro .macro TrampolineEntryX256 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 .endmacro __objc_blockTrampolineStart: // 2048-2 trampolines to fill 16K page TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry __objc_blockTrampolineLast: TrampolineEntry // TrampolineEntry // TrampolineEntry .text .globl __objc_blockTrampolineImpl_stret .globl __objc_blockTrampolineStart_stret .globl __objc_blockTrampolineLast_stret // Trampoline machinery assumes the trampolines are Thumb function pointers #if !__thumb2__ # error sorry #endif .thumb // Exported symbols are not marked as functions. // The trampoline construction code assumes that the Thumb bit is not set. .thumb_func L__objc_blockTrampolineImpl_stret_func .align PAGE_MAX_SHIFT __objc_blockTrampolineImpl_stret: L__objc_blockTrampolineImpl_stret_func: /* r1 == self r12 == pc of trampoline's first instruction + PC bias lr == original return address */ mov r2, r1 // _cmd = self // Trampoline's data is one page before the trampoline text. // Also correct PC bias of 4 bytes. sub r12, # 3*PAGE_MAX_SIZE ldr r1, [r12, #-4] // self = block object ldr pc, [r1, #12] // tail call block->invoke // not reached // Align trampolines to 8 bytes .align 3 .macro TrampolineEntry_stret mov r12, pc b L__objc_blockTrampolineImpl_stret_func .align 3 .endmacro .macro TrampolineEntryX16_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret .endmacro .macro TrampolineEntryX256_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret .endmacro __objc_blockTrampolineStart_stret: // 2048-2 trampolines to fill 16K page TrampolineEntryX256_stret TrampolineEntryX256_stret TrampolineEntryX256_stret TrampolineEntryX256_stret TrampolineEntryX256_stret TrampolineEntryX256_stret TrampolineEntryX256_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntryX16_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret __objc_blockTrampolineLast_stret: TrampolineEntry_stret // TrampolineEntry_stret // TrampolineEntry_stret #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-blocktramps-arm64.s ================================================ #if __arm64__ #include #include "arm64-asm.h" // Offset of block->invoke field. #if __LP64__ // true arm64 # define BLOCK_INVOKE 16 #else // arm64_32 # define BLOCK_INVOKE 12 #endif .text .globl __objc_blockTrampolineImpl .globl __objc_blockTrampolineStart .globl __objc_blockTrampolineLast .align PAGE_MAX_SHIFT __objc_blockTrampolineImpl: L_objc_blockTrampolineImpl: /* x0 == self x17 == address of called trampoline's data (2 pages before its code) lr == original return address */ mov x1, x0 // _cmd = self ldr p0, [x17] // self = block object add p15, p0, #BLOCK_INVOKE // x15 = &block->invoke ldr p16, [x15] // x16 = block->invoke TailCallBlockInvoke x16, x15 // pad up to TrampolineBlockPagePair header size nop .macro TrampolineEntry // load address of trampoline data (two pages before this instruction) adr x17, -2*PAGE_MAX_SIZE b L_objc_blockTrampolineImpl .endmacro .macro TrampolineEntryX16 TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry .endmacro .macro TrampolineEntryX256 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 .endmacro .align 3 __objc_blockTrampolineStart: // 2048-3 trampolines to fill 16K page TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntryX16 TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry __objc_blockTrampolineLast: TrampolineEntry // TrampolineEntry // TrampolineEntry // TrampolineEntry #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-blocktramps-i386.s ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifdef __i386__ #include .text .globl __objc_blockTrampolineImpl .globl __objc_blockTrampolineStart .globl __objc_blockTrampolineLast .align PAGE_SHIFT __objc_blockTrampolineImpl: popl %eax andl $0xFFFFFFF8, %eax subl $ 2*PAGE_SIZE, %eax movl 4(%esp), %ecx // self -> ecx movl %ecx, 8(%esp) // ecx -> _cmd movl (%eax), %ecx // blockPtr -> ecx movl %ecx, 4(%esp) // ecx -> self jmp *12(%ecx) // tail to block->invoke .macro TrampolineEntry call __objc_blockTrampolineImpl nop nop nop .endmacro .align 5 __objc_blockTrampolineStart: TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry __objc_blockTrampolineLast: TrampolineEntry .text .globl __objc_blockTrampolineImpl_stret .globl __objc_blockTrampolineStart_stret .globl __objc_blockTrampolineLast_stret .align PAGE_SHIFT __objc_blockTrampolineImpl_stret: popl %eax andl $0xFFFFFFF8, %eax subl $ 3*PAGE_SIZE, %eax movl 8(%esp), %ecx // self -> ecx movl %ecx, 12(%esp) // ecx -> _cmd movl (%eax), %ecx // blockPtr -> ecx movl %ecx, 8(%esp) // ecx -> self jmp *12(%ecx) // tail to block->invoke .macro TrampolineEntry_stret call __objc_blockTrampolineImpl_stret nop nop nop .endmacro .align 5 __objc_blockTrampolineStart_stret: TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret __objc_blockTrampolineLast_stret: TrampolineEntry_stret #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-blocktramps-x86_64.s ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifdef __x86_64__ #include .text .globl __objc_blockTrampolineImpl .globl __objc_blockTrampolineStart .globl __objc_blockTrampolineLast .align PAGE_SHIFT __objc_blockTrampolineImpl: popq %r10 andq $0xFFFFFFFFFFFFFFF8, %r10 subq $ 2*PAGE_SIZE, %r10 movq %rdi, %rsi // arg1 -> arg2 movq (%r10), %rdi // block -> arg1 jmp *16(%rdi) .macro TrampolineEntry callq __objc_blockTrampolineImpl nop nop nop .endmacro .align 5 __objc_blockTrampolineStart: TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry TrampolineEntry __objc_blockTrampolineLast: TrampolineEntry .text .globl __objc_blockTrampolineImpl_stret .globl __objc_blockTrampolineStart_stret .globl __objc_blockTrampolineLast_stret .align PAGE_SHIFT __objc_blockTrampolineImpl_stret: popq %r10 andq $0xFFFFFFFFFFFFFFF8, %r10 subq $ 3*PAGE_SIZE, %r10 // %rdi -- first arg -- is address of return value's space. Don't mess with it. movq %rsi, %rdx // arg2 -> arg3 movq (%r10), %rsi // block -> arg2 jmp *16(%rsi) .macro TrampolineEntry_stret callq __objc_blockTrampolineImpl_stret nop nop nop .endmacro .align 5 __objc_blockTrampolineStart_stret: TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret __objc_blockTrampolineLast_stret: TrampolineEntry_stret #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-cache-old.h ================================================ /* * Copyright (c) 2012 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_CACHE_OLD_H #define _OBJC_CACHE_OLD_H #include "objc-private.h" __BEGIN_DECLS extern IMP _cache_getImp(Class cls, SEL sel); extern Method _cache_getMethod(Class cls, SEL sel, IMP objc_msgForward_internal_imp); extern void flush_cache(Class cls); extern bool _cache_fill(Class cls, Method meth, SEL sel); extern void _cache_addForwardEntry(Class cls, SEL sel); extern IMP _cache_addIgnoredEntry(Class cls, SEL sel); extern void _cache_free(Cache cache); extern void _cache_collect(bool collectALot); __END_DECLS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-cache-old.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-cache.m * Method cache management * Cache flushing * Cache garbage collection * Cache instrumentation * Dedicated allocator for large caches **********************************************************************/ /*********************************************************************** * Method cache locking (GrP 2001-1-14) * * For speed, objc_msgSend does not acquire any locks when it reads * method caches. Instead, all cache changes are performed so that any * objc_msgSend running concurrently with the cache mutator will not * crash or hang or get an incorrect result from the cache. * * When cache memory becomes unused (e.g. the old cache after cache * expansion), it is not immediately freed, because a concurrent * objc_msgSend could still be using it. Instead, the memory is * disconnected from the data structures and placed on a garbage list. * The memory is now only accessible to instances of objc_msgSend that * were running when the memory was disconnected; any further calls to * objc_msgSend will not see the garbage memory because the other data * structures don't point to it anymore. The collecting_in_critical * function checks the PC of all threads and returns FALSE when all threads * are found to be outside objc_msgSend. This means any call to objc_msgSend * that could have had access to the garbage has finished or moved past the * cache lookup stage, so it is safe to free the memory. * * All functions that modify cache data or structures must acquire the * cacheUpdateLock to prevent interference from concurrent modifications. * The function that frees cache garbage must acquire the cacheUpdateLock * and use collecting_in_critical() to flush out cache readers. * The cacheUpdateLock is also used to protect the custom allocator used * for large method cache blocks. * * Cache readers (PC-checked by collecting_in_critical()) * objc_msgSend* * _cache_getImp * _cache_getMethod * * Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked) * _cache_fill (acquires lock) * _cache_expand (only called from cache_fill) * _cache_create (only called from cache_expand) * bcopy (only called from instrumented cache_expand) * flush_caches (acquires lock) * _cache_flush (only called from cache_fill and flush_caches) * _cache_collect_free (only called from cache_expand and cache_flush) * * UNPROTECTED cache readers (NOT thread-safe; used for debug info only) * _cache_print * _class_printMethodCaches * _class_printDuplicateCacheEntries * _class_printMethodCacheStatistics * * _class_lookupMethodAndLoadCache is a special case. It may read a * method triplet out of one cache and store it in another cache. This * is unsafe if the method triplet is a forward:: entry, because the * triplet itself could be freed unless _class_lookupMethodAndLoadCache * were PC-checked or used a lock. Additionally, storing the method * triplet in both caches would result in double-freeing if both caches * were flushed or expanded. The solution is for _cache_getMethod to * ignore all entries whose implementation is _objc_msgForward_impcache, * so _class_lookupMethodAndLoadCache cannot look at a forward:: entry * unsafely or place it in multiple caches. ***********************************************************************/ #if !__OBJC2__ #include "objc-private.h" #include "objc-cache-old.h" #include "hashtable2.h" typedef struct { SEL name; // same layout as struct old_method void *unused; IMP imp; // same layout as struct old_method } cache_entry; /* When _class_slow_grow is non-zero, any given cache is actually grown * only on the odd-numbered times it becomes full; on the even-numbered * times, it is simply emptied and re-used. When this flag is zero, * caches are grown every time. */ static const int _class_slow_grow = 1; /* For min cache size: clear_cache=1, slow_grow=1 For max cache size: clear_cache=0, slow_grow=0 */ /* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */ enum { INIT_CACHE_SIZE_LOG2 = 2, INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2) }; /* Amount of space required for `count` hash table buckets, knowing that * one entry is embedded in the cache structure itself. */ #define TABLE_SIZE(count) ((count - 1) * sizeof(cache_entry *)) #if !TARGET_OS_WIN32 # define CACHE_ALLOCATOR #endif /* Custom cache allocator parameters. * CACHE_REGION_SIZE must be a multiple of CACHE_QUANTUM. */ #define CACHE_ALLOCATOR_MIN 512 #define CACHE_QUANTUM (CACHE_ALLOCATOR_MIN+sizeof(struct objc_cache)-sizeof(cache_entry*)) #define CACHE_REGION_SIZE ((128*1024 / CACHE_QUANTUM) * CACHE_QUANTUM) // #define CACHE_REGION_SIZE ((256*1024 / CACHE_QUANTUM) * CACHE_QUANTUM) static uintptr_t cache_allocator_mask_for_size(size_t size) { return (size - sizeof(struct objc_cache)) / sizeof(cache_entry *); } static size_t cache_allocator_size_for_mask(uintptr_t mask) { size_t requested = sizeof(struct objc_cache) + TABLE_SIZE(mask+1); size_t actual = CACHE_QUANTUM; while (actual < requested) actual += CACHE_QUANTUM; return actual; } /* Cache instrumentation data. Immediately follows the cache block itself. */ #ifdef OBJC_INSTRUMENTED typedef struct { unsigned int hitCount; // cache lookup success tally unsigned int hitProbes; // sum entries checked to hit unsigned int maxHitProbes; // max entries checked to hit unsigned int missCount; // cache lookup no-find tally unsigned int missProbes; // sum entries checked to miss unsigned int maxMissProbes; // max entries checked to miss unsigned int flushCount; // cache flush tally unsigned int flushedEntries; // sum cache entries flushed unsigned int maxFlushedEntries; // max cache entries flushed } CacheInstrumentation; #define CACHE_INSTRUMENTATION(cache) (CacheInstrumentation *) &cache->buckets[cache->mask + 1]; #endif /* Cache filling and flushing instrumentation */ static int totalCacheFills = 0; #ifdef OBJC_INSTRUMENTED unsigned int LinearFlushCachesCount = 0; unsigned int LinearFlushCachesVisitedCount = 0; unsigned int MaxLinearFlushCachesVisitedCount = 0; unsigned int NonlinearFlushCachesCount = 0; unsigned int NonlinearFlushCachesClassCount = 0; unsigned int NonlinearFlushCachesVisitedCount = 0; unsigned int MaxNonlinearFlushCachesVisitedCount = 0; unsigned int IdealFlushCachesCount = 0; unsigned int MaxIdealFlushCachesCount = 0; #endif /*********************************************************************** * A static empty cache. All classes initially point at this cache. * When the first message is sent it misses in the cache, and when * the cache is grown it checks for this case and uses malloc rather * than realloc. This avoids the need to check for NULL caches in the * messenger. ***********************************************************************/ struct objc_cache _objc_empty_cache = { 0, // mask 0, // occupied { NULL } // buckets }; #ifdef OBJC_INSTRUMENTED CacheInstrumentation emptyCacheInstrumentation = {0}; #endif /* Local prototypes */ static bool _cache_isEmpty(Cache cache); static Cache _cache_malloc(uintptr_t slotCount); static Cache _cache_create(Class cls); static Cache _cache_expand(Class cls); static int _collecting_in_critical(void); static void _garbage_make_room(void); static void _cache_collect_free(void *data, size_t size); #if defined(CACHE_ALLOCATOR) static bool cache_allocator_is_block(void *block); static Cache cache_allocator_calloc(size_t size); static void cache_allocator_free(void *block); #endif /*********************************************************************** * Cache statistics for OBJC_PRINT_CACHE_SETUP **********************************************************************/ static unsigned int cache_counts[16]; static size_t cache_allocations; static size_t cache_collections; static size_t cache_allocator_regions; static size_t log2u(size_t x) { unsigned int log; log = 0; while (x >>= 1) log += 1; return log; } /*********************************************************************** * _cache_isEmpty. * Returns YES if the given cache is some empty cache. * Empty caches should never be allocated on the heap. **********************************************************************/ static bool _cache_isEmpty(Cache cache) { return (cache == NULL || cache == (Cache)&_objc_empty_cache || cache->mask == 0); } /*********************************************************************** * _cache_malloc. * * Called from _cache_create() and cache_expand() * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ static Cache _cache_malloc(uintptr_t slotCount) { Cache new_cache; size_t size; cacheUpdateLock.assertLocked(); // Allocate table (why not check for failure?) size = sizeof(struct objc_cache) + TABLE_SIZE(slotCount); #if defined(OBJC_INSTRUMENTED) // Custom cache allocator can't handle instrumentation. size += sizeof(CacheInstrumentation); new_cache = calloc(size, 1); new_cache->mask = slotCount - 1; #elif !defined(CACHE_ALLOCATOR) // fixme cache allocator implementation isn't 64-bit clean new_cache = calloc(size, 1); new_cache->mask = (unsigned int)(slotCount - 1); #else if (size < CACHE_ALLOCATOR_MIN) { new_cache = (Cache)calloc(size, 1); new_cache->mask = slotCount - 1; // occupied and buckets and instrumentation are all zero } else { new_cache = cache_allocator_calloc(size); // mask is already set // occupied and buckets and instrumentation are all zero } #endif if (PrintCaches) { size_t bucket = log2u(slotCount); if (bucket < sizeof(cache_counts) / sizeof(cache_counts[0])) { cache_counts[bucket]++; } cache_allocations++; } return new_cache; } /*********************************************************************** * _cache_free_block. * * Called from _cache_free() and _cache_collect_free(). * block may be a cache or a forward:: entry. * If block is a cache, forward:: entries it points to will NOT be freed. * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ static inline int isPowerOf2(unsigned long l) { return 1 == __builtin_popcountl(l); } static void _cache_free_block(void *block) { cacheUpdateLock.assertLocked(); #if !TARGET_OS_WIN32 if (PrintCaches) { Cache cache = (Cache)block; size_t slotCount = cache->mask + 1; if (isPowerOf2(slotCount)) { size_t bucket = log2u(slotCount); if (bucket < sizeof(cache_counts) / sizeof(cache_counts[0])) { cache_counts[bucket]--; } } } #endif #if defined(CACHE_ALLOCATOR) if (cache_allocator_is_block(block)) { cache_allocator_free(block); } else #endif { free(block); } } /*********************************************************************** * _cache_free. * * Called from _objc_remove_classes_in_image(). * forward:: entries in the cache ARE freed. * Cache locks: cacheUpdateLock must NOT be held by the caller. **********************************************************************/ void _cache_free(Cache cache) { unsigned int i; mutex_locker_t lock(cacheUpdateLock); for (i = 0; i < cache->mask + 1; i++) { cache_entry *entry = (cache_entry *)cache->buckets[i]; if (entry && entry->imp == _objc_msgForward_impcache) { _cache_free_block(entry); } } _cache_free_block(cache); } /*********************************************************************** * _cache_create. * * Called from _cache_expand(). * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ static Cache _cache_create(Class cls) { Cache new_cache; cacheUpdateLock.assertLocked(); // Allocate new cache block new_cache = _cache_malloc(INIT_CACHE_SIZE); // Install the cache cls->cache = new_cache; // Clear the grow flag so that we will re-use the current storage, // rather than actually grow the cache, when expanding the cache // for the first time if (_class_slow_grow) { cls->setShouldGrowCache(false); } // Return our creation return new_cache; } /*********************************************************************** * _cache_expand. * * Called from _cache_fill () * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ static Cache _cache_expand(Class cls) { Cache old_cache; Cache new_cache; uintptr_t slotCount; uintptr_t index; cacheUpdateLock.assertLocked(); // First growth goes from empty cache to a real one old_cache = cls->cache; if (_cache_isEmpty(old_cache)) return _cache_create (cls); if (_class_slow_grow) { // Cache grows every other time only. if (cls->shouldGrowCache()) { // Grow the cache this time. Don't grow next time. cls->setShouldGrowCache(false); } else { // Reuse the current cache storage this time. Do grow next time. cls->setShouldGrowCache(true); // Clear the valid-entry counter old_cache->occupied = 0; // Invalidate all the cache entries for (index = 0; index < old_cache->mask + 1; index += 1) { // Remember what this entry was, so we can possibly // deallocate it after the bucket has been invalidated cache_entry *oldEntry = (cache_entry *)old_cache->buckets[index]; // Skip invalid entry if (!oldEntry) continue; // Invalidate this entry old_cache->buckets[index] = NULL; // Deallocate "forward::" entry if (oldEntry->imp == _objc_msgForward_impcache) { _cache_collect_free (oldEntry, sizeof(cache_entry)); } } // Return the same old cache, freshly emptied return old_cache; } } // Double the cache size slotCount = (old_cache->mask + 1) << 1; new_cache = _cache_malloc(slotCount); #ifdef OBJC_INSTRUMENTED // Propagate the instrumentation data { CacheInstrumentation *oldCacheData; CacheInstrumentation *newCacheData; oldCacheData = CACHE_INSTRUMENTATION(old_cache); newCacheData = CACHE_INSTRUMENTATION(new_cache); bcopy ((const char *)oldCacheData, (char *)newCacheData, sizeof(CacheInstrumentation)); } #endif // Deallocate "forward::" entries from the old cache for (index = 0; index < old_cache->mask + 1; index++) { cache_entry *entry = (cache_entry *)old_cache->buckets[index]; if (entry && entry->imp == _objc_msgForward_impcache) { _cache_collect_free (entry, sizeof(cache_entry)); } } // Install new cache cls->cache = new_cache; // Deallocate old cache, try freeing all the garbage _cache_collect_free (old_cache, old_cache->mask * sizeof(cache_entry *)); _cache_collect(false); return new_cache; } /*********************************************************************** * _cache_fill. Add the specified method to the specified class' cache. * Returns NO if the cache entry wasn't added: cache was busy, * class is still being initialized, new entry is a duplicate. * * Called only from _class_lookupMethodAndLoadCache and * class_respondsToMethod and _cache_addForwardEntry. * * Cache locks: cacheUpdateLock must not be held. **********************************************************************/ bool _cache_fill(Class cls, Method smt, SEL sel) { uintptr_t newOccupied; uintptr_t index; cache_entry **buckets; cache_entry *entry; Cache cache; cacheUpdateLock.assertUnlocked(); // Never cache before +initialize is done if (!cls->isInitialized()) { return NO; } // Keep tally of cache additions totalCacheFills += 1; mutex_locker_t lock(cacheUpdateLock); entry = (cache_entry *)smt; cache = cls->cache; // Make sure the entry wasn't added to the cache by some other thread // before we grabbed the cacheUpdateLock. // Don't use _cache_getMethod() because _cache_getMethod() doesn't // return forward:: entries. if (_cache_getImp(cls, sel)) { return NO; // entry is already cached, didn't add new one } // Use the cache as-is if it is less than 3/4 full newOccupied = cache->occupied + 1; if ((newOccupied * 4) <= (cache->mask + 1) * 3) { // Cache is less than 3/4 full. cache->occupied = (unsigned int)newOccupied; } else { // Cache is too full. Expand it. cache = _cache_expand (cls); // Account for the addition cache->occupied += 1; } // Scan for the first unused slot and insert there. // There is guaranteed to be an empty slot because the // minimum size is 4 and we resized at 3/4 full. buckets = (cache_entry **)cache->buckets; for (index = CACHE_HASH(sel, cache->mask); buckets[index] != NULL; index = (index+1) & cache->mask) { // empty } buckets[index] = entry; return YES; // successfully added new cache entry } /*********************************************************************** * _cache_addForwardEntry * Add a forward:: entry for the given selector to cls's method cache. * Does nothing if the cache addition fails for any reason. * Called from class_respondsToMethod and _class_lookupMethodAndLoadCache. * Cache locks: cacheUpdateLock must not be held. **********************************************************************/ void _cache_addForwardEntry(Class cls, SEL sel) { cache_entry *smt; smt = (cache_entry *)malloc(sizeof(cache_entry)); smt->name = sel; smt->imp = _objc_msgForward_impcache; if (! _cache_fill(cls, (Method)smt, sel)) { // fixme hack // Entry not added to cache. Don't leak the method struct. free(smt); } } /*********************************************************************** * _cache_flush. Invalidate all valid entries in the given class' cache. * * Called from flush_caches() and _cache_fill() * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ void _cache_flush(Class cls) { Cache cache; unsigned int index; cacheUpdateLock.assertLocked(); // Locate cache. Ignore unused cache. cache = cls->cache; if (_cache_isEmpty(cache)) return; #ifdef OBJC_INSTRUMENTED { CacheInstrumentation *cacheData; // Tally this flush cacheData = CACHE_INSTRUMENTATION(cache); cacheData->flushCount += 1; cacheData->flushedEntries += cache->occupied; if (cache->occupied > cacheData->maxFlushedEntries) cacheData->maxFlushedEntries = cache->occupied; } #endif // Traverse the cache for (index = 0; index <= cache->mask; index += 1) { // Remember what this entry was, so we can possibly // deallocate it after the bucket has been invalidated cache_entry *oldEntry = (cache_entry *)cache->buckets[index]; // Invalidate this entry cache->buckets[index] = NULL; // Deallocate "forward::" entry if (oldEntry && oldEntry->imp == _objc_msgForward_impcache) _cache_collect_free (oldEntry, sizeof(cache_entry)); } // Clear the valid-entry counter cache->occupied = 0; } /*********************************************************************** * flush_cache. Flushes the instance method cache for class cls only. * Use flush_caches() if cls might have in-use subclasses. **********************************************************************/ void flush_cache(Class cls) { if (cls) { mutex_locker_t lock(cacheUpdateLock); _cache_flush(cls); } } /*********************************************************************** * cache collection. **********************************************************************/ #if !TARGET_OS_WIN32 // A sentinel (magic value) to report bad thread_get_state status. // Must not be a valid PC. // Must not be zero - thread_get_state() on a new thread returns PC == 0. #define PC_SENTINEL 1 // UNIX03 compliance hack (4508809) #if !__DARWIN_UNIX03 #define __srr0 srr0 #define __eip eip #endif static uintptr_t _get_pc_for_thread(thread_t thread) #if defined(__i386__) { i386_thread_state_t state; unsigned int count = i386_THREAD_STATE_COUNT; kern_return_t okay = thread_get_state (thread, i386_THREAD_STATE, (thread_state_t)&state, &count); return (okay == KERN_SUCCESS) ? state.__eip : PC_SENTINEL; } #elif defined(__x86_64__) { x86_thread_state64_t state; unsigned int count = x86_THREAD_STATE64_COUNT; kern_return_t okay = thread_get_state (thread, x86_THREAD_STATE64, (thread_state_t)&state, &count); return (okay == KERN_SUCCESS) ? state.__rip : PC_SENTINEL; } #elif defined(__arm__) { arm_thread_state_t state; unsigned int count = ARM_THREAD_STATE_COUNT; kern_return_t okay = thread_get_state (thread, ARM_THREAD_STATE, (thread_state_t)&state, &count); return (okay == KERN_SUCCESS) ? state.__pc : PC_SENTINEL; } #else { #error _get_pc_for_thread () not implemented for this architecture } #endif #endif /*********************************************************************** * _collecting_in_critical. * Returns TRUE if some thread is currently executing a cache-reading * function. Collection of cache garbage is not allowed when a cache- * reading function is in progress because it might still be using * the garbage memory. **********************************************************************/ extern "C" uintptr_t objc_entryPoints[]; extern "C" uintptr_t objc_exitPoints[]; static int _collecting_in_critical(void) { #if TARGET_OS_WIN32 return TRUE; #else thread_act_port_array_t threads; unsigned number; unsigned count; kern_return_t ret; int result; mach_port_t mythread = pthread_mach_thread_np(pthread_self()); // Get a list of all the threads in the current task ret = task_threads (mach_task_self (), &threads, &number); if (ret != KERN_SUCCESS) { _objc_fatal("task_thread failed (result %d)\n", ret); } // Check whether any thread is in the cache lookup code result = FALSE; for (count = 0; count < number; count++) { int region; uintptr_t pc; // Don't bother checking ourselves if (threads[count] == mythread) continue; // Find out where thread is executing pc = _get_pc_for_thread (threads[count]); // Check for bad status, and if so, assume the worse (can't collect) if (pc == PC_SENTINEL) { result = TRUE; goto done; } // Check whether it is in the cache lookup code for (region = 0; objc_entryPoints[region] != 0; region++) { if ((pc >= objc_entryPoints[region]) && (pc <= objc_exitPoints[region])) { result = TRUE; goto done; } } } done: // Deallocate the port rights for the threads for (count = 0; count < number; count++) { mach_port_deallocate(mach_task_self (), threads[count]); } // Deallocate the thread list vm_deallocate (mach_task_self (), (vm_address_t) threads, sizeof(threads[0]) * number); // Return our finding return result; #endif } /*********************************************************************** * _garbage_make_room. Ensure that there is enough room for at least * one more ref in the garbage. **********************************************************************/ // amount of memory represented by all refs in the garbage static size_t garbage_byte_size = 0; // do not empty the garbage until garbage_byte_size gets at least this big static size_t garbage_threshold = 1024; // table of refs to free static void **garbage_refs = 0; // current number of refs in garbage_refs static size_t garbage_count = 0; // capacity of current garbage_refs static size_t garbage_max = 0; // capacity of initial garbage_refs enum { INIT_GARBAGE_COUNT = 128 }; static void _garbage_make_room(void) { static int first = 1; // Create the collection table the first time it is needed if (first) { first = 0; garbage_refs = (void**) malloc(INIT_GARBAGE_COUNT * sizeof(void *)); garbage_max = INIT_GARBAGE_COUNT; } // Double the table if it is full else if (garbage_count == garbage_max) { garbage_refs = (void**) realloc(garbage_refs, garbage_max * 2 * sizeof(void *)); garbage_max *= 2; } } /*********************************************************************** * _cache_collect_free. Add the specified malloc'd memory to the list * of them to free at some later point. * size is used for the collection threshold. It does not have to be * precisely the block's size. * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ static void _cache_collect_free(void *data, size_t size) { cacheUpdateLock.assertLocked(); _garbage_make_room (); garbage_byte_size += size; garbage_refs[garbage_count++] = data; } /*********************************************************************** * _cache_collect. Try to free accumulated dead caches. * collectALot tries harder to free memory. * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ void _cache_collect(bool collectALot) { cacheUpdateLock.assertLocked(); // Done if the garbage is not full if (garbage_byte_size < garbage_threshold && !collectALot) { return; } // Synchronize collection with objc_msgSend and other cache readers if (!collectALot) { if (_collecting_in_critical ()) { // objc_msgSend (or other cache reader) is currently looking in // the cache and might still be using some garbage. if (PrintCaches) { _objc_inform ("CACHES: not collecting; " "objc_msgSend in progress"); } return; } } else { // No excuses. while (_collecting_in_critical()) ; } // No cache readers in progress - garbage is now deletable // Log our progress if (PrintCaches) { cache_collections++; _objc_inform ("CACHES: COLLECTING %zu bytes (%zu regions, %zu allocations, %zu collections)", garbage_byte_size, cache_allocator_regions, cache_allocations, cache_collections); } // Dispose all refs now in the garbage while (garbage_count--) { _cache_free_block(garbage_refs[garbage_count]); } // Clear the garbage count and total size indicator garbage_count = 0; garbage_byte_size = 0; if (PrintCaches) { size_t i; size_t total = 0; size_t ideal_total = 0; size_t malloc_total = 0; size_t local_total = 0; for (i = 0; i < sizeof(cache_counts) / sizeof(cache_counts[0]); i++) { int count = cache_counts[i]; int slots = 1 << i; size_t size = sizeof(struct objc_cache) + TABLE_SIZE(slots); size_t ideal = size; #if TARGET_OS_WIN32 size_t malloc = size; #else size_t malloc = malloc_good_size(size); #endif size_t local = size < CACHE_ALLOCATOR_MIN ? malloc : cache_allocator_size_for_mask(cache_allocator_mask_for_size(size)); if (!count) continue; _objc_inform("CACHES: %4d slots: %4d caches, %6zu / %6zu / %6zu bytes ideal/malloc/local, %6zu / %6zu bytes wasted malloc/local", slots, count, ideal*count, malloc*count, local*count, malloc*count-ideal*count, local*count-ideal*count); total += count; ideal_total += ideal*count; malloc_total += malloc*count; local_total += local*count; } _objc_inform("CACHES: total: %4zu caches, %6zu / %6zu / %6zu bytes ideal/malloc/local, %6zu / %6zu bytes wasted malloc/local", total, ideal_total, malloc_total, local_total, malloc_total-ideal_total, local_total-ideal_total); } } #if defined(CACHE_ALLOCATOR) /*********************************************************************** * Custom method cache allocator. * Method cache block sizes are 2^slots+2 words, which is a pessimal * case for the system allocator. It wastes 504 bytes per cache block * with 128 or more slots, which adds up to tens of KB for an AppKit process. * To save memory, the custom cache allocator below is used. * * The cache allocator uses 128 KB allocation regions. Few processes will * require a second region. Within a region, allocation is address-ordered * first fit. * * The cache allocator uses a quantum of 520. * Cache block ideal sizes: 520, 1032, 2056, 4104 * Cache allocator sizes: 520, 1040, 2080, 4160 * * Because all blocks are known to be genuine method caches, the ordinary * cache->mask and cache->occupied fields are used as block headers. * No out-of-band headers are maintained. The number of blocks will * almost always be fewer than 200, so for simplicity there is no free * list or other optimization. * * Block in use: mask != 0, occupied != -1 (mask indicates block size) * Block free: mask != 0, occupied == -1 (mask is precisely block size) * * No cache allocator functions take any locks. Instead, the caller * must hold the cacheUpdateLock. * * fixme with 128 KB regions and 520 B min block size, an allocation * bitmap would be only 32 bytes - better than free list? **********************************************************************/ typedef struct cache_allocator_block { uintptr_t size; uintptr_t state; struct cache_allocator_block *nextFree; } cache_allocator_block; typedef struct cache_allocator_region { cache_allocator_block *start; cache_allocator_block *end; // first non-block address cache_allocator_block *freeList; struct cache_allocator_region *next; } cache_allocator_region; static cache_allocator_region *cacheRegion = NULL; /*********************************************************************** * cache_allocator_add_region * Allocates and returns a new region that can hold at least size * bytes of large method caches. * The actual size will be rounded up to a CACHE_QUANTUM boundary, * with a minimum of CACHE_REGION_SIZE. * The new region is lowest-priority for new allocations. Callers that * know the other regions are already full should allocate directly * into the returned region. **********************************************************************/ static cache_allocator_region *cache_allocator_add_region(size_t size) { vm_address_t addr; cache_allocator_block *b; cache_allocator_region **rgnP; cache_allocator_region *newRegion = (cache_allocator_region *) calloc(1, sizeof(cache_allocator_region)); // Round size up to quantum boundary, and apply the minimum size. size += CACHE_QUANTUM - (size % CACHE_QUANTUM); if (size < CACHE_REGION_SIZE) size = CACHE_REGION_SIZE; // Allocate the region addr = (vm_address_t)calloc(size, 1); newRegion->start = (cache_allocator_block *)addr; newRegion->end = (cache_allocator_block *)(addr + size); // Mark the first block: free and covers the entire region b = newRegion->start; b->size = size; b->state = (uintptr_t)-1; b->nextFree = NULL; newRegion->freeList = b; // Add to end of the linked list of regions. // Other regions should be re-used before this one is touched. newRegion->next = NULL; rgnP = &cacheRegion; while (*rgnP) { rgnP = &(**rgnP).next; } *rgnP = newRegion; cache_allocator_regions++; return newRegion; } /*********************************************************************** * cache_allocator_coalesce * Attempts to coalesce a free block with the single free block following * it in the free list, if any. **********************************************************************/ static void cache_allocator_coalesce(cache_allocator_block *block) { if (block->size + (uintptr_t)block == (uintptr_t)block->nextFree) { block->size += block->nextFree->size; block->nextFree = block->nextFree->nextFree; } } /*********************************************************************** * cache_region_calloc * Attempt to allocate a size-byte block in the given region. * Allocation is first-fit. The free list is already fully coalesced. * Returns NULL if there is not enough room in the region for the block. **********************************************************************/ static void *cache_region_calloc(cache_allocator_region *rgn, size_t size) { cache_allocator_block **blockP; uintptr_t mask; // Save mask for allocated block, then round size // up to CACHE_QUANTUM boundary mask = cache_allocator_mask_for_size(size); size = cache_allocator_size_for_mask(mask); // Search the free list for a sufficiently large free block. for (blockP = &rgn->freeList; *blockP != NULL; blockP = &(**blockP).nextFree) { cache_allocator_block *block = *blockP; if (block->size < size) continue; // not big enough // block is now big enough. Allocate from it. // Slice off unneeded fragment of block, if any, // and reconnect the free list around block. if (block->size - size >= CACHE_QUANTUM) { cache_allocator_block *leftover = (cache_allocator_block *)(size + (uintptr_t)block); leftover->size = block->size - size; leftover->state = (uintptr_t)-1; leftover->nextFree = block->nextFree; *blockP = leftover; } else { *blockP = block->nextFree; } // block is now exactly the right size. bzero(block, size); block->size = mask; // Cache->mask block->state = 0; // Cache->occupied return block; } // No room in this region. return NULL; } /*********************************************************************** * cache_allocator_calloc * Custom allocator for large method caches (128+ slots) * The returned cache block already has cache->mask set. * cache->occupied and the cache contents are zero. * Cache locks: cacheUpdateLock must be held by the caller **********************************************************************/ static Cache cache_allocator_calloc(size_t size) { cache_allocator_region *rgn; cacheUpdateLock.assertLocked(); for (rgn = cacheRegion; rgn != NULL; rgn = rgn->next) { void *p = cache_region_calloc(rgn, size); if (p) { return (Cache)p; } } // No regions or all regions full - make a region and try one more time // In the unlikely case of a cache over 256KB, it will get its own region. return (Cache)cache_region_calloc(cache_allocator_add_region(size), size); } /*********************************************************************** * cache_allocator_region_for_block * Returns the cache allocator region that ptr points into, or NULL. **********************************************************************/ static cache_allocator_region *cache_allocator_region_for_block(cache_allocator_block *block) { cache_allocator_region *rgn; for (rgn = cacheRegion; rgn != NULL; rgn = rgn->next) { if (block >= rgn->start && block < rgn->end) return rgn; } return NULL; } /*********************************************************************** * cache_allocator_is_block * If ptr is a live block from the cache allocator, return YES * If ptr is a block from some other allocator, return NO. * If ptr is a dead block from the cache allocator, result is undefined. * Cache locks: cacheUpdateLock must be held by the caller **********************************************************************/ static bool cache_allocator_is_block(void *ptr) { cacheUpdateLock.assertLocked(); return (cache_allocator_region_for_block((cache_allocator_block *)ptr) != NULL); } /*********************************************************************** * cache_allocator_free * Frees a block allocated by the cache allocator. * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ static void cache_allocator_free(void *ptr) { cache_allocator_block *dead = (cache_allocator_block *)ptr; cache_allocator_block *cur; cache_allocator_region *rgn; cacheUpdateLock.assertLocked(); if (! (rgn = cache_allocator_region_for_block(dead))) { // free of non-pointer _objc_inform("cache_allocator_free of non-pointer %p", dead); return; } dead->size = cache_allocator_size_for_mask(dead->size); dead->state = (uintptr_t)-1; if (!rgn->freeList || rgn->freeList > dead) { // dead block belongs at front of free list dead->nextFree = rgn->freeList; rgn->freeList = dead; cache_allocator_coalesce(dead); return; } // dead block belongs in the middle or end of free list for (cur = rgn->freeList; cur != NULL; cur = cur->nextFree) { cache_allocator_block *ahead = cur->nextFree; if (!ahead || ahead > dead) { // cur and ahead straddle dead, OR dead belongs at end of free list cur->nextFree = dead; dead->nextFree = ahead; // coalesce into dead first in case both succeed cache_allocator_coalesce(dead); cache_allocator_coalesce(cur); return; } } // uh-oh _objc_inform("cache_allocator_free of non-pointer %p", ptr); } // defined(CACHE_ALLOCATOR) #endif /*********************************************************************** * Cache instrumentation and debugging **********************************************************************/ #ifdef OBJC_INSTRUMENTED enum { CACHE_HISTOGRAM_SIZE = 512 }; unsigned int CacheHitHistogram [CACHE_HISTOGRAM_SIZE]; unsigned int CacheMissHistogram [CACHE_HISTOGRAM_SIZE]; #endif /*********************************************************************** * _cache_print. **********************************************************************/ static void _cache_print(Cache cache) { uintptr_t index; uintptr_t count; count = cache->mask + 1; for (index = 0; index < count; index += 1) { cache_entry *entry = (cache_entry *)cache->buckets[index]; if (entry) { if (entry->imp == _objc_msgForward_impcache) printf ("does not recognize: \n"); printf ("%s\n", sel_getName(entry->name)); } } } /*********************************************************************** * _class_printMethodCaches. **********************************************************************/ void _class_printMethodCaches(Class cls) { if (_cache_isEmpty(cls->cache)) { printf("no instance-method cache for class %s\n",cls->nameForLogging()); } else { printf("instance-method cache for class %s:\n", cls->nameForLogging()); _cache_print(cls->cache); } if (_cache_isEmpty(cls->ISA()->cache)) { printf("no class-method cache for class %s\n", cls->nameForLogging()); } else { printf ("class-method cache for class %s:\n", cls->nameForLogging()); _cache_print(cls->ISA()->cache); } } #if 0 #warning fixme /*********************************************************************** * _class_printDuplicateCacheEntries. **********************************************************************/ void _class_printDuplicateCacheEntries(bool detail) { NXHashState state; Class cls; unsigned int duplicates; unsigned int index1; unsigned int index2; unsigned int mask; unsigned int count; unsigned int isMeta; Cache cache; printf ("Checking for duplicate cache entries \n"); // Outermost loop - iterate over all classes state = NXInitHashState (class_hash); duplicates = 0; while (NXNextHashState (class_hash, &state, (void **) &cls)) { // Control loop - do given class' cache, then its isa's cache for (isMeta = 0; isMeta <= 1; isMeta += 1) { // Select cache of interest and make sure it exists cache = (isMeta ? cls->ISA : cls)->cache; if (_cache_isEmpty(cache)) continue; // Middle loop - check each entry in the given cache mask = cache->mask; count = mask + 1; for (index1 = 0; index1 < count; index1 += 1) { // Skip invalid entry if (!cache->buckets[index1]) continue; // Inner loop - check that given entry matches no later entry for (index2 = index1 + 1; index2 < count; index2 += 1) { // Skip invalid entry if (!cache->buckets[index2]) continue; // Check for duplication by method name comparison if (strcmp ((char *) cache->buckets[index1]->name), (char *) cache->buckets[index2]->name)) == 0) { if (detail) printf ("%s %s\n", cls->nameForLogging(), sel_getName(cache->buckets[index1]->name)); duplicates += 1; break; } } } } } // Log the findings printf ("duplicates = %d\n", duplicates); printf ("total cache fills = %d\n", totalCacheFills); } /*********************************************************************** * PrintCacheHeader. **********************************************************************/ static void PrintCacheHeader(void) { #ifdef OBJC_INSTRUMENTED printf ("Cache Cache Slots Avg Max AvgS MaxS AvgS MaxS TotalD AvgD MaxD TotalD AvgD MaxD TotD AvgD MaxD\n"); printf ("Size Count Used Used Used Hit Hit Miss Miss Hits Prbs Prbs Misses Prbs Prbs Flsh Flsh Flsh\n"); printf ("----- ----- ----- ----- ---- ---- ---- ---- ---- ------- ---- ---- ------- ---- ---- ---- ---- ----\n"); #else printf ("Cache Cache Slots Avg Max AvgS MaxS AvgS MaxS\n"); printf ("Size Count Used Used Used Hit Hit Miss Miss\n"); printf ("----- ----- ----- ----- ---- ---- ---- ---- ----\n"); #endif } /*********************************************************************** * PrintCacheInfo. **********************************************************************/ static void PrintCacheInfo(unsigned int cacheSize, unsigned int cacheCount, unsigned int slotsUsed, float avgUsed, unsigned int maxUsed, float avgSHit, unsigned int maxSHit, float avgSMiss, unsigned int maxSMiss #ifdef OBJC_INSTRUMENTED , unsigned int totDHits, float avgDHit, unsigned int maxDHit, unsigned int totDMisses, float avgDMiss, unsigned int maxDMiss, unsigned int totDFlsh, float avgDFlsh, unsigned int maxDFlsh #endif ) { #ifdef OBJC_INSTRUMENTED printf ("%5u %5u %5u %5.1f %4u %4.1f %4u %4.1f %4u %7u %4.1f %4u %7u %4.1f %4u %4u %4.1f %4u\n", #else printf ("%5u %5u %5u %5.1f %4u %4.1f %4u %4.1f %4u\n", #endif cacheSize, cacheCount, slotsUsed, avgUsed, maxUsed, avgSHit, maxSHit, avgSMiss, maxSMiss #ifdef OBJC_INSTRUMENTED , totDHits, avgDHit, maxDHit, totDMisses, avgDMiss, maxDMiss, totDFlsh, avgDFlsh, maxDFlsh #endif ); } #ifdef OBJC_INSTRUMENTED /*********************************************************************** * PrintCacheHistogram. Show the non-zero entries from the specified * cache histogram. **********************************************************************/ static void PrintCacheHistogram(char *title, unsigned int *firstEntry, unsigned int entryCount) { unsigned int index; unsigned int *thisEntry; printf ("%s\n", title); printf (" Probes Tally\n"); printf (" ------ -----\n"); for (index = 0, thisEntry = firstEntry; index < entryCount; index += 1, thisEntry += 1) { if (*thisEntry == 0) continue; printf (" %6d %5d\n", index, *thisEntry); } } #endif /*********************************************************************** * _class_printMethodCacheStatistics. **********************************************************************/ #define MAX_LOG2_SIZE 32 #define MAX_CHAIN_SIZE 100 void _class_printMethodCacheStatistics(void) { unsigned int isMeta; unsigned int index; NXHashState state; Class cls; unsigned int totalChain; unsigned int totalMissChain; unsigned int maxChain; unsigned int maxMissChain; unsigned int classCount; unsigned int negativeEntryCount; unsigned int cacheExpandCount; unsigned int cacheCountBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int totalEntriesBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int maxEntriesBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int totalChainBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int totalMissChainBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int totalMaxChainBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int totalMaxMissChainBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int maxChainBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int maxMissChainBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int chainCount[MAX_CHAIN_SIZE] = {0}; unsigned int missChainCount[MAX_CHAIN_SIZE] = {0}; #ifdef OBJC_INSTRUMENTED unsigned int hitCountBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int hitProbesBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int maxHitProbesBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int missCountBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int missProbesBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int maxMissProbesBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int flushCountBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int flushedEntriesBySize[2][MAX_LOG2_SIZE] = {{0}}; unsigned int maxFlushedEntriesBySize[2][MAX_LOG2_SIZE] = {{0}}; #endif printf ("Printing cache statistics\n"); // Outermost loop - iterate over all classes state = NXInitHashState (class_hash); classCount = 0; negativeEntryCount = 0; cacheExpandCount = 0; while (NXNextHashState (class_hash, &state, (void **) &cls)) { // Tally classes classCount += 1; // Control loop - do given class' cache, then its isa's cache for (isMeta = 0; isMeta <= 1; isMeta += 1) { Cache cache; unsigned int mask; unsigned int log2Size; unsigned int entryCount; // Select cache of interest cache = (isMeta ? cls->ISA : cls)->cache; // Ignore empty cache... should we? if (_cache_isEmpty(cache)) continue; // Middle loop - do each entry in the given cache mask = cache->mask; entryCount = 0; totalChain = 0; totalMissChain = 0; maxChain = 0; maxMissChain = 0; for (index = 0; index < mask + 1; index += 1) { cache_entry **buckets; cache_entry *entry; unsigned int hash; unsigned int methodChain; unsigned int methodMissChain; unsigned int index2; // If entry is invalid, the only item of // interest is that future insert hashes // to this entry can use it directly. buckets = (cache_entry **)cache->buckets; if (!buckets[index]) { missChainCount[0] += 1; continue; } entry = buckets[index]; // Tally valid entries entryCount += 1; // Tally "forward::" entries if (entry->imp == _objc_msgForward_impcache) negativeEntryCount += 1; // Calculate search distance (chain length) for this method // The chain may wrap around to the beginning of the table. hash = CACHE_HASH(entry->name, mask); if (index >= hash) methodChain = index - hash; else methodChain = (mask+1) + index - hash; // Tally chains of this length if (methodChain < MAX_CHAIN_SIZE) chainCount[methodChain] += 1; // Keep sum of all chain lengths totalChain += methodChain; // Record greatest chain length if (methodChain > maxChain) maxChain = methodChain; // Calculate search distance for miss that hashes here index2 = index; while (buckets[index2]) { index2 += 1; index2 &= mask; } methodMissChain = ((index2 - index) & mask); // Tally miss chains of this length if (methodMissChain < MAX_CHAIN_SIZE) missChainCount[methodMissChain] += 1; // Keep sum of all miss chain lengths in this class totalMissChain += methodMissChain; // Record greatest miss chain length if (methodMissChain > maxMissChain) maxMissChain = methodMissChain; } // Factor this cache into statistics about caches of the same // type and size (all caches are a power of two in size) log2Size = log2u (mask + 1); cacheCountBySize[isMeta][log2Size] += 1; totalEntriesBySize[isMeta][log2Size] += entryCount; if (entryCount > maxEntriesBySize[isMeta][log2Size]) maxEntriesBySize[isMeta][log2Size] = entryCount; totalChainBySize[isMeta][log2Size] += totalChain; totalMissChainBySize[isMeta][log2Size] += totalMissChain; totalMaxChainBySize[isMeta][log2Size] += maxChain; totalMaxMissChainBySize[isMeta][log2Size] += maxMissChain; if (maxChain > maxChainBySize[isMeta][log2Size]) maxChainBySize[isMeta][log2Size] = maxChain; if (maxMissChain > maxMissChainBySize[isMeta][log2Size]) maxMissChainBySize[isMeta][log2Size] = maxMissChain; #ifdef OBJC_INSTRUMENTED { CacheInstrumentation *cacheData; cacheData = CACHE_INSTRUMENTATION(cache); hitCountBySize[isMeta][log2Size] += cacheData->hitCount; hitProbesBySize[isMeta][log2Size] += cacheData->hitProbes; if (cacheData->maxHitProbes > maxHitProbesBySize[isMeta][log2Size]) maxHitProbesBySize[isMeta][log2Size] = cacheData->maxHitProbes; missCountBySize[isMeta][log2Size] += cacheData->missCount; missProbesBySize[isMeta][log2Size] += cacheData->missProbes; if (cacheData->maxMissProbes > maxMissProbesBySize[isMeta][log2Size]) maxMissProbesBySize[isMeta][log2Size] = cacheData->maxMissProbes; flushCountBySize[isMeta][log2Size] += cacheData->flushCount; flushedEntriesBySize[isMeta][log2Size] += cacheData->flushedEntries; if (cacheData->maxFlushedEntries > maxFlushedEntriesBySize[isMeta][log2Size]) maxFlushedEntriesBySize[isMeta][log2Size] = cacheData->maxFlushedEntries; } #endif // Caches start with a power of two number of entries, and grow by doubling, so // we can calculate the number of times this cache has expanded cacheExpandCount += log2Size - INIT_CACHE_SIZE_LOG2; } } { unsigned int cacheCountByType[2] = {0}; unsigned int totalCacheCount = 0; unsigned int totalEntries = 0; unsigned int maxEntries = 0; unsigned int totalSlots = 0; #ifdef OBJC_INSTRUMENTED unsigned int totalHitCount = 0; unsigned int totalHitProbes = 0; unsigned int maxHitProbes = 0; unsigned int totalMissCount = 0; unsigned int totalMissProbes = 0; unsigned int maxMissProbes = 0; unsigned int totalFlushCount = 0; unsigned int totalFlushedEntries = 0; unsigned int maxFlushedEntries = 0; #endif totalChain = 0; maxChain = 0; totalMissChain = 0; maxMissChain = 0; // Sum information over all caches for (isMeta = 0; isMeta <= 1; isMeta += 1) { for (index = 0; index < MAX_LOG2_SIZE; index += 1) { cacheCountByType[isMeta] += cacheCountBySize[isMeta][index]; totalEntries += totalEntriesBySize[isMeta][index]; totalSlots += cacheCountBySize[isMeta][index] * (1 << index); totalChain += totalChainBySize[isMeta][index]; if (maxEntriesBySize[isMeta][index] > maxEntries) maxEntries = maxEntriesBySize[isMeta][index]; if (maxChainBySize[isMeta][index] > maxChain) maxChain = maxChainBySize[isMeta][index]; totalMissChain += totalMissChainBySize[isMeta][index]; if (maxMissChainBySize[isMeta][index] > maxMissChain) maxMissChain = maxMissChainBySize[isMeta][index]; #ifdef OBJC_INSTRUMENTED totalHitCount += hitCountBySize[isMeta][index]; totalHitProbes += hitProbesBySize[isMeta][index]; if (maxHitProbesBySize[isMeta][index] > maxHitProbes) maxHitProbes = maxHitProbesBySize[isMeta][index]; totalMissCount += missCountBySize[isMeta][index]; totalMissProbes += missProbesBySize[isMeta][index]; if (maxMissProbesBySize[isMeta][index] > maxMissProbes) maxMissProbes = maxMissProbesBySize[isMeta][index]; totalFlushCount += flushCountBySize[isMeta][index]; totalFlushedEntries += flushedEntriesBySize[isMeta][index]; if (maxFlushedEntriesBySize[isMeta][index] > maxFlushedEntries) maxFlushedEntries = maxFlushedEntriesBySize[isMeta][index]; #endif } totalCacheCount += cacheCountByType[isMeta]; } // Log our findings printf ("There are %u classes\n", classCount); for (isMeta = 0; isMeta <= 1; isMeta += 1) { // Number of this type of class printf ("\nThere are %u %s-method caches, broken down by size (slot count):\n", cacheCountByType[isMeta], isMeta ? "class" : "instance"); // Print header PrintCacheHeader (); // Keep format consistent even if there are caches of this kind if (cacheCountByType[isMeta] == 0) { printf ("(none)\n"); continue; } // Usage information by cache size for (index = 0; index < MAX_LOG2_SIZE; index += 1) { unsigned int cacheCount; unsigned int cacheSlotCount; unsigned int cacheEntryCount; // Get number of caches of this type and size cacheCount = cacheCountBySize[isMeta][index]; if (cacheCount == 0) continue; // Get the cache slot count and the total number of valid entries cacheSlotCount = (1 << index); cacheEntryCount = totalEntriesBySize[isMeta][index]; // Give the analysis PrintCacheInfo (cacheSlotCount, cacheCount, cacheEntryCount, (float) cacheEntryCount / (float) cacheCount, maxEntriesBySize[isMeta][index], (float) totalChainBySize[isMeta][index] / (float) cacheEntryCount, maxChainBySize[isMeta][index], (float) totalMissChainBySize[isMeta][index] / (float) (cacheCount * cacheSlotCount), maxMissChainBySize[isMeta][index] #ifdef OBJC_INSTRUMENTED , hitCountBySize[isMeta][index], hitCountBySize[isMeta][index] ? (float) hitProbesBySize[isMeta][index] / (float) hitCountBySize[isMeta][index] : 0.0, maxHitProbesBySize[isMeta][index], missCountBySize[isMeta][index], missCountBySize[isMeta][index] ? (float) missProbesBySize[isMeta][index] / (float) missCountBySize[isMeta][index] : 0.0, maxMissProbesBySize[isMeta][index], flushCountBySize[isMeta][index], flushCountBySize[isMeta][index] ? (float) flushedEntriesBySize[isMeta][index] / (float) flushCountBySize[isMeta][index] : 0.0, maxFlushedEntriesBySize[isMeta][index] #endif ); } } // Give overall numbers printf ("\nCumulative:\n"); PrintCacheHeader (); PrintCacheInfo (totalSlots, totalCacheCount, totalEntries, (float) totalEntries / (float) totalCacheCount, maxEntries, (float) totalChain / (float) totalEntries, maxChain, (float) totalMissChain / (float) totalSlots, maxMissChain #ifdef OBJC_INSTRUMENTED , totalHitCount, totalHitCount ? (float) totalHitProbes / (float) totalHitCount : 0.0, maxHitProbes, totalMissCount, totalMissCount ? (float) totalMissProbes / (float) totalMissCount : 0.0, maxMissProbes, totalFlushCount, totalFlushCount ? (float) totalFlushedEntries / (float) totalFlushCount : 0.0, maxFlushedEntries #endif ); printf ("\nNumber of \"forward::\" entries: %d\n", negativeEntryCount); printf ("Number of cache expansions: %d\n", cacheExpandCount); #ifdef OBJC_INSTRUMENTED printf ("flush_caches: total calls total visits average visits max visits total classes visits/class\n"); printf (" ----------- ------------ -------------- ---------- ------------- -------------\n"); printf (" linear %11u %12u %14.1f %10u %13u %12.2f\n", LinearFlushCachesCount, LinearFlushCachesVisitedCount, LinearFlushCachesCount ? (float) LinearFlushCachesVisitedCount / (float) LinearFlushCachesCount : 0.0, MaxLinearFlushCachesVisitedCount, LinearFlushCachesVisitedCount, 1.0); printf (" nonlinear %11u %12u %14.1f %10u %13u %12.2f\n", NonlinearFlushCachesCount, NonlinearFlushCachesVisitedCount, NonlinearFlushCachesCount ? (float) NonlinearFlushCachesVisitedCount / (float) NonlinearFlushCachesCount : 0.0, MaxNonlinearFlushCachesVisitedCount, NonlinearFlushCachesClassCount, NonlinearFlushCachesClassCount ? (float) NonlinearFlushCachesVisitedCount / (float) NonlinearFlushCachesClassCount : 0.0); printf (" ideal %11u %12u %14.1f %10u %13u %12.2f\n", LinearFlushCachesCount + NonlinearFlushCachesCount, IdealFlushCachesCount, LinearFlushCachesCount + NonlinearFlushCachesCount ? (float) IdealFlushCachesCount / (float) (LinearFlushCachesCount + NonlinearFlushCachesCount) : 0.0, MaxIdealFlushCachesCount, LinearFlushCachesVisitedCount + NonlinearFlushCachesClassCount, LinearFlushCachesVisitedCount + NonlinearFlushCachesClassCount ? (float) IdealFlushCachesCount / (float) (LinearFlushCachesVisitedCount + NonlinearFlushCachesClassCount) : 0.0); PrintCacheHistogram ("\nCache hit histogram:", &CacheHitHistogram[0], CACHE_HISTOGRAM_SIZE); PrintCacheHistogram ("\nCache miss histogram:", &CacheMissHistogram[0], CACHE_HISTOGRAM_SIZE); #endif #if 0 printf ("\nLookup chains:"); for (index = 0; index < MAX_CHAIN_SIZE; index += 1) { if (chainCount[index] != 0) printf (" %u:%u", index, chainCount[index]); } printf ("\nMiss chains:"); for (index = 0; index < MAX_CHAIN_SIZE; index += 1) { if (missChainCount[index] != 0) printf (" %u:%u", index, missChainCount[index]); } printf ("\nTotal memory usage for cache data structures: %lu bytes\n", totalCacheCount * (sizeof(struct objc_cache) - sizeof(cache_entry *)) + totalSlots * sizeof(cache_entry *) + negativeEntryCount * sizeof(cache_entry)); #endif } } #endif // !__OBJC2__ #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-cache.h ================================================ #ifndef _OBJC_CACHE_H #define _OBJC_CACHE_H #include "objc-private.h" __BEGIN_DECLS extern IMP cache_getImp(Class cls, SEL sel); extern void cache_fill(Class cls, SEL sel, IMP imp, id receiver); extern void cache_erase_nolock(Class cls); extern void cache_delete(Class cls); extern void cache_collect(bool collectALot); __END_DECLS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-cache.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-cache.m * Method cache management * Cache flushing * Cache garbage collection * Cache instrumentation * Dedicated allocator for large caches **********************************************************************/ /*********************************************************************** * Method cache locking (GrP 2001-1-14) * * For speed, objc_msgSend does not acquire any locks when it reads * method caches. Instead, all cache changes are performed so that any * objc_msgSend running concurrently with the cache mutator will not * crash or hang or get an incorrect result from the cache. * * When cache memory becomes unused (e.g. the old cache after cache * expansion), it is not immediately freed, because a concurrent * objc_msgSend could still be using it. Instead, the memory is * disconnected from the data structures and placed on a garbage list. * The memory is now only accessible to instances of objc_msgSend that * were running when the memory was disconnected; any further calls to * objc_msgSend will not see the garbage memory because the other data * structures don't point to it anymore. The collecting_in_critical * function checks the PC of all threads and returns FALSE when all threads * are found to be outside objc_msgSend. This means any call to objc_msgSend * that could have had access to the garbage has finished or moved past the * cache lookup stage, so it is safe to free the memory. * * All functions that modify cache data or structures must acquire the * cacheUpdateLock to prevent interference from concurrent modifications. * The function that frees cache garbage must acquire the cacheUpdateLock * and use collecting_in_critical() to flush out cache readers. * The cacheUpdateLock is also used to protect the custom allocator used * for large method cache blocks. * * Cache readers (PC-checked by collecting_in_critical()) * objc_msgSend* * cache_getImp * * Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked) * cache_fill (acquires lock) * cache_expand (only called from cache_fill) * cache_create (only called from cache_expand) * bcopy (only called from instrumented cache_expand) * flush_caches (acquires lock) * cache_flush (only called from cache_fill and flush_caches) * cache_collect_free (only called from cache_expand and cache_flush) * * UNPROTECTED cache readers (NOT thread-safe; used for debug info only) * cache_print * _class_printMethodCaches * _class_printDuplicateCacheEntries * _class_printMethodCacheStatistics * ***********************************************************************/ #if __OBJC2__ #include "objc-private.h" #include "objc-cache.h" /* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */ enum { INIT_CACHE_SIZE_LOG2 = 2, INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2) }; static void cache_collect_free(struct bucket_t *data, mask_t capacity); static int _collecting_in_critical(void); static void _garbage_make_room(void); /*********************************************************************** * Cache statistics for OBJC_PRINT_CACHE_SETUP **********************************************************************/ static unsigned int cache_counts[16]; static size_t cache_allocations; static size_t cache_collections; static void recordNewCache(mask_t capacity) { size_t bucket = log2u(capacity); if (bucket < countof(cache_counts)) { cache_counts[bucket]++; } cache_allocations++; } static void recordDeadCache(mask_t capacity) { size_t bucket = log2u(capacity); if (bucket < countof(cache_counts)) { cache_counts[bucket]--; } } /*********************************************************************** * Pointers used by compiled class objects * These use asm to avoid conflicts with the compiler's internal declarations **********************************************************************/ // EMPTY_BYTES includes space for a cache end marker bucket. // This end marker doesn't actually have the wrap-around pointer // because cache scans always find an empty bucket before they might wrap. // 1024 buckets is fairly common. #if DEBUG // Use a smaller size to exercise heap-allocated empty caches. # define EMPTY_BYTES ((8+1)*16) #else # define EMPTY_BYTES ((1024+1)*16) #endif #define stringize(x) #x #define stringize2(x) stringize(x) // "cache" is cache->buckets; "vtable" is cache->mask/occupied // hack to avoid conflicts with compiler's internal declaration asm("\n .section __TEXT,__const" "\n .globl __objc_empty_vtable" "\n .set __objc_empty_vtable, 0" "\n .globl __objc_empty_cache" "\n .align 3" "\n __objc_empty_cache: .space " stringize2(EMPTY_BYTES) ); #if __arm__ || __x86_64__ || __i386__ // objc_msgSend has few registers available. // Cache scan increments and wraps at special end-marking bucket. #define CACHE_END_MARKER 1 static inline mask_t cache_next(mask_t i, mask_t mask) { return (i+1) & mask; } #elif __arm64__ // objc_msgSend has lots of registers available. // Cache scan decrements. No end marker needed. #define CACHE_END_MARKER 0 static inline mask_t cache_next(mask_t i, mask_t mask) { return i ? i-1 : mask; } #else #error unknown architecture #endif // copied from dispatch_atomic_maximally_synchronizing_barrier // fixme verify that this barrier hack does in fact work here #if __x86_64__ #define mega_barrier() \ do { unsigned long _clbr; __asm__ __volatile__( \ "cpuid" \ : "=a" (_clbr) : "0" (0) : "rbx", "rcx", "rdx", "cc", "memory" \ ); } while(0) #elif __i386__ #define mega_barrier() \ do { unsigned long _clbr; __asm__ __volatile__( \ "cpuid" \ : "=a" (_clbr) : "0" (0) : "ebx", "ecx", "edx", "cc", "memory" \ ); } while(0) #elif __arm__ || __arm64__ #define mega_barrier() \ __asm__ __volatile__( \ "dsb ish" \ : : : "memory") #else #error unknown architecture #endif #if __arm64__ // Pointer-size register prefix for inline asm # if __LP64__ # define p "x" // true arm64 # else # define p "w" // arm64_32 # endif // Use atomic double-word instructions to update cache entries. // This requires cache buckets not cross cache line boundaries. static ALWAYS_INLINE void stp(uintptr_t onep, uintptr_t twop, void *destp) { __asm__ ("stp %" p "[one], %" p "[two], [%x[dest]]" : "=m" (((uintptr_t *)(destp))[0]), "=m" (((uintptr_t *)(destp))[1]) : [one] "r" (onep), [two] "r" (twop), [dest] "r" (destp) : /* no clobbers */ ); } static ALWAYS_INLINE void __unused ldp(uintptr_t& onep, uintptr_t& twop, const void *srcp) { __asm__ ("ldp %" p "[one], %" p "[two], [%x[src]]" : [one] "=r" (onep), [two] "=r" (twop) : "m" (((const uintptr_t *)(srcp))[0]), "m" (((const uintptr_t *)(srcp))[1]), [src] "r" (srcp) : /* no clobbers */ ); } #undef p #endif // Class points to cache. SEL is key. Cache buckets store SEL+IMP. // Caches are never built in the dyld shared cache. static inline mask_t cache_hash(cache_key_t key, mask_t mask) { return (mask_t)(key & mask); } cache_t *getCache(Class cls) { assert(cls); return &cls->cache; } cache_key_t getKey(SEL sel) { assert(sel); return (cache_key_t)sel; } #if __arm64__ void bucket_t::set(cache_key_t newKey, IMP newImp) { assert(_key == 0 || _key == newKey); static_assert(offsetof(bucket_t,_imp) == 0 && offsetof(bucket_t,_key) == sizeof(void *), "bucket_t doesn't match arm64 bucket_t::set()"); #if __has_feature(ptrauth_calls) // Authenticate as a C function pointer and re-sign for the cache bucket. uintptr_t signedImp = _imp.prepareWrite(newImp); #else // No function pointer signing. uintptr_t signedImp = (uintptr_t)newImp; #endif // Write to the bucket. // LDP/STP guarantees that all observers get // either imp/key or newImp/newKey stp(signedImp, newKey, this); } #else void bucket_t::set(cache_key_t newKey, IMP newImp) { assert(_key == 0 || _key == newKey); // objc_msgSend uses key and imp with no locks. // It is safe for objc_msgSend to see new imp but NULL key // (It will get a cache miss but not dispatch to the wrong place.) // It is unsafe for objc_msgSend to see old imp and new key. // Therefore we write new imp, wait a lot, then write new key. _imp = newImp; if (_key != newKey) { mega_barrier(); _key = newKey; } } #endif void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) { // objc_msgSend uses mask and buckets with no locks. // It is safe for objc_msgSend to see new buckets but old mask. // (It will get a cache miss but not overrun the buckets' bounds). // It is unsafe for objc_msgSend to see old buckets and new mask. // Therefore we write new buckets, wait a lot, then write new mask. // objc_msgSend reads mask first, then buckets. // ensure other threads see buckets contents before buckets pointer mega_barrier(); _buckets = newBuckets; // ensure other threads see new buckets before new mask mega_barrier(); _mask = newMask; _occupied = 0; } struct bucket_t *cache_t::buckets() { return _buckets; } mask_t cache_t::mask() { return _mask; } mask_t cache_t::occupied() { return _occupied; } void cache_t::incrementOccupied() { _occupied++; } void cache_t::initializeToEmpty() { bzero(this, sizeof(*this)); _buckets = (bucket_t *)&_objc_empty_cache; } mask_t cache_t::capacity() { return mask() ? mask()+1 : 0; } #if CACHE_END_MARKER size_t cache_t::bytesForCapacity(uint32_t cap) { // fixme put end marker inline when capacity+1 malloc is inefficient return sizeof(bucket_t) * (cap + 1); } bucket_t *cache_t::endMarker(struct bucket_t *b, uint32_t cap) { // bytesForCapacity() chooses whether the end marker is inline or not return (bucket_t *)((uintptr_t)b + bytesForCapacity(cap)) - 1; } bucket_t *allocateBuckets(mask_t newCapacity) { // Allocate one extra bucket to mark the end of the list. // This can't overflow mask_t because newCapacity is a power of 2. // fixme instead put the end mark inline when +1 is malloc-inefficient bucket_t *newBuckets = (bucket_t *) calloc(cache_t::bytesForCapacity(newCapacity), 1); bucket_t *end = cache_t::endMarker(newBuckets, newCapacity); #if __arm__ // End marker's key is 1 and imp points BEFORE the first bucket. // This saves an instruction in objc_msgSend. end->setKey((cache_key_t)(uintptr_t)1); end->setImp((IMP)(newBuckets - 1)); #else // End marker's key is 1 and imp points to the first bucket. end->setKey((cache_key_t)(uintptr_t)1); end->setImp((IMP)newBuckets); #endif if (PrintCaches) recordNewCache(newCapacity); return newBuckets; } #else size_t cache_t::bytesForCapacity(uint32_t cap) { return sizeof(bucket_t) * cap; } bucket_t *allocateBuckets(mask_t newCapacity) { if (PrintCaches) recordNewCache(newCapacity); return (bucket_t *)calloc(cache_t::bytesForCapacity(newCapacity), 1); } #endif bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true) { cacheUpdateLock.assertLocked(); size_t bytes = cache_t::bytesForCapacity(capacity); // Use _objc_empty_cache if the buckets is small enough. if (bytes <= EMPTY_BYTES) { return (bucket_t *)&_objc_empty_cache; } // Use shared empty buckets allocated on the heap. static bucket_t **emptyBucketsList = nil; static mask_t emptyBucketsListCount = 0; mask_t index = log2u(capacity); if (index >= emptyBucketsListCount) { if (!allocate) return nil; mask_t newListCount = index + 1; bucket_t *newBuckets = (bucket_t *)calloc(bytes, 1); emptyBucketsList = (bucket_t**) realloc(emptyBucketsList, newListCount * sizeof(bucket_t *)); // Share newBuckets for every un-allocated size smaller than index. // The array is therefore always fully populated. for (mask_t i = emptyBucketsListCount; i < newListCount; i++) { emptyBucketsList[i] = newBuckets; } emptyBucketsListCount = newListCount; if (PrintCaches) { _objc_inform("CACHES: new empty buckets at %p (capacity %zu)", newBuckets, (size_t)capacity); } } return emptyBucketsList[index]; } bool cache_t::isConstantEmptyCache() { return occupied() == 0 && buckets() == emptyBucketsForCapacity(capacity(), false); } bool cache_t::canBeFreed() { return !isConstantEmptyCache(); } void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity) { bool freeOld = canBeFreed(); bucket_t *oldBuckets = buckets(); bucket_t *newBuckets = allocateBuckets(newCapacity); // Cache's old contents are not propagated. // This is thought to save cache memory at the cost of extra cache fills. // fixme re-measure this assert(newCapacity > 0); assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1); setBucketsAndMask(newBuckets, newCapacity - 1); if (freeOld) { cache_collect_free(oldBuckets, oldCapacity); cache_collect(false); } } void cache_t::bad_cache(id receiver, SEL sel, Class isa) { // Log in separate steps in case the logging itself causes a crash. _objc_inform_now_and_on_crash ("Method cache corrupted. This may be a message to an " "invalid object, or a memory error somewhere else."); cache_t *cache = &isa->cache; _objc_inform_now_and_on_crash ("%s %p, SEL %p, isa %p, cache %p, buckets %p, " "mask 0x%x, occupied 0x%x", receiver ? "receiver" : "unused", receiver, sel, isa, cache, cache->_buckets, cache->_mask, cache->_occupied); _objc_inform_now_and_on_crash ("%s %zu bytes, buckets %zu bytes", receiver ? "receiver" : "unused", malloc_size(receiver), malloc_size(cache->_buckets)); _objc_inform_now_and_on_crash ("selector '%s'", sel_getName(sel)); _objc_inform_now_and_on_crash ("isa '%s'", isa->nameForLogging()); _objc_fatal ("Method cache corrupted. This may be a message to an " "invalid object, or a memory error somewhere else."); } bucket_t * cache_t::find(cache_key_t k, id receiver) { assert(k != 0); bucket_t *b = buckets(); mask_t m = mask(); mask_t begin = cache_hash(k, m); mask_t i = begin; do { if (b[i].key() == 0 || b[i].key() == k) { return &b[i]; } } while ((i = cache_next(i, m)) != begin); // hack Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache)); cache_t::bad_cache(receiver, (SEL)k, cls); } void cache_t::expand() { cacheUpdateLock.assertLocked(); uint32_t oldCapacity = capacity(); uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE; if ((uint32_t)(mask_t)newCapacity != newCapacity) { // mask overflow - can't grow further // fixme this wastes one bit of mask newCapacity = oldCapacity; } reallocate(oldCapacity, newCapacity); } static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver) { cacheUpdateLock.assertLocked(); // Never cache before +initialize is done if (!cls->isInitialized()) return; // Make sure the entry wasn't added to the cache by some other thread // before we grabbed the cacheUpdateLock. if (cache_getImp(cls, sel)) return; cache_t *cache = getCache(cls); cache_key_t key = getKey(sel); // Use the cache as-is if it is less than 3/4 full mask_t newOccupied = cache->occupied() + 1; mask_t capacity = cache->capacity(); if (cache->isConstantEmptyCache()) { // Cache is read-only. Replace it. cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE); } else if (newOccupied <= capacity / 4 * 3) { // Cache is less than 3/4 full. Use it as-is. } else { // Cache is too full. Expand it. cache->expand(); } // Scan for the first unused slot and insert there. // There is guaranteed to be an empty slot because the // minimum size is 4 and we resized at 3/4 full. bucket_t *bucket = cache->find(key, receiver); if (bucket->key() == 0) cache->incrementOccupied(); bucket->set(key, imp); } void cache_fill(Class cls, SEL sel, IMP imp, id receiver) { #if !DEBUG_TASK_THREADS mutex_locker_t lock(cacheUpdateLock); cache_fill_nolock(cls, sel, imp, receiver); #else _collecting_in_critical(); return; #endif } // Reset this entire cache to the uncached lookup by reallocating it. // This must not shrink the cache - that breaks the lock-free scheme. void cache_erase_nolock(Class cls) { cacheUpdateLock.assertLocked(); cache_t *cache = getCache(cls); mask_t capacity = cache->capacity(); if (capacity > 0 && cache->occupied() > 0) { auto oldBuckets = cache->buckets(); auto buckets = emptyBucketsForCapacity(capacity); cache->setBucketsAndMask(buckets, capacity - 1); // also clears occupied cache_collect_free(oldBuckets, capacity); cache_collect(false); } } void cache_delete(Class cls) { mutex_locker_t lock(cacheUpdateLock); if (cls->cache.canBeFreed()) { if (PrintCaches) recordDeadCache(cls->cache.capacity()); free(cls->cache.buckets()); } } /*********************************************************************** * cache collection. **********************************************************************/ #if !TARGET_OS_WIN32 // A sentinel (magic value) to report bad thread_get_state status. // Must not be a valid PC. // Must not be zero - thread_get_state() on a new thread returns PC == 0. #define PC_SENTINEL 1 static uintptr_t _get_pc_for_thread(thread_t thread) #if defined(__i386__) { i386_thread_state_t state; unsigned int count = i386_THREAD_STATE_COUNT; kern_return_t okay = thread_get_state (thread, i386_THREAD_STATE, (thread_state_t)&state, &count); return (okay == KERN_SUCCESS) ? state.__eip : PC_SENTINEL; } #elif defined(__x86_64__) { x86_thread_state64_t state; unsigned int count = x86_THREAD_STATE64_COUNT; kern_return_t okay = thread_get_state (thread, x86_THREAD_STATE64, (thread_state_t)&state, &count); return (okay == KERN_SUCCESS) ? state.__rip : PC_SENTINEL; } #elif defined(__arm__) { arm_thread_state_t state; unsigned int count = ARM_THREAD_STATE_COUNT; kern_return_t okay = thread_get_state (thread, ARM_THREAD_STATE, (thread_state_t)&state, &count); return (okay == KERN_SUCCESS) ? state.__pc : PC_SENTINEL; } #elif defined(__arm64__) { arm_thread_state64_t state; unsigned int count = ARM_THREAD_STATE64_COUNT; kern_return_t okay = thread_get_state (thread, ARM_THREAD_STATE64, (thread_state_t)&state, &count); return (okay == KERN_SUCCESS) ? arm_thread_state64_get_pc(state) : PC_SENTINEL; } #else { #error _get_pc_for_thread () not implemented for this architecture } #endif #endif /*********************************************************************** * _collecting_in_critical. * Returns TRUE if some thread is currently executing a cache-reading * function. Collection of cache garbage is not allowed when a cache- * reading function is in progress because it might still be using * the garbage memory. **********************************************************************/ extern "C" uintptr_t objc_entryPoints[]; extern "C" uintptr_t objc_exitPoints[]; static int _collecting_in_critical(void) { #if TARGET_OS_WIN32 return TRUE; #else thread_act_port_array_t threads; unsigned number; unsigned count; kern_return_t ret; int result; mach_port_t mythread = pthread_mach_thread_np(pthread_self()); // Get a list of all the threads in the current task #if !DEBUG_TASK_THREADS ret = task_threads(mach_task_self(), &threads, &number); #else ret = objc_task_threads(mach_task_self(), &threads, &number); #endif if (ret != KERN_SUCCESS) { // See DEBUG_TASK_THREADS below to help debug this. _objc_fatal("task_threads failed (result 0x%x)\n", ret); } // Check whether any thread is in the cache lookup code result = FALSE; for (count = 0; count < number; count++) { int region; uintptr_t pc; // Don't bother checking ourselves if (threads[count] == mythread) continue; // Find out where thread is executing pc = _get_pc_for_thread (threads[count]); // Check for bad status, and if so, assume the worse (can't collect) if (pc == PC_SENTINEL) { result = TRUE; goto done; } // Check whether it is in the cache lookup code for (region = 0; objc_entryPoints[region] != 0; region++) { if ((pc >= objc_entryPoints[region]) && (pc <= objc_exitPoints[region])) { result = TRUE; goto done; } } } done: // Deallocate the port rights for the threads for (count = 0; count < number; count++) { mach_port_deallocate(mach_task_self (), threads[count]); } // Deallocate the thread list vm_deallocate (mach_task_self (), (vm_address_t) threads, sizeof(threads[0]) * number); // Return our finding return result; #endif } /*********************************************************************** * _garbage_make_room. Ensure that there is enough room for at least * one more ref in the garbage. **********************************************************************/ // amount of memory represented by all refs in the garbage static size_t garbage_byte_size = 0; // do not empty the garbage until garbage_byte_size gets at least this big static size_t garbage_threshold = 32*1024; // table of refs to free static bucket_t **garbage_refs = 0; // current number of refs in garbage_refs static size_t garbage_count = 0; // capacity of current garbage_refs static size_t garbage_max = 0; // capacity of initial garbage_refs enum { INIT_GARBAGE_COUNT = 128 }; static void _garbage_make_room(void) { static int first = 1; // Create the collection table the first time it is needed if (first) { first = 0; garbage_refs = (bucket_t**) malloc(INIT_GARBAGE_COUNT * sizeof(void *)); garbage_max = INIT_GARBAGE_COUNT; } // Double the table if it is full else if (garbage_count == garbage_max) { garbage_refs = (bucket_t**) realloc(garbage_refs, garbage_max * 2 * sizeof(void *)); garbage_max *= 2; } } /*********************************************************************** * cache_collect_free. Add the specified malloc'd memory to the list * of them to free at some later point. * size is used for the collection threshold. It does not have to be * precisely the block's size. * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ static void cache_collect_free(bucket_t *data, mask_t capacity) { cacheUpdateLock.assertLocked(); if (PrintCaches) recordDeadCache(capacity); _garbage_make_room (); garbage_byte_size += cache_t::bytesForCapacity(capacity); garbage_refs[garbage_count++] = data; } /*********************************************************************** * cache_collect. Try to free accumulated dead caches. * collectALot tries harder to free memory. * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ void cache_collect(bool collectALot) { cacheUpdateLock.assertLocked(); // Done if the garbage is not full if (garbage_byte_size < garbage_threshold && !collectALot) { return; } // Synchronize collection with objc_msgSend and other cache readers if (!collectALot) { if (_collecting_in_critical ()) { // objc_msgSend (or other cache reader) is currently looking in // the cache and might still be using some garbage. if (PrintCaches) { _objc_inform ("CACHES: not collecting; " "objc_msgSend in progress"); } return; } } else { // No excuses. while (_collecting_in_critical()) ; } // No cache readers in progress - garbage is now deletable // Log our progress if (PrintCaches) { cache_collections++; _objc_inform ("CACHES: COLLECTING %zu bytes (%zu allocations, %zu collections)", garbage_byte_size, cache_allocations, cache_collections); } // Dispose all refs now in the garbage // Erase each entry so debugging tools don't see stale pointers. while (garbage_count--) { auto dead = garbage_refs[garbage_count]; garbage_refs[garbage_count] = nil; free(dead); } // Clear the garbage count and total size indicator garbage_count = 0; garbage_byte_size = 0; if (PrintCaches) { size_t i; size_t total_count = 0; size_t total_size = 0; for (i = 0; i < countof(cache_counts); i++) { int count = cache_counts[i]; int slots = 1 << i; size_t size = count * slots * sizeof(bucket_t); if (!count) continue; _objc_inform("CACHES: %4d slots: %4d caches, %6zu bytes", slots, count, size); total_count += count; total_size += size; } _objc_inform("CACHES: total: %4zu caches, %6zu bytes", total_count, total_size); } } /*********************************************************************** * objc_task_threads * Replacement for task_threads(). Define DEBUG_TASK_THREADS to debug * crashes when task_threads() is failing. * * A failure in task_threads() usually means somebody has botched their * Mach or MIG traffic. For example, somebody's error handling was wrong * and they left a message queued on the MIG reply port for task_threads() * to trip over. * * The code below is a modified version of task_threads(). It logs * the msgh_id of the reply message. The msgh_id can identify the sender * of the message, which can help pinpoint the faulty code. * DEBUG_TASK_THREADS also calls collecting_in_critical() during every * message dispatch, which can increase reproducibility of bugs. * * This code can be regenerated by running * `mig /usr/include/mach/task.defs`. **********************************************************************/ #if DEBUG_TASK_THREADS #include #include #include #define __MIG_check__Reply__task_subsystem__ 1 #define mig_internal static inline #define __DeclareSendRpc(a, b) #define __BeforeSendRpc(a, b) #define __AfterSendRpc(a, b) #define msgh_request_port msgh_remote_port #define msgh_reply_port msgh_local_port #ifndef __MachMsgErrorWithTimeout #define __MachMsgErrorWithTimeout(_R_) { \ switch (_R_) { \ case MACH_SEND_INVALID_DATA: \ case MACH_SEND_INVALID_DEST: \ case MACH_SEND_INVALID_HEADER: \ mig_put_reply_port(InP->Head.msgh_reply_port); \ break; \ case MACH_SEND_TIMED_OUT: \ case MACH_RCV_TIMED_OUT: \ default: \ mig_dealloc_reply_port(InP->Head.msgh_reply_port); \ } \ } #endif /* __MachMsgErrorWithTimeout */ #ifndef __MachMsgErrorWithoutTimeout #define __MachMsgErrorWithoutTimeout(_R_) { \ switch (_R_) { \ case MACH_SEND_INVALID_DATA: \ case MACH_SEND_INVALID_DEST: \ case MACH_SEND_INVALID_HEADER: \ mig_put_reply_port(InP->Head.msgh_reply_port); \ break; \ default: \ mig_dealloc_reply_port(InP->Head.msgh_reply_port); \ } \ } #endif /* __MachMsgErrorWithoutTimeout */ #if ( __MigTypeCheck ) #if __MIG_check__Reply__task_subsystem__ #if !defined(__MIG_check__Reply__task_threads_t__defined) #define __MIG_check__Reply__task_threads_t__defined mig_internal kern_return_t __MIG_check__Reply__task_threads_t(__Reply__task_threads_t *Out0P) { typedef __Reply__task_threads_t __Reply; boolean_t msgh_simple; #if __MigTypeCheck unsigned int msgh_size; #endif /* __MigTypeCheck */ if (Out0P->Head.msgh_id != 3502) { if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE) { return MIG_SERVER_DIED; } else { return MIG_REPLY_MISMATCH; } } msgh_simple = !(Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX); #if __MigTypeCheck msgh_size = Out0P->Head.msgh_size; if ((msgh_simple || Out0P->msgh_body.msgh_descriptor_count != 1 || msgh_size != (mach_msg_size_t)sizeof(__Reply)) && (!msgh_simple || msgh_size != (mach_msg_size_t)sizeof(mig_reply_error_t) || ((mig_reply_error_t *)Out0P)->RetCode == KERN_SUCCESS)) { return MIG_TYPE_ERROR ; } #endif /* __MigTypeCheck */ if (msgh_simple) { return ((mig_reply_error_t *)Out0P)->RetCode; } #if __MigTypeCheck if (Out0P->act_list.type != MACH_MSG_OOL_PORTS_DESCRIPTOR || Out0P->act_list.disposition != 17) { return MIG_TYPE_ERROR; } #endif /* __MigTypeCheck */ return MACH_MSG_SUCCESS; } #endif /* !defined(__MIG_check__Reply__task_threads_t__defined) */ #endif /* __MIG_check__Reply__task_subsystem__ */ #endif /* ( __MigTypeCheck ) */ /* Routine task_threads */ static kern_return_t objc_task_threads ( task_t target_task, thread_act_array_t *act_list, mach_msg_type_number_t *act_listCnt ) { #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; } Request; #ifdef __MigPackStructs #pragma pack() #endif #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; /* start of the kernel processed data */ mach_msg_body_t msgh_body; mach_msg_ool_ports_descriptor_t act_list; /* end of the kernel processed data */ NDR_record_t NDR; mach_msg_type_number_t act_listCnt; mach_msg_trailer_t trailer; } Reply; #ifdef __MigPackStructs #pragma pack() #endif #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; /* start of the kernel processed data */ mach_msg_body_t msgh_body; mach_msg_ool_ports_descriptor_t act_list; /* end of the kernel processed data */ NDR_record_t NDR; mach_msg_type_number_t act_listCnt; } __Reply; #ifdef __MigPackStructs #pragma pack() #endif /* * typedef struct { * mach_msg_header_t Head; * NDR_record_t NDR; * kern_return_t RetCode; * } mig_reply_error_t; */ union { Request In; Reply Out; } Mess; Request *InP = &Mess.In; Reply *Out0P = &Mess.Out; mach_msg_return_t msg_result; #ifdef __MIG_check__Reply__task_threads_t__defined kern_return_t check_result; #endif /* __MIG_check__Reply__task_threads_t__defined */ __DeclareSendRpc(3402, "task_threads") InP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); /* msgh_size passed as argument */ InP->Head.msgh_request_port = target_task; InP->Head.msgh_reply_port = mig_get_reply_port(); InP->Head.msgh_id = 3402; __BeforeSendRpc(3402, "task_threads") msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, (mach_msg_size_t)sizeof(Request), (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); __AfterSendRpc(3402, "task_threads") if (msg_result != MACH_MSG_SUCCESS) { _objc_inform("task_threads received unexpected reply msgh_id 0x%zx", (size_t)Out0P->Head.msgh_id); __MachMsgErrorWithoutTimeout(msg_result); { return msg_result; } } #if defined(__MIG_check__Reply__task_threads_t__defined) check_result = __MIG_check__Reply__task_threads_t((__Reply__task_threads_t *)Out0P); if (check_result != MACH_MSG_SUCCESS) { return check_result; } #endif /* defined(__MIG_check__Reply__task_threads_t__defined) */ *act_list = (thread_act_array_t)(Out0P->act_list.address); *act_listCnt = Out0P->act_listCnt; return KERN_SUCCESS; } // DEBUG_TASK_THREADS #endif // __OBJC2__ #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-class-old.mm ================================================ /* * Copyright (c) 1999-2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-class-old.m * Support for old-ABI classes, methods, and categories. **********************************************************************/ #if !__OBJC2__ #include "objc-private.h" #include "objc-runtime-old.h" #include "objc-file-old.h" #include "objc-cache-old.h" static Method _class_getMethod(Class cls, SEL sel); static Method _class_getMethodNoSuper(Class cls, SEL sel); static Method _class_getMethodNoSuper_nolock(Class cls, SEL sel); static void flush_caches(Class cls, bool flush_meta); // Freed objects have their isa set to point to this dummy class. // This avoids the need to check for Nil classes in the messenger. static const void* freedObjectClass[12] = { Nil, // isa Nil, // superclass "FREED(id)", // name 0, // version 0, // info 0, // instance_size nil, // ivars nil, // methodLists (Cache) &_objc_empty_cache, // cache nil, // protocols nil, // ivar_layout; nil // ext }; /*********************************************************************** * _class_getFreedObjectClass. Return a pointer to the dummy freed * object class. Freed objects get their isa pointers replaced with * a pointer to the freedObjectClass, so that we can catch usages of * the freed object. **********************************************************************/ static Class _class_getFreedObjectClass(void) { return (Class)freedObjectClass; } /*********************************************************************** * _objc_getFreedObjectClass. Return a pointer to the dummy freed * object class. Freed objects get their isa pointers replaced with * a pointer to the freedObjectClass, so that we can catch usages of * the freed object. **********************************************************************/ Class _objc_getFreedObjectClass(void) { return _class_getFreedObjectClass(); } static void allocateExt(Class cls) { if (! (cls->info & CLS_EXT)) { _objc_inform("class '%s' needs to be recompiled", cls->name); return; } if (!cls->ext) { uint32_t size = (uint32_t)sizeof(old_class_ext); cls->ext = (old_class_ext *)calloc(size, 1); cls->ext->size = size; } } static inline old_method *_findNamedMethodInList(old_method_list * mlist, const char *meth_name) { int i; if (!mlist) return nil; for (i = 0; i < mlist->method_count; i++) { old_method *m = &mlist->method_list[i]; if (0 == strcmp((const char *)(m->method_name), meth_name)) { return m; } } return nil; } /*********************************************************************** * Method list fixup markers. * mlist->obsolete == fixed_up_method_list marks method lists with real SELs * versus method lists with un-uniqued char*. * PREOPTIMIZED VERSION: * Fixed-up method lists get mlist->obsolete == OBJC_FIXED_UP * dyld shared cache sets this for method lists it preoptimizes. * UN-PREOPTIMIZED VERSION * Fixed-up method lists get mlist->obsolete == OBJC_FIXED_UP_outside_dyld * dyld shared cache uses OBJC_FIXED_UP, but those aren't trusted. **********************************************************************/ #define OBJC_FIXED_UP ((void *)1771) #define OBJC_FIXED_UP_outside_dyld ((void *)1773) static void *fixed_up_method_list = OBJC_FIXED_UP; // sel_init() decided that selectors in the dyld shared cache are untrustworthy void disableSharedCacheOptimizations(void) { fixed_up_method_list = OBJC_FIXED_UP_outside_dyld; } /*********************************************************************** * fixupSelectorsInMethodList * Uniques selectors in the given method list. * The given method list must be non-nil and not already fixed-up. * If the class was loaded from a bundle: * fixes up the given list in place with heap-allocated selector strings * If the class was not from a bundle: * allocates a copy of the method list, fixes up the copy, and returns * the copy. The given list is unmodified. * * If cls is already in use, methodListLock must be held by the caller. **********************************************************************/ static old_method_list *fixupSelectorsInMethodList(Class cls, old_method_list *mlist) { int i; size_t size; old_method *method; old_method_list *old_mlist; if ( ! mlist ) return nil; if ( mlist->obsolete == fixed_up_method_list ) { // method list OK } else { bool isBundle = cls->info & CLS_FROM_BUNDLE; if (!isBundle) { old_mlist = mlist; size = sizeof(old_method_list) - sizeof(old_method) + old_mlist->method_count * sizeof(old_method); mlist = (old_method_list *)malloc(size); memmove(mlist, old_mlist, size); } else { // Mach-O bundles are fixed up in place. // This prevents leaks when a bundle is unloaded. } mutex_locker_t lock(selLock); for ( i = 0; i < mlist->method_count; i += 1 ) { method = &mlist->method_list[i]; method->method_name = sel_registerNameNoLock((const char *)method->method_name, isBundle); // Always copy selector data from bundles. } mlist->obsolete = fixed_up_method_list; } return mlist; } /*********************************************************************** * nextMethodList * Returns successive method lists from the given class. * Method lists are returned in method search order (i.e. highest-priority * implementations first). * All necessary method list fixups are performed, so the * returned method list is fully-constructed. * * If cls is already in use, methodListLock must be held by the caller. * For full thread-safety, methodListLock must be continuously held by the * caller across all calls to nextMethodList(). If the lock is released, * the bad results listed in class_nextMethodList() may occur. * * void *iterator = nil; * old_method_list *mlist; * mutex_locker_t lock(methodListLock); * while ((mlist = nextMethodList(cls, &iterator))) { * // do something with mlist * } **********************************************************************/ static old_method_list *nextMethodList(Class cls, void **it) { uintptr_t index = *(uintptr_t *)it; old_method_list **resultp; if (index == 0) { // First call to nextMethodList. if (!cls->methodLists) { resultp = nil; } else if (cls->info & CLS_NO_METHOD_ARRAY) { resultp = (old_method_list **)&cls->methodLists; } else { resultp = &cls->methodLists[0]; if (!*resultp || *resultp == END_OF_METHODS_LIST) { resultp = nil; } } } else { // Subsequent call to nextMethodList. if (!cls->methodLists) { resultp = nil; } else if (cls->info & CLS_NO_METHOD_ARRAY) { resultp = nil; } else { resultp = &cls->methodLists[index]; if (!*resultp || *resultp == END_OF_METHODS_LIST) { resultp = nil; } } } // resultp now is nil, meaning there are no more method lists, // OR the address of the method list pointer to fix up and return. if (resultp) { if (*resultp) { *resultp = fixupSelectorsInMethodList(cls, *resultp); } *it = (void *)(index + 1); return *resultp; } else { *it = 0; return nil; } } /* These next three functions are the heart of ObjC method lookup. * If the class is currently in use, methodListLock must be held by the caller. */ static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) { int i; if (!mlist) return nil; for (i = 0; i < mlist->method_count; i++) { old_method *m = &mlist->method_list[i]; if (m->method_name == sel) { return m; } } return nil; } static inline old_method * _findMethodInClass(Class cls, SEL sel) __attribute__((always_inline)); static inline old_method * _findMethodInClass(Class cls, SEL sel) { // Flattened version of nextMethodList(). The optimizer doesn't // do a good job with hoisting the conditionals out of the loop. // Conceptually, this looks like: // while ((mlist = nextMethodList(cls, &iterator))) { // old_method *m = _findMethodInList(mlist, sel); // if (m) return m; // } if (!cls->methodLists) { // No method lists. return nil; } else if (cls->info & CLS_NO_METHOD_ARRAY) { // One method list. old_method_list **mlistp; mlistp = (old_method_list **)&cls->methodLists; *mlistp = fixupSelectorsInMethodList(cls, *mlistp); return _findMethodInList(*mlistp, sel); } else { // Multiple method lists. old_method_list **mlistp; for (mlistp = cls->methodLists; *mlistp != nil && *mlistp != END_OF_METHODS_LIST; mlistp++) { old_method *m; *mlistp = fixupSelectorsInMethodList(cls, *mlistp); m = _findMethodInList(*mlistp, sel); if (m) return m; } return nil; } } static inline old_method * _getMethod(Class cls, SEL sel) { for (; cls; cls = cls->superclass) { old_method *m; m = _findMethodInClass(cls, sel); if (m) return m; } return nil; } // called by a debugging check in _objc_insertMethods IMP findIMPInClass(Class cls, SEL sel) { old_method *m = _findMethodInClass(cls, sel); if (m) return m->method_imp; else return nil; } /*********************************************************************** * _freedHandler. **********************************************************************/ static void _freedHandler(id obj, SEL sel) { __objc_error (obj, "message %s sent to freed object=%p", sel_getName(sel), (void*)obj); } /*********************************************************************** * log_and_fill_cache * Log this method call. If the logger permits it, fill the method cache. * cls is the method whose cache should be filled. * implementer is the class that owns the implementation in question. **********************************************************************/ static void log_and_fill_cache(Class cls, Class implementer, Method meth, SEL sel) { #if SUPPORT_MESSAGE_LOGGING if (objcMsgLogEnabled) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), sel); if (!cacheIt) return; } #endif _cache_fill (cls, meth, sel); } /*********************************************************************** * _class_lookupMethodAndLoadCache. * Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp(). * This lookup avoids optimistic cache scan because the dispatcher * already tried that. **********************************************************************/ IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) { return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/); } /*********************************************************************** * lookUpImpOrForward. * The standard IMP lookup. * initialize==NO tries to avoid +initialize (but sometimes fails) * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere) * Most callers should use initialize==YES and cache==YES. * inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. * If you don't want forwarding at all, use lookUpImpOrNil() instead. **********************************************************************/ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { Class curClass; IMP methodPC = nil; Method meth; bool triedResolver = NO; methodListLock.assertUnlocked(); // Optimistic cache lookup if (cache) { methodPC = _cache_getImp(cls, sel); if (methodPC) return methodPC; } // Check for freed class if (cls == _class_getFreedObjectClass()) return (IMP) _freedHandler; // Check for +initialize if (initialize && !cls->isInitialized()) { _class_initialize (_class_getNonMetaClass(cls, inst)); // If sel == initialize, _class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172 } // The lock is held to make method-lookup + cache-fill atomic // with respect to method addition. Otherwise, a category could // be added but ignored indefinitely because the cache was re-filled // with the old value after the cache flush on behalf of the category. retry: methodListLock.lock(); // Try this class's cache. methodPC = _cache_getImp(cls, sel); if (methodPC) goto done; // Try this class's method lists. meth = _class_getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, cls, meth, sel); methodPC = method_getImplementation(meth); goto done; } // Try superclass caches and method lists. curClass = cls; while ((curClass = curClass->superclass)) { // Superclass cache. meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache); if (meth) { if (meth != (Method)1) { // Found the method in a superclass. Cache it in this class. log_and_fill_cache(cls, curClass, meth, sel); methodPC = method_getImplementation(meth); goto done; } else { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. meth = _class_getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, curClass, meth, sel); methodPC = method_getImplementation(meth); goto done; } } // No implementation found. Try method resolver once. if (resolver && !triedResolver) { methodListLock.unlock(); _class_resolveMethod(cls, sel, inst); triedResolver = YES; goto retry; } // No implementation found, and method resolver didn't help. // Use forwarding. _cache_addForwardEntry(cls, sel); methodPC = _objc_msgForward_impcache; done: methodListLock.unlock(); return methodPC; } /*********************************************************************** * lookUpImpOrNil. * Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache **********************************************************************/ IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver); if (imp == _objc_msgForward_impcache) return nil; else return imp; } /*********************************************************************** * lookupMethodInClassAndLoadCache. * Like _class_lookupMethodAndLoadCache, but does not search superclasses. * Caches and returns objc_msgForward if the method is not found in the class. **********************************************************************/ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) { Method meth; IMP imp; // fixme this still has the method list vs method cache race // because it doesn't hold a lock across lookup+cache_fill, // but it's only used for .cxx_construct/destruct and we assume // categories don't change them. // Search cache first. imp = _cache_getImp(cls, sel); if (imp) return imp; // Cache miss. Search method list. meth = _class_getMethodNoSuper(cls, sel); if (meth) { // Hit in method list. Cache it. _cache_fill(cls, meth, sel); return method_getImplementation(meth); } else { // Miss in method list. Cache objc_msgForward. _cache_addForwardEntry(cls, sel); return _objc_msgForward_impcache; } } /*********************************************************************** * _class_getClassForIvar * Given a class and an ivar that is in it or one of its superclasses, * find the actual class that defined the ivar. **********************************************************************/ Class _class_getClassForIvar(Class cls, Ivar ivar) { for ( ; cls; cls = cls->superclass) { if (auto ivars = cls->ivars) { if (ivar >= &ivars->ivar_list[0] && ivar < &ivars->ivar_list[ivars->ivar_count]) { return cls; } } } return nil; } /*********************************************************************** * class_getVariable. Return the named instance variable. **********************************************************************/ Ivar _class_getVariable(Class cls, const char *name) { for (; cls != Nil; cls = cls->superclass) { int i; // Skip class having no ivars if (!cls->ivars) continue; for (i = 0; i < cls->ivars->ivar_count; i++) { // Check this ivar's name. Be careful because the // compiler generates ivar entries with nil ivar_name // (e.g. for anonymous bit fields). old_ivar *ivar = &cls->ivars->ivar_list[i]; if (ivar->ivar_name && 0 == strcmp(name, ivar->ivar_name)) { return (Ivar)ivar; } } } // Not found return nil; } old_property * property_list_nth(const old_property_list *plist, uint32_t i) { return (old_property *)(i*plist->entsize + (char *)&plist->first); } old_property ** copyPropertyList(old_property_list *plist, unsigned int *outCount) { old_property **result = nil; unsigned int count = 0; if (plist) { count = plist->count; } if (count > 0) { unsigned int i; result = (old_property **)malloc((count+1) * sizeof(old_property *)); for (i = 0; i < count; i++) { result[i] = property_list_nth(plist, i); } result[i] = nil; } if (outCount) *outCount = count; return result; } static old_property_list * nextPropertyList(Class cls, uintptr_t *indexp) { old_property_list *result = nil; classLock.assertLocked(); if (! ((cls->info & CLS_EXT) && cls->ext)) { // No class ext result = nil; } else if (!cls->ext->propertyLists) { // No property lists result = nil; } else if (cls->info & CLS_NO_PROPERTY_ARRAY) { // Only one property list if (*indexp == 0) { result = (old_property_list *)cls->ext->propertyLists; } else { result = nil; } } else { // More than one property list result = cls->ext->propertyLists[*indexp]; } if (result) { ++*indexp; return result; } else { *indexp = 0; return nil; } } /*********************************************************************** * class_getIvarLayout * nil means all-scanned. "" means non-scanned. **********************************************************************/ const uint8_t * class_getIvarLayout(Class cls) { if (cls && (cls->info & CLS_EXT)) { return cls->ivar_layout; } else { return nil; // conservative scan } } /*********************************************************************** * class_getWeakIvarLayout * nil means no weak ivars. **********************************************************************/ const uint8_t * class_getWeakIvarLayout(Class cls) { if (cls && (cls->info & CLS_EXT) && cls->ext) { return cls->ext->weak_ivar_layout; } else { return nil; // no weak ivars } } /*********************************************************************** * class_setIvarLayout * nil means all-scanned. "" means non-scanned. **********************************************************************/ void class_setIvarLayout(Class cls, const uint8_t *layout) { if (!cls) return; if (! (cls->info & CLS_EXT)) { _objc_inform("class '%s' needs to be recompiled", cls->name); return; } // fixme leak cls->ivar_layout = ustrdupMaybeNil(layout); } /*********************************************************************** * class_setWeakIvarLayout * nil means no weak ivars. **********************************************************************/ void class_setWeakIvarLayout(Class cls, const uint8_t *layout) { if (!cls) return; mutex_locker_t lock(classLock); allocateExt(cls); // fixme leak cls->ext->weak_ivar_layout = ustrdupMaybeNil(layout); } /*********************************************************************** * class_setVersion. Record the specified version with the class. **********************************************************************/ void class_setVersion(Class cls, int version) { if (!cls) return; cls->version = version; } /*********************************************************************** * class_getVersion. Return the version recorded with the class. **********************************************************************/ int class_getVersion(Class cls) { if (!cls) return 0; return (int)cls->version; } /*********************************************************************** * class_getName. **********************************************************************/ const char *class_getName(Class cls) { if (!cls) return "nil"; else return cls->demangledName(); } /*********************************************************************** * _class_getNonMetaClass. * Return the ordinary class for this class or metaclass. * Used by +initialize. **********************************************************************/ Class _class_getNonMetaClass(Class cls, id obj) { // fixme ick if (cls->isMetaClass()) { if (cls->info & CLS_CONSTRUCTING) { // Class is under construction and isn't in the class_hash, // so objc_getClass doesn't work. cls = obj; // fixme this may be nil in some paths } else if (strncmp(cls->name, "_%", 2) == 0) { // Posee's meta's name is smashed and isn't in the class_hash, // so objc_getClass doesn't work. const char *baseName = strchr(cls->name, '%'); // get posee's real name cls = objc_getClass(baseName); } else { cls = objc_getClass(cls->name); } assert(cls); } return cls; } Cache _class_getCache(Class cls) { return cls->cache; } void _class_setCache(Class cls, Cache cache) { cls->cache = cache; } const char *_category_getName(Category cat) { return oldcategory(cat)->category_name; } const char *_category_getClassName(Category cat) { return oldcategory(cat)->class_name; } Class _category_getClass(Category cat) { return objc_getClass(oldcategory(cat)->class_name); } IMP _category_getLoadMethod(Category cat) { old_method_list *mlist = oldcategory(cat)->class_methods; if (mlist) { return lookupNamedMethodInMethodList(mlist, "load"); } else { return nil; } } /*********************************************************************** * class_nextMethodList. * External version of nextMethodList(). * * This function is not fully thread-safe. A series of calls to * class_nextMethodList() may fail if methods are added to or removed * from the class between calls. * If methods are added between calls to class_nextMethodList(), it may * return previously-returned method lists again, and may fail to return * newly-added lists. * If methods are removed between calls to class_nextMethodList(), it may * omit surviving method lists or simply crash. **********************************************************************/ struct objc_method_list *class_nextMethodList(Class cls, void **it) { OBJC_WARN_DEPRECATED; mutex_locker_t lock(methodListLock); return (struct objc_method_list *) nextMethodList(cls, it); } /*********************************************************************** * class_addMethods. * * Formerly class_addInstanceMethods () **********************************************************************/ void class_addMethods(Class cls, struct objc_method_list *meths) { OBJC_WARN_DEPRECATED; // Add the methods. { mutex_locker_t lock(methodListLock); _objc_insertMethods(cls, (old_method_list *)meths, nil); } // Must flush when dynamically adding methods. No need to flush // all the class method caches. If cls is a meta class, though, // this will still flush it and any of its sub-meta classes. flush_caches (cls, NO); } /*********************************************************************** * class_removeMethods. **********************************************************************/ void class_removeMethods(Class cls, struct objc_method_list *meths) { OBJC_WARN_DEPRECATED; // Remove the methods { mutex_locker_t lock(methodListLock); _objc_removeMethods(cls, (old_method_list *)meths); } // Must flush when dynamically removing methods. No need to flush // all the class method caches. If cls is a meta class, though, // this will still flush it and any of its sub-meta classes. flush_caches (cls, NO); } /*********************************************************************** * lookupNamedMethodInMethodList * Only called to find +load/-.cxx_construct/-.cxx_destruct methods, * without fixing up the entire method list. * The class is not yet in use, so methodListLock is not taken. **********************************************************************/ IMP lookupNamedMethodInMethodList(old_method_list *mlist, const char *meth_name) { old_method *m; m = meth_name ? _findNamedMethodInList(mlist, meth_name) : nil; return (m ? m->method_imp : nil); } static Method _class_getMethod(Class cls, SEL sel) { mutex_locker_t lock(methodListLock); return (Method)_getMethod(cls, sel); } static Method _class_getMethodNoSuper(Class cls, SEL sel) { mutex_locker_t lock(methodListLock); return (Method)_findMethodInClass(cls, sel); } static Method _class_getMethodNoSuper_nolock(Class cls, SEL sel) { methodListLock.assertLocked(); return (Method)_findMethodInClass(cls, sel); } /*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/ Method class_getInstanceMethod(Class cls, SEL sel) { if (!cls || !sel) return nil; // This deliberately avoids +initialize because it historically did so. // This implementation is a bit weird because it's the only place that // wants a Method instead of an IMP. Method meth; meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache); if (meth == (Method)1) { // Cache contains forward:: . Stop searching. return nil; } else if (meth) { return meth; } // Search method lists, try method resolver, etc. lookUpImpOrNil(cls, sel, nil, NO/*initialize*/, NO/*cache*/, YES/*resolver*/); meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache); if (meth == (Method)1) { // Cache contains forward:: . Stop searching. return nil; } else if (meth) { return meth; } return _class_getMethod(cls, sel); } BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) { old_protocol *proto = oldprotocol(proto_gen); if (!cls) return NO; if (!proto) return NO; if (cls->ISA()->version >= 3) { old_protocol_list *list; for (list = cls->protocols; list != nil; list = list->next) { int i; for (i = 0; i < list->count; i++) { if (list->list[i] == proto) return YES; if (protocol_conformsToProtocol((Protocol *)list->list[i], proto_gen)) return YES; } if (cls->ISA()->version <= 4) break; } } return NO; } static NXMapTable * posed_class_hash = nil; /*********************************************************************** * objc_getOrigClass. **********************************************************************/ extern "C" Class _objc_getOrigClass(const char *name) { // Look for class among the posers { mutex_locker_t lock(classLock); if (posed_class_hash) { Class cls = (Class) NXMapGet (posed_class_hash, name); if (cls) return cls; } } // Not a poser. Do a normal lookup. Class cls = objc_getClass (name); if (cls) return cls; _objc_inform ("class `%s' not linked into application", name); return nil; } Class objc_getOrigClass(const char *name) { OBJC_WARN_DEPRECATED; return _objc_getOrigClass(name); } /*********************************************************************** * _objc_addOrigClass. This function is only used from class_poseAs. * Registers the original class names, before they get obscured by * posing, so that [super ..] will work correctly from categories * in posing classes and in categories in classes being posed for. **********************************************************************/ static void _objc_addOrigClass (Class origClass) { mutex_locker_t lock(classLock); // Create the poser's hash table on first use if (!posed_class_hash) { posed_class_hash = NXCreateMapTable(NXStrValueMapPrototype, 8); } // Add the named class iff it is not already there (or collides?) if (NXMapGet (posed_class_hash, origClass->name) == 0) NXMapInsert (posed_class_hash, origClass->name, origClass); } /*********************************************************************** * change_class_references * Change classrefs and superclass pointers from original to imposter * But if copy!=nil, don't change copy->superclass. * If changeSuperRefs==YES, also change [super message] classrefs. * Used by class_poseAs and objc_setFutureClass * classLock must be locked. **********************************************************************/ void change_class_references(Class imposter, Class original, Class copy, bool changeSuperRefs) { header_info *hInfo; Class clsObject; NXHashState state; // Change all subclasses of the original to point to the imposter. state = NXInitHashState (class_hash); while (NXNextHashState (class_hash, &state, (void **) &clsObject)) { while ((clsObject) && (clsObject != imposter) && (clsObject != copy)) { if (clsObject->superclass == original) { clsObject->superclass = imposter; clsObject->ISA()->superclass = imposter->ISA(); // We must flush caches here! break; } clsObject = clsObject->superclass; } } // Replace the original with the imposter in all class refs // Major loop - process all headers for (hInfo = FirstHeader; hInfo != nil; hInfo = hInfo->getNext()) { Class *cls_refs; size_t refCount; unsigned int index; // Fix class refs associated with this header cls_refs = _getObjcClassRefs(hInfo, &refCount); if (cls_refs) { for (index = 0; index < refCount; index += 1) { if (cls_refs[index] == original) { cls_refs[index] = imposter; } } } } } /*********************************************************************** * class_poseAs. * * !!! class_poseAs () does not currently flush any caches. **********************************************************************/ Class class_poseAs(Class imposter, Class original) { char * imposterNamePtr; Class copy; OBJC_WARN_DEPRECATED; // Trivial case is easy if (imposter == original) return imposter; // Imposter must be an immediate subclass of the original if (imposter->superclass != original) { __objc_error(imposter, "[%s poseAs:%s]: target not immediate superclass", imposter->name, original->name); } // Can't pose when you have instance variables (how could it work?) if (imposter->ivars) { __objc_error(imposter, "[%s poseAs:%s]: %s defines new instance variables", imposter->name, original->name, imposter->name); } // Build a string to use to replace the name of the original class. #if TARGET_OS_WIN32 # define imposterNamePrefix "_%" imposterNamePtr = malloc(strlen(original->name) + strlen(imposterNamePrefix) + 1); strcpy(imposterNamePtr, imposterNamePrefix); strcat(imposterNamePtr, original->name); # undef imposterNamePrefix #else asprintf(&imposterNamePtr, "_%%%s", original->name); #endif // We lock the class hashtable, so we are thread safe with respect to // calls to objc_getClass (). However, the class names are not // changed atomically, nor are all of the subclasses updated // atomically. I have ordered the operations so that you will // never crash, but you may get inconsistent results.... // Register the original class so that [super ..] knows // exactly which classes are the "original" classes. _objc_addOrigClass (original); _objc_addOrigClass (imposter); // Copy the imposter, so that the imposter can continue // its normal life in addition to changing the behavior of // the original. As a hack we don't bother to copy the metaclass. // For some reason we modify the original rather than the copy. copy = (Class)malloc(sizeof(objc_class)); memmove(copy, imposter, sizeof(objc_class)); mutex_locker_t lock(classLock); // Remove both the imposter and the original class. NXHashRemove (class_hash, imposter); NXHashRemove (class_hash, original); NXHashInsert (class_hash, copy); // Mark the imposter as such imposter->setInfo(CLS_POSING); imposter->ISA()->setInfo(CLS_POSING); // Change the name of the imposter to that of the original class. imposter->name = original->name; imposter->ISA()->name = original->ISA()->name; // Also copy the version field to avoid archiving problems. imposter->version = original->version; // Change classrefs and superclass pointers // Don't change copy->superclass // Don't change [super ...] messages change_class_references(imposter, original, copy, NO); // Change the name of the original class. original->name = imposterNamePtr + 1; original->ISA()->name = imposterNamePtr; // Restore the imposter and the original class with their new names. NXHashInsert (class_hash, imposter); NXHashInsert (class_hash, original); return imposter; } /*********************************************************************** * _objc_flush_caches. Flush the instance and class method caches * of cls and all its subclasses. * * Specifying Nil for the class "all classes." **********************************************************************/ static void flush_caches(Class target, bool flush_meta) { bool collectALot = (target == nil); NXHashState state; Class clsObject; #ifdef OBJC_INSTRUMENTED unsigned int classesVisited; unsigned int subclassCount; #endif mutex_locker_t lock(classLock); mutex_locker_t lock2(cacheUpdateLock); // Leaf classes are fastest because there are no subclass caches to flush. // fixme instrument if (target && (target->info & CLS_LEAF)) { _cache_flush (target); if (target->ISA() && (target->ISA()->info & CLS_LEAF)) { _cache_flush (target->ISA()); return; // done } else { // Reset target and handle it by one of the methods below. target = target->ISA(); flush_meta = NO; // NOT done } } state = NXInitHashState(class_hash); // Handle nil and root instance class specially: flush all // instance and class method caches. Nice that this // loop is linear vs the N-squared loop just below. if (!target || !target->superclass) { #ifdef OBJC_INSTRUMENTED LinearFlushCachesCount += 1; classesVisited = 0; subclassCount = 0; #endif // Traverse all classes in the hash table while (NXNextHashState(class_hash, &state, (void**)&clsObject)) { Class metaClsObject; #ifdef OBJC_INSTRUMENTED classesVisited += 1; #endif // Skip class that is known not to be a subclass of this root // (the isa pointer of any meta class points to the meta class // of the root). // NOTE: When is an isa pointer of a hash tabled class ever nil? metaClsObject = clsObject->ISA(); if (target && metaClsObject && target->ISA() != metaClsObject->ISA()) { continue; } #ifdef OBJC_INSTRUMENTED subclassCount += 1; #endif _cache_flush (clsObject); if (flush_meta && metaClsObject != nil) { _cache_flush (metaClsObject); } } #ifdef OBJC_INSTRUMENTED LinearFlushCachesVisitedCount += classesVisited; if (classesVisited > MaxLinearFlushCachesVisitedCount) MaxLinearFlushCachesVisitedCount = classesVisited; IdealFlushCachesCount += subclassCount; if (subclassCount > MaxIdealFlushCachesCount) MaxIdealFlushCachesCount = subclassCount; #endif goto done; } // Outer loop - flush any cache that could now get a method from // cls (i.e. the cache associated with cls and any of its subclasses). #ifdef OBJC_INSTRUMENTED NonlinearFlushCachesCount += 1; classesVisited = 0; subclassCount = 0; #endif while (NXNextHashState(class_hash, &state, (void**)&clsObject)) { Class clsIter; #ifdef OBJC_INSTRUMENTED NonlinearFlushCachesClassCount += 1; #endif // Inner loop - Process a given class clsIter = clsObject; while (clsIter) { #ifdef OBJC_INSTRUMENTED classesVisited += 1; #endif // Flush clsObject instance method cache if // clsObject is a subclass of cls, or is cls itself // Flush the class method cache if that was asked for if (clsIter == target) { #ifdef OBJC_INSTRUMENTED subclassCount += 1; #endif _cache_flush (clsObject); if (flush_meta) _cache_flush (clsObject->ISA()); break; } // Flush clsObject class method cache if cls is // the meta class of clsObject or of one // of clsObject's superclasses else if (clsIter->ISA() == target) { #ifdef OBJC_INSTRUMENTED subclassCount += 1; #endif _cache_flush (clsObject->ISA()); break; } // Move up superclass chain // else if (clsIter->isInitialized()) clsIter = clsIter->superclass; // clsIter is not initialized, so its cache // must be empty. This happens only when // clsIter == clsObject, because // superclasses are initialized before // subclasses, and this loop traverses // from sub- to super- classes. // else // break; } } #ifdef OBJC_INSTRUMENTED NonlinearFlushCachesVisitedCount += classesVisited; if (classesVisited > MaxNonlinearFlushCachesVisitedCount) MaxNonlinearFlushCachesVisitedCount = classesVisited; IdealFlushCachesCount += subclassCount; if (subclassCount > MaxIdealFlushCachesCount) MaxIdealFlushCachesCount = subclassCount; #endif done: if (collectALot) { _cache_collect(true); } } void _objc_flush_caches(Class target) { flush_caches(target, YES); } /*********************************************************************** * flush_marked_caches. Flush the method cache of any class marked * CLS_FLUSH_CACHE (and all subclasses thereof) * fixme instrument **********************************************************************/ void flush_marked_caches(void) { Class cls; Class supercls; NXHashState state; mutex_locker_t lock(classLock); mutex_locker_t lock2(cacheUpdateLock); state = NXInitHashState(class_hash); while (NXNextHashState(class_hash, &state, (void**)&cls)) { for (supercls = cls; supercls; supercls = supercls->superclass) { if (supercls->info & CLS_FLUSH_CACHE) { _cache_flush(cls); break; } } for (supercls = cls->ISA(); supercls; supercls = supercls->superclass) { if (supercls->info & CLS_FLUSH_CACHE) { _cache_flush(cls->ISA()); break; } } } state = NXInitHashState(class_hash); while (NXNextHashState(class_hash, &state, (void**)&cls)) { if (cls->info & CLS_FLUSH_CACHE) { cls->clearInfo(CLS_FLUSH_CACHE); } if (cls->ISA()->info & CLS_FLUSH_CACHE) { cls->ISA()->clearInfo(CLS_FLUSH_CACHE); } } } /*********************************************************************** * get_base_method_list * Returns the method list containing the class's own methods, * ignoring any method lists added by categories or class_addMethods. * Called only by add_class_to_loadable_list. * Does not hold methodListLock because add_class_to_loadable_list * does not manipulate in-use classes. **********************************************************************/ static old_method_list *get_base_method_list(Class cls) { old_method_list **ptr; if (!cls->methodLists) return nil; if (cls->info & CLS_NO_METHOD_ARRAY) return (old_method_list *)cls->methodLists; ptr = cls->methodLists; if (!*ptr || *ptr == END_OF_METHODS_LIST) return nil; while ( *ptr != 0 && *ptr != END_OF_METHODS_LIST ) { ptr++; } --ptr; return *ptr; } static IMP _class_getLoadMethod_nocheck(Class cls) { old_method_list *mlist; mlist = get_base_method_list(cls->ISA()); if (mlist) { return lookupNamedMethodInMethodList (mlist, "load"); } return nil; } bool _class_hasLoadMethod(Class cls) { if (cls->ISA()->info & CLS_HAS_LOAD_METHOD) return YES; return _class_getLoadMethod_nocheck(cls); } /*********************************************************************** * objc_class::getLoadMethod * Returns cls's +load implementation, or nil if it doesn't have one. **********************************************************************/ IMP objc_class::getLoadMethod() { if (ISA()->info & CLS_HAS_LOAD_METHOD) { return _class_getLoadMethod_nocheck((Class)this); } return nil; } ptrdiff_t ivar_getOffset(Ivar ivar) { return oldivar(ivar)->ivar_offset; } const char *ivar_getName(Ivar ivar) { return oldivar(ivar)->ivar_name; } const char *ivar_getTypeEncoding(Ivar ivar) { return oldivar(ivar)->ivar_type; } IMP method_getImplementation(Method m) { if (!m) return nil; return oldmethod(m)->method_imp; } SEL method_getName(Method m) { if (!m) return nil; return oldmethod(m)->method_name; } const char *method_getTypeEncoding(Method m) { if (!m) return nil; return oldmethod(m)->method_types; } unsigned int method_getSizeOfArguments(Method m) { OBJC_WARN_DEPRECATED; if (!m) return 0; return encoding_getSizeOfArguments(method_getTypeEncoding(m)); } // This function was accidentally un-exported beginning in macOS 10.9. // As of macOS 10.13 nobody had complained. /* unsigned int method_getArgumentInfo(Method m, int arg, const char **type, int *offset) { OBJC_WARN_DEPRECATED; if (!m) return 0; return encoding_getArgumentInfo(method_getTypeEncoding(m), arg, type, offset); } */ spinlock_t impLock; IMP method_setImplementation(Method m_gen, IMP imp) { IMP old; old_method *m = oldmethod(m_gen); if (!m) return nil; if (!imp) return nil; impLock.lock(); old = m->method_imp; m->method_imp = imp; impLock.unlock(); return old; } void method_exchangeImplementations(Method m1_gen, Method m2_gen) { IMP m1_imp; old_method *m1 = oldmethod(m1_gen); old_method *m2 = oldmethod(m2_gen); if (!m1 || !m2) return; impLock.lock(); m1_imp = m1->method_imp; m1->method_imp = m2->method_imp; m2->method_imp = m1_imp; impLock.unlock(); } struct objc_method_description * method_getDescription(Method m) { if (!m) return nil; return (struct objc_method_description *)oldmethod(m); } const char *property_getName(objc_property_t prop) { return oldproperty(prop)->name; } const char *property_getAttributes(objc_property_t prop) { return oldproperty(prop)->attributes; } objc_property_attribute_t *property_copyAttributeList(objc_property_t prop, unsigned int *outCount) { if (!prop) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(classLock); return copyPropertyAttributeList(oldproperty(prop)->attributes,outCount); } char * property_copyAttributeValue(objc_property_t prop, const char *name) { if (!prop || !name || *name == '\0') return nil; mutex_locker_t lock(classLock); return copyPropertyAttributeValue(oldproperty(prop)->attributes, name); } /*********************************************************************** * class_addMethod **********************************************************************/ static IMP _class_addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace) { old_method *m; IMP result = nil; if (!types) types = ""; mutex_locker_t lock(methodListLock); if ((m = _findMethodInClass(cls, name))) { // already exists // fixme atomic result = method_getImplementation((Method)m); if (replace) { method_setImplementation((Method)m, imp); } } else { // fixme could be faster old_method_list *mlist = (old_method_list *)calloc(sizeof(old_method_list), 1); mlist->obsolete = fixed_up_method_list; mlist->method_count = 1; mlist->method_list[0].method_name = name; mlist->method_list[0].method_types = strdup(types); mlist->method_list[0].method_imp = imp; _objc_insertMethods(cls, mlist, nil); if (!(cls->info & CLS_CONSTRUCTING)) { flush_caches(cls, NO); } else { // in-construction class has no subclasses flush_cache(cls); } result = nil; } return result; } /*********************************************************************** * class_addMethod **********************************************************************/ BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { IMP old; if (!cls) return NO; old = _class_addMethod(cls, name, imp, types, NO); return !old; } /*********************************************************************** * class_replaceMethod **********************************************************************/ IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) { if (!cls) return nil; return _class_addMethod(cls, name, imp, types, YES); } /*********************************************************************** * class_addIvar **********************************************************************/ BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *type) { bool result = YES; if (!cls) return NO; if (ISMETA(cls)) return NO; if (!(cls->info & CLS_CONSTRUCTING)) return NO; if (!type) type = ""; if (name && 0 == strcmp(name, "")) name = nil; mutex_locker_t lock(classLock); // Check for existing ivar with this name // fixme check superclasses? if (cls->ivars) { int i; for (i = 0; i < cls->ivars->ivar_count; i++) { if (0 == strcmp(cls->ivars->ivar_list[i].ivar_name, name)) { result = NO; break; } } } if (result) { old_ivar_list *old = cls->ivars; size_t oldSize; int newCount; old_ivar *ivar; size_t alignBytes; size_t misalign; if (old) { oldSize = sizeof(old_ivar_list) + (old->ivar_count - 1) * sizeof(old_ivar); newCount = 1 + old->ivar_count; } else { oldSize = sizeof(old_ivar_list) - sizeof(old_ivar); newCount = 1; } // allocate new ivar list cls->ivars = (old_ivar_list *) calloc(oldSize+sizeof(old_ivar), 1); if (old) memcpy(cls->ivars, old, oldSize); if (old && malloc_size(old)) free(old); cls->ivars->ivar_count = newCount; ivar = &cls->ivars->ivar_list[newCount-1]; // set ivar name and type ivar->ivar_name = strdup(name); ivar->ivar_type = strdup(type); // align if necessary alignBytes = 1 << alignment; misalign = cls->instance_size % alignBytes; if (misalign) cls->instance_size += (long)(alignBytes - misalign); // set ivar offset and increase instance size ivar->ivar_offset = (int)cls->instance_size; cls->instance_size += (long)size; } return result; } /*********************************************************************** * class_addProtocol **********************************************************************/ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) { old_protocol *protocol = oldprotocol(protocol_gen); old_protocol_list *plist; if (!cls) return NO; if (class_conformsToProtocol(cls, protocol_gen)) return NO; mutex_locker_t lock(classLock); // fixme optimize - protocol list doesn't escape? plist = (old_protocol_list*)calloc(sizeof(old_protocol_list), 1); plist->count = 1; plist->list[0] = protocol; plist->next = cls->protocols; cls->protocols = plist; // fixme metaclass? return YES; } /*********************************************************************** * _class_addProperties * Internal helper to add properties to a class. * Used by category attachment and class_addProperty() * Locking: acquires classLock **********************************************************************/ bool _class_addProperties(Class cls, old_property_list *additions) { old_property_list *newlist; if (!(cls->info & CLS_EXT)) return NO; newlist = (old_property_list *) memdup(additions, sizeof(*newlist) - sizeof(newlist->first) + (additions->entsize * additions->count)); mutex_locker_t lock(classLock); allocateExt(cls); if (!cls->ext->propertyLists) { // cls has no properties - simply use this list cls->ext->propertyLists = (old_property_list **)newlist; cls->setInfo(CLS_NO_PROPERTY_ARRAY); } else if (cls->info & CLS_NO_PROPERTY_ARRAY) { // cls has one property list - make a new array old_property_list **newarray = (old_property_list **) malloc(3 * sizeof(*newarray)); newarray[0] = newlist; newarray[1] = (old_property_list *)cls->ext->propertyLists; newarray[2] = nil; cls->ext->propertyLists = newarray; cls->clearInfo(CLS_NO_PROPERTY_ARRAY); } else { // cls has a property array - make a bigger one old_property_list **newarray; int count = 0; while (cls->ext->propertyLists[count]) count++; newarray = (old_property_list **) malloc((count+2) * sizeof(*newarray)); newarray[0] = newlist; memcpy(&newarray[1], &cls->ext->propertyLists[0], count * sizeof(*newarray)); newarray[count+1] = nil; free(cls->ext->propertyLists); cls->ext->propertyLists = newarray; } return YES; } /*********************************************************************** * class_addProperty * Adds a property to a class. Returns NO if the proeprty already exists. * Locking: acquires classLock **********************************************************************/ static bool _class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attrs, unsigned int count, bool replace) { if (!cls) return NO; if (!name) return NO; old_property *prop = oldproperty(class_getProperty(cls, name)); if (prop && !replace) { // already exists, refuse to replace return NO; } else if (prop) { // replace existing mutex_locker_t lock(classLock); try_free(prop->attributes); prop->attributes = copyPropertyAttributeString(attrs, count); return YES; } else { // add new old_property_list proplist; proplist.entsize = sizeof(old_property); proplist.count = 1; proplist.first.name = strdup(name); proplist.first.attributes = copyPropertyAttributeString(attrs, count); return _class_addProperties(cls, &proplist); } } BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attrs, unsigned int n) { return _class_addProperty(cls, name, attrs, n, NO); } void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attrs, unsigned int n) { _class_addProperty(cls, name, attrs, n, YES); } /*********************************************************************** * class_copyProtocolList. Returns a heap block containing the * protocols implemented by the class, or nil if the class * implements no protocols. Caller must free the block. * Does not copy any superclass's protocols. **********************************************************************/ Protocol * __unsafe_unretained * class_copyProtocolList(Class cls, unsigned int *outCount) { old_protocol_list *plist; Protocol **result = nil; unsigned int count = 0; unsigned int p; if (!cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(classLock); for (plist = cls->protocols; plist != nil; plist = plist->next) { count += (int)plist->count; } if (count > 0) { result = (Protocol **)malloc((count+1) * sizeof(Protocol *)); for (p = 0, plist = cls->protocols; plist != nil; plist = plist->next) { int i; for (i = 0; i < plist->count; i++) { result[p++] = (Protocol *)plist->list[i]; } } result[p] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * class_getProperty. Return the named property. **********************************************************************/ objc_property_t class_getProperty(Class cls, const char *name) { if (!cls || !name) return nil; mutex_locker_t lock(classLock); for (; cls; cls = cls->superclass) { uintptr_t iterator = 0; old_property_list *plist; while ((plist = nextPropertyList(cls, &iterator))) { uint32_t i; for (i = 0; i < plist->count; i++) { old_property *p = property_list_nth(plist, i); if (0 == strcmp(name, p->name)) { return (objc_property_t)p; } } } } return nil; } /*********************************************************************** * class_copyPropertyList. Returns a heap block containing the * properties declared in the class, or nil if the class * declares no properties. Caller must free the block. * Does not copy any superclass's properties. **********************************************************************/ objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) { old_property_list *plist; uintptr_t iterator = 0; old_property **result = nil; unsigned int count = 0; unsigned int p, i; if (!cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(classLock); iterator = 0; while ((plist = nextPropertyList(cls, &iterator))) { count += plist->count; } if (count > 0) { result = (old_property **)malloc((count+1) * sizeof(old_property *)); p = 0; iterator = 0; while ((plist = nextPropertyList(cls, &iterator))) { for (i = 0; i < plist->count; i++) { result[p++] = property_list_nth(plist, i); } } result[p] = nil; } if (outCount) *outCount = count; return (objc_property_t *)result; } /*********************************************************************** * class_copyMethodList. Returns a heap block containing the * methods implemented by the class, or nil if the class * implements no methods. Caller must free the block. * Does not copy any superclass's methods. **********************************************************************/ Method *class_copyMethodList(Class cls, unsigned int *outCount) { old_method_list *mlist; void *iterator = nil; Method *result = nil; unsigned int count = 0; unsigned int m; if (!cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(methodListLock); iterator = nil; while ((mlist = nextMethodList(cls, &iterator))) { count += mlist->method_count; } if (count > 0) { result = (Method *)malloc((count+1) * sizeof(Method)); m = 0; iterator = nil; while ((mlist = nextMethodList(cls, &iterator))) { int i; for (i = 0; i < mlist->method_count; i++) { result[m++] = (Method)&mlist->method_list[i]; } } result[m] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * class_copyIvarList. Returns a heap block containing the * ivars declared in the class, or nil if the class * declares no ivars. Caller must free the block. * Does not copy any superclass's ivars. **********************************************************************/ Ivar *class_copyIvarList(Class cls, unsigned int *outCount) { Ivar *result = nil; unsigned int count = 0; int i; if (!cls) { if (outCount) *outCount = 0; return nil; } if (cls->ivars) { count = cls->ivars->ivar_count; } if (count > 0) { result = (Ivar *)malloc((count+1) * sizeof(Ivar)); for (i = 0; i < cls->ivars->ivar_count; i++) { result[i] = (Ivar)&cls->ivars->ivar_list[i]; } result[i] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * objc_allocateClass. **********************************************************************/ void set_superclass(Class cls, Class supercls, bool cls_is_new) { Class meta = cls->ISA(); if (supercls) { cls->superclass = supercls; meta->superclass = supercls->ISA(); meta->initIsa(supercls->ISA()->ISA()); // Propagate C++ cdtors from superclass. if (supercls->info & CLS_HAS_CXX_STRUCTORS) { if (cls_is_new) cls->info |= CLS_HAS_CXX_STRUCTORS; else cls->setInfo(CLS_HAS_CXX_STRUCTORS); } // Superclass is no longer a leaf for cache flushing if (supercls->info & CLS_LEAF) { supercls->clearInfo(CLS_LEAF); supercls->ISA()->clearInfo(CLS_LEAF); } } else { cls->superclass = Nil; // superclass of root class is nil meta->superclass = cls; // superclass of root metaclass is root class meta->initIsa(meta); // metaclass of root metaclass is root metaclass // Root class is never a leaf for cache flushing, because the // root metaclass is a subclass. (This could be optimized, but // is too uncommon to bother.) cls->clearInfo(CLS_LEAF); meta->clearInfo(CLS_LEAF); } } // &UnsetLayout is the default ivar layout during class construction static const uint8_t UnsetLayout = 0; Class objc_initializeClassPair(Class supercls, const char *name, Class cls, Class meta) { // Connect to superclasses and metaclasses cls->initIsa(meta); set_superclass(cls, supercls, YES); // Set basic info cls->name = strdup(name); meta->name = strdup(name); cls->version = 0; meta->version = 7; cls->info = CLS_CLASS | CLS_CONSTRUCTING | CLS_EXT | CLS_LEAF; meta->info = CLS_META | CLS_CONSTRUCTING | CLS_EXT | CLS_LEAF; // Set instance size based on superclass. if (supercls) { cls->instance_size = supercls->instance_size; meta->instance_size = supercls->ISA()->instance_size; } else { cls->instance_size = sizeof(Class); // just an isa meta->instance_size = sizeof(objc_class); } // No ivars. No methods. Empty cache. No protocols. No layout. Empty ext. cls->ivars = nil; cls->methodLists = nil; cls->cache = (Cache)&_objc_empty_cache; cls->protocols = nil; cls->ivar_layout = &UnsetLayout; cls->ext = nil; allocateExt(cls); cls->ext->weak_ivar_layout = &UnsetLayout; meta->ivars = nil; meta->methodLists = nil; meta->cache = (Cache)&_objc_empty_cache; meta->protocols = nil; meta->ext = nil; return cls; } Class objc_allocateClassPair(Class supercls, const char *name, size_t extraBytes) { Class cls, meta; if (objc_getClass(name)) return nil; // fixme reserve class name against simultaneous allocation if (supercls && (supercls->info & CLS_CONSTRUCTING)) { // Can't make subclass of an in-construction class return nil; } // Allocate new classes. if (supercls) { cls = _calloc_class(supercls->ISA()->alignedInstanceSize() + extraBytes); meta = _calloc_class(supercls->ISA()->ISA()->alignedInstanceSize() + extraBytes); } else { cls = _calloc_class(sizeof(objc_class) + extraBytes); meta = _calloc_class(sizeof(objc_class) + extraBytes); } objc_initializeClassPair(supercls, name, cls, meta); return cls; } void objc_registerClassPair(Class cls) { if ((cls->info & CLS_CONSTRUCTED) || (cls->ISA()->info & CLS_CONSTRUCTED)) { _objc_inform("objc_registerClassPair: class '%s' was already " "registered!", cls->name); return; } if (!(cls->info & CLS_CONSTRUCTING) || !(cls->ISA()->info & CLS_CONSTRUCTING)) { _objc_inform("objc_registerClassPair: class '%s' was not " "allocated with objc_allocateClassPair!", cls->name); return; } if (ISMETA(cls)) { _objc_inform("objc_registerClassPair: class '%s' is a metaclass, " "not a class!", cls->name); return; } mutex_locker_t lock(classLock); // Clear "under construction" bit, set "done constructing" bit cls->info &= ~CLS_CONSTRUCTING; cls->ISA()->info &= ~CLS_CONSTRUCTING; cls->info |= CLS_CONSTRUCTED; cls->ISA()->info |= CLS_CONSTRUCTED; NXHashInsertIfAbsent(class_hash, cls); } Class objc_duplicateClass(Class original, const char *name, size_t extraBytes) { unsigned int count, i; old_method **originalMethods; old_method_list *duplicateMethods; // Don't use sizeof(objc_class) here because // instance_size has historically contained two extra words, // and instance_size is what objc_getIndexedIvars() actually uses. Class duplicate = _calloc_class(original->ISA()->alignedInstanceSize() + extraBytes); duplicate->initIsa(original->ISA()); duplicate->superclass = original->superclass; duplicate->name = strdup(name); duplicate->version = original->version; duplicate->info = original->info & (CLS_CLASS|CLS_META|CLS_INITIALIZED|CLS_JAVA_HYBRID|CLS_JAVA_CLASS|CLS_HAS_CXX_STRUCTORS|CLS_HAS_LOAD_METHOD); duplicate->instance_size = original->instance_size; duplicate->ivars = original->ivars; // methodLists handled below duplicate->cache = (Cache)&_objc_empty_cache; duplicate->protocols = original->protocols; if (original->info & CLS_EXT) { duplicate->info |= original->info & (CLS_EXT|CLS_NO_PROPERTY_ARRAY); duplicate->ivar_layout = original->ivar_layout; if (original->ext) { duplicate->ext = (old_class_ext *)malloc(original->ext->size); memcpy(duplicate->ext, original->ext, original->ext->size); } else { duplicate->ext = nil; } } // Method lists are deep-copied so they can be stomped. originalMethods = (old_method **)class_copyMethodList(original, &count); if (originalMethods) { duplicateMethods = (old_method_list *) calloc(sizeof(old_method_list) + (count-1)*sizeof(old_method), 1); duplicateMethods->obsolete = fixed_up_method_list; duplicateMethods->method_count = count; for (i = 0; i < count; i++) { duplicateMethods->method_list[i] = *(originalMethods[i]); } duplicate->methodLists = (old_method_list **)duplicateMethods; duplicate->info |= CLS_NO_METHOD_ARRAY; free(originalMethods); } mutex_locker_t lock(classLock); NXHashInsert(class_hash, duplicate); return duplicate; } void objc_disposeClassPair(Class cls) { if (!(cls->info & (CLS_CONSTRUCTED|CLS_CONSTRUCTING)) || !(cls->ISA()->info & (CLS_CONSTRUCTED|CLS_CONSTRUCTING))) { // class not allocated with objc_allocateClassPair // disposing still-unregistered class is OK! _objc_inform("objc_disposeClassPair: class '%s' was not " "allocated with objc_allocateClassPair!", cls->name); return; } if (ISMETA(cls)) { _objc_inform("objc_disposeClassPair: class '%s' is a metaclass, " "not a class!", cls->name); return; } mutex_locker_t lock(classLock); NXHashRemove(class_hash, cls); unload_class(cls->ISA()); unload_class(cls); } /*********************************************************************** * objc_constructInstance * Creates an instance of `cls` at the location pointed to by `bytes`. * `bytes` must point to at least class_getInstanceSize(cls) bytes of * well-aligned zero-filled memory. * The new object's isa is set. Any C++ constructors are called. * Returns `bytes` if successful. Returns nil if `cls` or `bytes` is * nil, or if C++ constructors fail. **********************************************************************/ id objc_constructInstance(Class cls, void *bytes) { if (!cls || !bytes) return nil; id obj = (id)bytes; obj->initIsa(cls); if (cls->hasCxxCtor()) { return object_cxxConstructFromClass(obj, cls); } else { return obj; } } /*********************************************************************** * _class_createInstanceFromZone. Allocate an instance of the * specified class with the specified number of bytes for indexed * variables, in the specified zone. The isa field is set to the * class, C++ default constructors are called, and all other fields are zeroed. **********************************************************************/ id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) { void *bytes; size_t size; // Can't create something for nothing if (!cls) return nil; // Allocate and initialize size = cls->alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; if (zone) { bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { bytes = calloc(1, size); } return objc_constructInstance(cls, bytes); } /*********************************************************************** * _class_createInstance. Allocate an instance of the specified * class with the specified number of bytes for indexed variables, in * the default zone, using _class_createInstanceFromZone. **********************************************************************/ static id _class_createInstance(Class cls, size_t extraBytes) { return _class_createInstanceFromZone (cls, extraBytes, nil); } static id _object_copyFromZone(id oldObj, size_t extraBytes, void *zone) { id obj; size_t size; if (!oldObj) return nil; obj = (*_zoneAlloc)(oldObj->ISA(), extraBytes, zone); size = oldObj->ISA()->alignedInstanceSize() + extraBytes; // fixme need C++ copy constructor memmove(obj, oldObj, size); fixupCopiedIvars(obj, oldObj); return obj; } /*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. * CoreFoundation and other clients do call this under GC. **********************************************************************/ void *objc_destructInstance(id obj) { if (obj) { Class isa = obj->getIsa(); if (isa->hasCxxDtor()) { object_cxxDestruct(obj); } if (isa->instancesHaveAssociatedObjects()) { _object_remove_assocations(obj); } objc_clear_deallocating(obj); } return obj; } static id _object_dispose(id anObject) { if (anObject==nil) return nil; objc_destructInstance(anObject); anObject->initIsa(_objc_getFreedObjectClass ()); free(anObject); return nil; } static id _object_copy(id oldObj, size_t extraBytes) { void *z = malloc_zone_from_ptr(oldObj); return _object_copyFromZone(oldObj, extraBytes, z ? z : malloc_default_zone()); } static id _object_reallocFromZone(id anObject, size_t nBytes, void *zone) { id newObject; Class tmp; if (anObject == nil) __objc_error(nil, "reallocating nil object"); if (anObject->ISA() == _objc_getFreedObjectClass ()) __objc_error(anObject, "reallocating freed object"); if (nBytes < anObject->ISA()->alignedInstanceSize()) __objc_error(anObject, "(%s, %zu) requested size too small", object_getClassName(anObject), nBytes); // fixme need C++ copy constructor // fixme GC copy // Make sure not to modify space that has been declared free tmp = anObject->ISA(); anObject->initIsa(_objc_getFreedObjectClass ()); newObject = (id)malloc_zone_realloc((malloc_zone_t *)zone, anObject, nBytes); if (newObject) { newObject->initIsa(tmp); } else { // realloc failed, anObject is still alive anObject->initIsa(tmp); } return newObject; } static id _object_realloc(id anObject, size_t nBytes) { void *z = malloc_zone_from_ptr(anObject); return _object_reallocFromZone(anObject, nBytes, z ? z : malloc_default_zone()); } id (*_alloc)(Class, size_t) = _class_createInstance; id (*_copy)(id, size_t) = _object_copy; id (*_realloc)(id, size_t) = _object_realloc; id (*_dealloc)(id) = _object_dispose; id (*_zoneAlloc)(Class, size_t, void *) = _class_createInstanceFromZone; id (*_zoneCopy)(id, size_t, void *) = _object_copyFromZone; id (*_zoneRealloc)(id, size_t, void *) = _object_reallocFromZone; void (*_error)(id, const char *, va_list) = _objc_error; id class_createInstance(Class cls, size_t extraBytes) { return (*_alloc)(cls, extraBytes); } id class_createInstanceFromZone(Class cls, size_t extraBytes, void *z) { OBJC_WARN_DEPRECATED; return (*_zoneAlloc)(cls, extraBytes, z); } unsigned class_createInstances(Class cls, size_t extraBytes, id *results, unsigned num_requested) { if (_alloc == &_class_createInstance) { return _class_createInstancesFromZone(cls, extraBytes, nil, results, num_requested); } else { // _alloc in use, which isn't understood by the batch allocator return 0; } } id object_copy(id obj, size_t extraBytes) { return (*_copy)(obj, extraBytes); } id object_copyFromZone(id obj, size_t extraBytes, void *z) { OBJC_WARN_DEPRECATED; return (*_zoneCopy)(obj, extraBytes, z); } id object_dispose(id obj) { return (*_dealloc)(obj); } id object_realloc(id obj, size_t nBytes) { OBJC_WARN_DEPRECATED; return (*_realloc)(obj, nBytes); } id object_reallocFromZone(id obj, size_t nBytes, void *z) { OBJC_WARN_DEPRECATED; return (*_zoneRealloc)(obj, nBytes, z); } /*********************************************************************** * object_getIndexedIvars. **********************************************************************/ void *object_getIndexedIvars(id obj) { // ivars are tacked onto the end of the object if (!obj) return nil; if (obj->isTaggedPointer()) return nil; return ((char *) obj) + obj->ISA()->alignedInstanceSize(); } // ProKit SPI Class class_setSuperclass(Class cls, Class newSuper) { Class oldSuper = cls->superclass; set_superclass(cls, newSuper, NO); flush_caches(cls, YES); return oldSuper; } #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-class.h ================================================ #include #include ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-class.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-class.m * Copyright 1988-1997, Apple Computer, Inc. * Author: s. naroff **********************************************************************/ /*********************************************************************** * Lazy method list arrays and method list locking (2004-10-19) * * cls->methodLists may be in one of three forms: * 1. nil: The class has no methods. * 2. non-nil, with CLS_NO_METHOD_ARRAY set: cls->methodLists points * to a single method list, which is the class's only method list. * 3. non-nil, with CLS_NO_METHOD_ARRAY clear: cls->methodLists points to * an array of method list pointers. The end of the array's block * is set to -1. If the actual number of method lists is smaller * than that, the rest of the array is nil. * * Attaching categories and adding and removing classes may change * the form of the class list. In addition, individual method lists * may be reallocated when fixed up. * * Classes are initially read as #1 or #2. If a category is attached * or other methods added, the class is changed to #3. Once in form #3, * the class is never downgraded to #1 or #2, even if methods are removed. * Classes added with objc_addClass are initially either #1 or #3. * * Accessing and manipulating a class's method lists are synchronized, * to prevent races when one thread restructures the list. However, * if the class is not yet in use (i.e. not in class_hash), then the * thread loading the class may access its method lists without locking. * * The following functions acquire methodListLock: * class_getInstanceMethod * class_getClassMethod * class_nextMethodList * class_addMethods * class_removeMethods * class_respondsToMethod * _class_lookupMethodAndLoadCache * lookupMethodInClassAndLoadCache * _objc_add_category_flush_caches * * The following functions don't acquire methodListLock because they * only access method lists during class load and unload: * _objc_register_category * _resolve_categories_for_class (calls _objc_add_category) * add_class_to_loadable_list * _objc_addClass * _objc_remove_classes_in_image * * The following functions use method lists without holding methodListLock. * The caller must either hold methodListLock, or be loading the class. * _getMethod (called by class_getInstanceMethod, class_getClassMethod, * and class_respondsToMethod) * _findMethodInClass (called by _class_lookupMethodAndLoadCache, * lookupMethodInClassAndLoadCache, _getMethod) * _findMethodInList (called by _findMethodInClass) * nextMethodList (called by _findMethodInClass and class_nextMethodList * fixupSelectorsInMethodList (called by nextMethodList) * _objc_add_category (called by _objc_add_category_flush_caches, * resolve_categories_for_class and _objc_register_category) * _objc_insertMethods (called by class_addMethods and _objc_add_category) * _objc_removeMethods (called by class_removeMethods) * _objcTweakMethodListPointerForClass (called by _objc_insertMethods) * get_base_method_list (called by add_class_to_loadable_list) * lookupNamedMethodInMethodList (called by add_class_to_loadable_list) ***********************************************************************/ /*********************************************************************** * Thread-safety of class info bits (2004-10-19) * * Some class info bits are used to store mutable runtime state. * Modifications of the info bits at particular times need to be * synchronized to prevent races. * * Three thread-safe modification functions are provided: * cls->setInfo() // atomically sets some bits * cls->clearInfo() // atomically clears some bits * cls->changeInfo() // atomically sets some bits and clears others * These replace CLS_SETINFO() for the multithreaded cases. * * Three modification windows are defined: * - compile time * - class construction or image load (before +load) in one thread * - multi-threaded messaging and method caches * * Info bit modification at compile time and class construction do not * need to be locked, because only one thread is manipulating the class. * Info bit modification during messaging needs to be locked, because * there may be other threads simultaneously messaging or otherwise * manipulating the class. * * Modification windows for each flag: * * CLS_CLASS: compile-time and class load * CLS_META: compile-time and class load * CLS_INITIALIZED: +initialize * CLS_POSING: messaging * CLS_MAPPED: compile-time * CLS_FLUSH_CACHE: class load and messaging * CLS_GROW_CACHE: messaging * CLS_NEED_BIND: unused * CLS_METHOD_ARRAY: unused * CLS_JAVA_HYBRID: JavaBridge only * CLS_JAVA_CLASS: JavaBridge only * CLS_INITIALIZING: messaging * CLS_FROM_BUNDLE: class load * CLS_HAS_CXX_STRUCTORS: compile-time and class load * CLS_NO_METHOD_ARRAY: class load and messaging * CLS_HAS_LOAD_METHOD: class load * * CLS_INITIALIZED and CLS_INITIALIZING have additional thread-safety * constraints to support thread-safe +initialize. See "Thread safety * during class initialization" for details. * * CLS_JAVA_HYBRID and CLS_JAVA_CLASS are set immediately after JavaBridge * calls objc_addClass(). The JavaBridge does not use an atomic update, * but the modification counts as "class construction" unless some other * thread quickly finds the class via the class list. This race is * small and unlikely in well-behaved code. * * Most info bits that may be modified during messaging are also never * read without a lock. There is no general read lock for the info bits. * CLS_INITIALIZED: classInitLock * CLS_FLUSH_CACHE: cacheUpdateLock * CLS_GROW_CACHE: cacheUpdateLock * CLS_NO_METHOD_ARRAY: methodListLock * CLS_INITIALIZING: classInitLock ***********************************************************************/ /*********************************************************************** * Imports. **********************************************************************/ #include "objc-private.h" #include "objc-abi.h" #include /*********************************************************************** * Information about multi-thread support: * * Since we do not lock many operations which walk the superclass, method * and ivar chains, these chains must remain intact once a class is published * by inserting it into the class hashtable. All modifications must be * atomic so that someone walking these chains will always geta valid * result. ***********************************************************************/ /*********************************************************************** * object_getClass. * Locking: None. If you add locking, tell gdb (rdar://7516456). **********************************************************************/ Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; } /*********************************************************************** * object_setClass. **********************************************************************/ Class object_setClass(id obj, Class cls) { if (!obj) return nil; // Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. // Unresolved future classes are not so protected. if (!cls->isFuture() && !cls->isInitialized()) { _class_initialize(_class_getNonMetaClass(cls, nil)); } return obj->changeIsa(cls); } /*********************************************************************** * object_isClass. **********************************************************************/ BOOL object_isClass(id obj) { if (!obj) return NO; return obj->isClass(); } /*********************************************************************** * object_getClassName. **********************************************************************/ const char *object_getClassName(id obj) { return class_getName(obj ? obj->getIsa() : nil); } /*********************************************************************** * object_getMethodImplementation. **********************************************************************/ IMP object_getMethodImplementation(id obj, SEL name) { Class cls = (obj ? obj->getIsa() : nil); return class_getMethodImplementation(cls, name); } /*********************************************************************** * object_getMethodImplementation_stret. **********************************************************************/ #if SUPPORT_STRET IMP object_getMethodImplementation_stret(id obj, SEL name) { Class cls = (obj ? obj->getIsa() : nil); return class_getMethodImplementation_stret(cls, name); } #endif static bool isScanned(ptrdiff_t ivar_offset, const uint8_t *layout) { if (!layout) return NO; ptrdiff_t index = 0, ivar_index = ivar_offset / sizeof(void*); uint8_t byte; while ((byte = *layout++)) { unsigned skips = (byte >> 4); unsigned scans = (byte & 0x0F); index += skips; if (index > ivar_index) return NO; index += scans; if (index > ivar_index) return YES; } return NO; } /*********************************************************************** * _class_lookUpIvar * Given an object and an ivar in it, look up some data about that ivar: * - its offset * - its memory management behavior * The ivar is assumed to be word-aligned and of of object type. **********************************************************************/ static void _class_lookUpIvar(Class cls, Ivar ivar, ptrdiff_t& ivarOffset, objc_ivar_memory_management_t& memoryManagement) { ivarOffset = ivar_getOffset(ivar); // Look for ARC variables and ARC-style weak. // Preflight the hasAutomaticIvars check // because _class_getClassForIvar() may need to take locks. bool hasAutomaticIvars = NO; for (Class c = cls; c; c = c->superclass) { if (c->hasAutomaticIvars()) { hasAutomaticIvars = YES; break; } } if (hasAutomaticIvars) { Class ivarCls = _class_getClassForIvar(cls, ivar); if (ivarCls->hasAutomaticIvars()) { // ARC layout bitmaps encode the class's own ivars only. // Use alignedInstanceStart() because unaligned bytes at the start // of this class's ivars are not represented in the layout bitmap. ptrdiff_t localOffset = ivarOffset - ivarCls->alignedInstanceStart(); if (isScanned(localOffset, class_getIvarLayout(ivarCls))) { memoryManagement = objc_ivar_memoryStrong; return; } if (isScanned(localOffset, class_getWeakIvarLayout(ivarCls))) { memoryManagement = objc_ivar_memoryWeak; return; } // Unretained is only for true ARC classes. if (ivarCls->isARC()) { memoryManagement = objc_ivar_memoryUnretained; return; } } } memoryManagement = objc_ivar_memoryUnknown; } /*********************************************************************** * _class_getIvarMemoryManagement * SPI for KVO and others to decide what memory management to use * when setting instance variables directly. **********************************************************************/ objc_ivar_memory_management_t _class_getIvarMemoryManagement(Class cls, Ivar ivar) { ptrdiff_t offset; objc_ivar_memory_management_t memoryManagement; _class_lookUpIvar(cls, ivar, offset, memoryManagement); return memoryManagement; } static ALWAYS_INLINE void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong) { if (!obj || !ivar || obj->isTaggedPointer()) return; ptrdiff_t offset; objc_ivar_memory_management_t memoryManagement; _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement); if (memoryManagement == objc_ivar_memoryUnknown) { if (assumeStrong) memoryManagement = objc_ivar_memoryStrong; else memoryManagement = objc_ivar_memoryUnretained; } id *location = (id *)((char *)obj + offset); switch (memoryManagement) { case objc_ivar_memoryWeak: objc_storeWeak(location, value); break; case objc_ivar_memoryStrong: objc_storeStrong(location, value); break; case objc_ivar_memoryUnretained: *location = value; break; case objc_ivar_memoryUnknown: _objc_fatal("impossible"); } } void object_setIvar(id obj, Ivar ivar, id value) { return _object_setIvar(obj, ivar, value, false /*not strong default*/); } void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value) { return _object_setIvar(obj, ivar, value, true /*strong default*/); } id object_getIvar(id obj, Ivar ivar) { if (!obj || !ivar || obj->isTaggedPointer()) return nil; ptrdiff_t offset; objc_ivar_memory_management_t memoryManagement; _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement); id *location = (id *)((char *)obj + offset); if (memoryManagement == objc_ivar_memoryWeak) { return objc_loadWeak(location); } else { return *location; } } static ALWAYS_INLINE Ivar _object_setInstanceVariable(id obj, const char *name, void *value, bool assumeStrong) { Ivar ivar = nil; if (obj && name && !obj->isTaggedPointer()) { if ((ivar = _class_getVariable(obj->ISA(), name))) { _object_setIvar(obj, ivar, (id)value, assumeStrong); } } return ivar; } Ivar object_setInstanceVariable(id obj, const char *name, void *value) { return _object_setInstanceVariable(obj, name, value, false); } Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name, void *value) { return _object_setInstanceVariable(obj, name, value, true); } Ivar object_getInstanceVariable(id obj, const char *name, void **value) { if (obj && name && !obj->isTaggedPointer()) { Ivar ivar; if ((ivar = class_getInstanceVariable(obj->ISA(), name))) { if (value) *value = (void *)object_getIvar(obj, ivar); return ivar; } } if (value) *value = nil; return nil; } /*********************************************************************** * object_cxxDestructFromClass. * Call C++ destructors on obj, starting with cls's * dtor method (if any) followed by superclasses' dtors (if any), * stopping at cls's dtor (if any). * Uses methodListLock and cacheUpdateLock. The caller must hold neither. **********************************************************************/ static void object_cxxDestructFromClass(id obj, Class cls) { void (*dtor)(id); // Call cls's dtor first, then superclasses's dtors. for ( ; cls; cls = cls->superclass) { if (!cls->hasCxxDtor()) return; dtor = (void(*)(id)) lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct); if (dtor != (void(*)(id))_objc_msgForward_impcache) { if (PrintCxxCtors) { _objc_inform("CXX: calling C++ destructors for class %s", cls->nameForLogging()); } (*dtor)(obj); } } } /*********************************************************************** * object_cxxDestruct. * Call C++ destructors on obj, if any. * Uses methodListLock and cacheUpdateLock. The caller must hold neither. **********************************************************************/ void object_cxxDestruct(id obj) { if (!obj) return; if (obj->isTaggedPointer()) return; object_cxxDestructFromClass(obj, obj->ISA()); } /*********************************************************************** * object_cxxConstructFromClass. * Recursively call C++ constructors on obj, starting with base class's * ctor method (if any) followed by subclasses' ctors (if any), stopping * at cls's ctor (if any). * Does not check cls->hasCxxCtor(). The caller should preflight that. * Returns self if construction succeeded. * Returns nil if some constructor threw an exception. The exception is * caught and discarded. Any partial construction is destructed. * Uses methodListLock and cacheUpdateLock. The caller must hold neither. * * .cxx_construct returns id. This really means: * return self: construction succeeded * return nil: construction failed because a C++ constructor threw an exception **********************************************************************/ id object_cxxConstructFromClass(id obj, Class cls) { assert(cls->hasCxxCtor()); // required for performance, not correctness id (*ctor)(id); Class supercls; supercls = cls->superclass; // Call superclasses' ctors first, if any. if (supercls && supercls->hasCxxCtor()) { bool ok = object_cxxConstructFromClass(obj, supercls); if (!ok) return nil; // some superclass's ctor failed - give up } // Find this class's ctor, if any. ctor = (id(*)(id))lookupMethodInClassAndLoadCache(cls, SEL_cxx_construct); if (ctor == (id(*)(id))_objc_msgForward_impcache) return obj; // no ctor - ok // Call this class's ctor. if (PrintCxxCtors) { _objc_inform("CXX: calling C++ constructors for class %s", cls->nameForLogging()); } if ((*ctor)(obj)) return obj; // ctor called and succeeded - ok // This class's ctor was called and failed. // Call superclasses's dtors to clean up. if (supercls) object_cxxDestructFromClass(obj, supercls); return nil; } /*********************************************************************** * fixupCopiedIvars * Fix up ARC strong and ARC-style weak variables * after oldObject was memcpy'd to newObject. **********************************************************************/ void fixupCopiedIvars(id newObject, id oldObject) { for (Class cls = oldObject->ISA(); cls; cls = cls->superclass) { if (cls->hasAutomaticIvars()) { // Use alignedInstanceStart() because unaligned bytes at the start // of this class's ivars are not represented in the layout bitmap. size_t instanceStart = cls->alignedInstanceStart(); const uint8_t *strongLayout = class_getIvarLayout(cls); if (strongLayout) { id *newPtr = (id *)((char*)newObject + instanceStart); unsigned char byte; while ((byte = *strongLayout++)) { unsigned skips = (byte >> 4); unsigned scans = (byte & 0x0F); newPtr += skips; while (scans--) { // ensure strong references are properly retained. id value = *newPtr++; if (value) objc_retain(value); } } } const uint8_t *weakLayout = class_getWeakIvarLayout(cls); // fix up weak references if any. if (weakLayout) { id *newPtr = (id *)((char*)newObject + instanceStart), *oldPtr = (id *)((char*)oldObject + instanceStart); unsigned char byte; while ((byte = *weakLayout++)) { unsigned skips = (byte >> 4); unsigned weaks = (byte & 0x0F); newPtr += skips, oldPtr += skips; while (weaks--) { objc_copyWeak(newPtr, oldPtr); ++newPtr, ++oldPtr; } } } } } } /*********************************************************************** * _class_resolveClassMethod * Call +resolveClassMethod, looking for a method to be added to class cls. * cls should be a metaclass. * Does not check if the method already exists. **********************************************************************/ static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { assert(cls->isMetaClass()); if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(_class_getNonMetaClass(cls, inst), SEL_resolveClassMethod, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); } } } /*********************************************************************** * _class_resolveInstanceMethod * Call +resolveInstanceMethod, looking for a method to be added to class cls. * cls may be a metaclass or a non-meta class. * Does not check if the method already exists. **********************************************************************/ static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) { if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); } } } /*********************************************************************** * _class_resolveMethod * Call +resolveClassMethod or +resolveInstanceMethod. * Returns nothing; any result would be potentially out-of-date already. * Does not check if the method already exists. **********************************************************************/ void _class_resolveMethod(Class cls, SEL sel, id inst) { if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] _class_resolveInstanceMethod(cls, sel, inst); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] _class_resolveClassMethod(cls, sel, inst); if (!lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); } } } /*********************************************************************** * class_getClassMethod. Return the class method for the specified * class and selector. **********************************************************************/ Method class_getClassMethod(Class cls, SEL sel) { if (!cls || !sel) return nil; return class_getInstanceMethod(cls->getMeta(), sel); } /*********************************************************************** * class_getInstanceVariable. Return the named instance variable. **********************************************************************/ Ivar class_getInstanceVariable(Class cls, const char *name) { if (!cls || !name) return nil; return _class_getVariable(cls, name); } /*********************************************************************** * class_getClassVariable. Return the named class variable. **********************************************************************/ Ivar class_getClassVariable(Class cls, const char *name) { if (!cls) return nil; return class_getInstanceVariable(cls->ISA(), name); } /*********************************************************************** * gdb_objc_class_changed * Tell gdb that a class changed. Currently used for OBJC2 ivar layouts only * Does nothing; gdb sets a breakpoint on it. **********************************************************************/ BREAKPOINT_FUNCTION( void gdb_objc_class_changed(Class cls, unsigned long changes, const char *classname) ); /*********************************************************************** * class_respondsToSelector. **********************************************************************/ BOOL class_respondsToMethod(Class cls, SEL sel) { OBJC_WARN_DEPRECATED; return class_respondsToSelector(cls, sel); } BOOL class_respondsToSelector(Class cls, SEL sel) { return class_respondsToSelector_inst(cls, sel, nil); } // inst is an instance of cls or a subclass thereof, or nil if none is known. // Non-nil inst is faster in some cases. See lookUpImpOrForward() for details. bool class_respondsToSelector_inst(Class cls, SEL sel, id inst) { IMP imp; if (!sel || !cls) return NO; // Avoids +initialize because it historically did so. // We're not returning a callable IMP anyway. imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, YES/*resolver*/); return bool(imp); } /*********************************************************************** * class_getMethodImplementation. * Returns the IMP that would be invoked if [obj sel] were sent, * where obj is an instance of class cls. **********************************************************************/ IMP class_lookupMethod(Class cls, SEL sel) { OBJC_WARN_DEPRECATED; // No one responds to zero! if (!sel) { __objc_error(cls, "invalid selector (null)"); } return class_getMethodImplementation(cls, sel); } IMP class_getMethodImplementation(Class cls, SEL sel) { IMP imp; if (!cls || !sel) return nil; imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/); // Translate forwarding function to C-callable external version if (!imp) { return _objc_msgForward; } return imp; } #if SUPPORT_STRET IMP class_getMethodImplementation_stret(Class cls, SEL sel) { IMP imp = class_getMethodImplementation(cls, sel); // Translate forwarding function to struct-returning version if (imp == (IMP)&_objc_msgForward /* not _internal! */) { return (IMP)&_objc_msgForward_stret; } return imp; } #endif /*********************************************************************** * instrumentObjcMessageSends **********************************************************************/ // Define this everywhere even if it isn't used to simplify fork() safety code. spinlock_t objcMsgLogLock; #if !SUPPORT_MESSAGE_LOGGING void instrumentObjcMessageSends(BOOL flag) { } #else bool objcMsgLogEnabled = false; static int objcMsgLogFD = -1; bool logMessageSend(bool isClassMethod, const char *objectsClass, const char *implementingClass, SEL selector) { char buf[ 1024 ]; // Create/open the log file if (objcMsgLogFD == (-1)) { snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ()); objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid()); if (objcMsgLogFD < 0) { // no log file - disable logging objcMsgLogEnabled = false; objcMsgLogFD = -1; return true; } } // Make the log entry snprintf(buf, sizeof(buf), "%c %s %s %s\n", isClassMethod ? '+' : '-', objectsClass, implementingClass, sel_getName(selector)); objcMsgLogLock.lock(); write (objcMsgLogFD, buf, strlen(buf)); objcMsgLogLock.unlock(); // Tell caller to not cache the method return false; } void instrumentObjcMessageSends(BOOL flag) { bool enable = flag; // Shortcut NOP if (objcMsgLogEnabled == enable) return; // If enabling, flush all method caches so we get some traces if (enable) _objc_flush_caches(Nil); // Sync our log file if (objcMsgLogFD != -1) fsync (objcMsgLogFD); objcMsgLogEnabled = enable; } // SUPPORT_MESSAGE_LOGGING #endif Class _calloc_class(size_t size) { return (Class) calloc(1, size); } Class class_getSuperclass(Class cls) { if (!cls) return nil; return cls->superclass; } BOOL class_isMetaClass(Class cls) { if (!cls) return NO; return cls->isMetaClass(); } size_t class_getInstanceSize(Class cls) { if (!cls) return 0; return cls->alignedInstanceSize(); } /*********************************************************************** * method_getNumberOfArguments. **********************************************************************/ unsigned int method_getNumberOfArguments(Method m) { if (!m) return 0; return encoding_getNumberOfArguments(method_getTypeEncoding(m)); } void method_getReturnType(Method m, char *dst, size_t dst_len) { encoding_getReturnType(method_getTypeEncoding(m), dst, dst_len); } char * method_copyReturnType(Method m) { return encoding_copyReturnType(method_getTypeEncoding(m)); } void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len) { encoding_getArgumentType(method_getTypeEncoding(m), index, dst, dst_len); } char * method_copyArgumentType(Method m, unsigned int index) { return encoding_copyArgumentType(method_getTypeEncoding(m), index); } /*********************************************************************** * _objc_constructOrFree * Call C++ constructors, and free() if they fail. * bytes->isa must already be set. * cls must have cxx constructors. * Returns the object, or nil. **********************************************************************/ id _objc_constructOrFree(id bytes, Class cls) { assert(cls->hasCxxCtor()); // for performance, not correctness id obj = object_cxxConstructFromClass(bytes, cls); if (!obj) free(bytes); return obj; } /*********************************************************************** * _class_createInstancesFromZone * Batch-allocating version of _class_createInstanceFromZone. * Attempts to allocate num_requested objects, each with extraBytes. * Returns the number of allocated objects (possibly zero), with * the allocated pointers in *results. **********************************************************************/ unsigned _class_createInstancesFromZone(Class cls, size_t extraBytes, void *zone, id *results, unsigned num_requested) { unsigned num_allocated; if (!cls) return 0; size_t size = cls->instanceSize(extraBytes); num_allocated = malloc_zone_batch_malloc((malloc_zone_t *)(zone ? zone : malloc_default_zone()), size, (void**)results, num_requested); for (unsigned i = 0; i < num_allocated; i++) { bzero(results[i], size); } // Construct each object, and delete any that fail construction. unsigned shift = 0; bool ctor = cls->hasCxxCtor(); for (unsigned i = 0; i < num_allocated; i++) { id obj = results[i]; obj->initIsa(cls); // fixme allow nonpointer if (ctor) obj = _objc_constructOrFree(obj, cls); if (obj) { results[i-shift] = obj; } else { shift++; } } return num_allocated - shift; } /*********************************************************************** * inform_duplicate. Complain about duplicate class implementations. **********************************************************************/ void inform_duplicate(const char *name, Class oldCls, Class newCls) { #if TARGET_OS_WIN32 (DebugDuplicateClasses ? _objc_fatal : _objc_inform) ("Class %s is implemented in two different images.", name); #else const header_info *oldHeader = _headerForClass(oldCls); const header_info *newHeader = _headerForClass(newCls); const char *oldName = oldHeader ? oldHeader->fname() : "??"; const char *newName = newHeader ? newHeader->fname() : "??"; (DebugDuplicateClasses ? _objc_fatal : _objc_inform) ("Class %s is implemented in both %s (%p) and %s (%p). " "One of the two will be used. Which one is undefined.", name, oldName, oldCls, newName, newCls); #endif } const char * copyPropertyAttributeString(const objc_property_attribute_t *attrs, unsigned int count) { char *result; unsigned int i; if (count == 0) return strdup(""); #if DEBUG // debug build: sanitize input for (i = 0; i < count; i++) { assert(attrs[i].name); assert(strlen(attrs[i].name) > 0); assert(! strchr(attrs[i].name, ',')); assert(! strchr(attrs[i].name, '"')); if (attrs[i].value) assert(! strchr(attrs[i].value, ',')); } #endif size_t len = 0; for (i = 0; i < count; i++) { if (attrs[i].value) { size_t namelen = strlen(attrs[i].name); if (namelen > 1) namelen += 2; // long names get quoted len += namelen + strlen(attrs[i].value) + 1; } } result = (char *)malloc(len + 1); char *s = result; for (i = 0; i < count; i++) { if (attrs[i].value) { size_t namelen = strlen(attrs[i].name); if (namelen > 1) { s += sprintf(s, "\"%s\"%s,", attrs[i].name, attrs[i].value); } else { s += sprintf(s, "%s%s,", attrs[i].name, attrs[i].value); } } } // remove trailing ',' if any if (s > result) s[-1] = '\0'; return result; } /* Property attribute string format: - Comma-separated name-value pairs. - Name and value may not contain , - Name may not contain " - Value may be empty - Name is single char, value follows - OR Name is double-quoted string of 2+ chars, value follows Grammar: attribute-string: \0 attribute-string: name-value-pair (',' name-value-pair)* name-value-pair: unquoted-name optional-value name-value-pair: quoted-name optional-value unquoted-name: [^",] quoted-name: '"' [^",]{2,} '"' optional-value: [^,]* */ static unsigned int iteratePropertyAttributes(const char *attrs, bool (*fn)(unsigned int index, void *ctx1, void *ctx2, const char *name, size_t nlen, const char *value, size_t vlen), void *ctx1, void *ctx2) { if (!attrs) return 0; #if DEBUG const char *attrsend = attrs + strlen(attrs); #endif unsigned int attrcount = 0; while (*attrs) { // Find the next comma-separated attribute const char *start = attrs; const char *end = start + strcspn(attrs, ","); // Move attrs past this attribute and the comma (if any) attrs = *end ? end+1 : end; assert(attrs <= attrsend); assert(start <= attrsend); assert(end <= attrsend); // Skip empty attribute if (start == end) continue; // Process one non-empty comma-free attribute [start,end) const char *nameStart; const char *nameEnd; assert(start < end); assert(*start); if (*start != '\"') { // single-char short name nameStart = start; nameEnd = start+1; start++; } else { // double-quoted long name nameStart = start+1; nameEnd = nameStart + strcspn(nameStart, "\","); start++; // leading quote start += nameEnd - nameStart; // name if (*start == '\"') start++; // trailing quote, if any } // Process one possibly-empty comma-free attribute value [start,end) const char *valueStart; const char *valueEnd; assert(start <= end); valueStart = start; valueEnd = end; bool more = (*fn)(attrcount, ctx1, ctx2, nameStart, nameEnd-nameStart, valueStart, valueEnd-valueStart); attrcount++; if (!more) break; } return attrcount; } static bool copyOneAttribute(unsigned int index, void *ctxa, void *ctxs, const char *name, size_t nlen, const char *value, size_t vlen) { objc_property_attribute_t **ap = (objc_property_attribute_t**)ctxa; char **sp = (char **)ctxs; objc_property_attribute_t *a = *ap; char *s = *sp; a->name = s; memcpy(s, name, nlen); s += nlen; *s++ = '\0'; a->value = s; memcpy(s, value, vlen); s += vlen; *s++ = '\0'; a++; *ap = a; *sp = s; return YES; } objc_property_attribute_t * copyPropertyAttributeList(const char *attrs, unsigned int *outCount) { if (!attrs) { if (outCount) *outCount = 0; return nil; } // Result size: // number of commas plus 1 for the attributes (upper bound) // plus another attribute for the attribute array terminator // plus strlen(attrs) for name/value string data (upper bound) // plus count*2 for the name/value string terminators (upper bound) unsigned int attrcount = 1; const char *s; for (s = attrs; s && *s; s++) { if (*s == ',') attrcount++; } size_t size = attrcount * sizeof(objc_property_attribute_t) + sizeof(objc_property_attribute_t) + strlen(attrs) + attrcount * 2; objc_property_attribute_t *result = (objc_property_attribute_t *) calloc(size, 1); objc_property_attribute_t *ra = result; char *rs = (char *)(ra+attrcount+1); attrcount = iteratePropertyAttributes(attrs, copyOneAttribute, &ra, &rs); assert((uint8_t *)(ra+1) <= (uint8_t *)result+size); assert((uint8_t *)rs <= (uint8_t *)result+size); if (attrcount == 0) { free(result); result = nil; } if (outCount) *outCount = attrcount; return result; } static bool findOneAttribute(unsigned int index, void *ctxa, void *ctxs, const char *name, size_t nlen, const char *value, size_t vlen) { const char *query = (char *)ctxa; char **resultp = (char **)ctxs; if (strlen(query) == nlen && 0 == strncmp(name, query, nlen)) { char *result = (char *)calloc(vlen+1, 1); memcpy(result, value, vlen); result[vlen] = '\0'; *resultp = result; return NO; } return YES; } char *copyPropertyAttributeValue(const char *attrs, const char *name) { char *result = nil; iteratePropertyAttributes(attrs, findOneAttribute, (void*)name, &result); return result; } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-config.h ================================================ /* * Copyright (c) 1999-2002, 2005-2008 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_CONFIG_H_ #define _OBJC_CONFIG_H_ #include // Define __OBJC2__ for the benefit of our asm files. #ifndef __OBJC2__ # if TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__ // old ABI # else # define __OBJC2__ 1 # endif #endif // Avoid the !NDEBUG double negative. #if !NDEBUG # define DEBUG 1 #else # define DEBUG 0 #endif // Define SUPPORT_GC_COMPAT=1 to enable compatibility where GC once was. // OBJC_NO_GC and OBJC_NO_GC_API in objc-api.h mean something else. #if !TARGET_OS_OSX # define SUPPORT_GC_COMPAT 0 #else # define SUPPORT_GC_COMPAT 1 #endif // Define SUPPORT_ZONES=1 to enable malloc zone support in NXHashTable. #if !(TARGET_OS_OSX || TARGET_OS_IOSMAC) # define SUPPORT_ZONES 0 #else # define SUPPORT_ZONES 1 #endif // Define SUPPORT_MOD=1 to use the mod operator in NXHashTable and objc-sel-set #if defined(__arm__) # define SUPPORT_MOD 0 #else # define SUPPORT_MOD 1 #endif // Define SUPPORT_PREOPT=1 to enable dyld shared cache optimizations #if TARGET_OS_WIN32 || TARGET_OS_SIMULATOR # define SUPPORT_PREOPT 0 #else # define SUPPORT_PREOPT 1 #endif // Define SUPPORT_TAGGED_POINTERS=1 to enable tagged pointer objects // Be sure to edit tagged pointer SPI in objc-internal.h as well. #if !(__OBJC2__ && __LP64__) # define SUPPORT_TAGGED_POINTERS 0 #else # define SUPPORT_TAGGED_POINTERS 1 #endif // Define SUPPORT_MSB_TAGGED_POINTERS to use the MSB // as the tagged pointer marker instead of the LSB. // Be sure to edit tagged pointer SPI in objc-internal.h as well. #if !SUPPORT_TAGGED_POINTERS || (TARGET_OS_OSX || TARGET_OS_IOSMAC) # define SUPPORT_MSB_TAGGED_POINTERS 0 #else # define SUPPORT_MSB_TAGGED_POINTERS 1 #endif // Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa // field as an index into a class table. // Note, keep this in sync with any .s files which also define it. // Be sure to edit objc-abi.h as well. #if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__) # define SUPPORT_INDEXED_ISA 1 #else # define SUPPORT_INDEXED_ISA 0 #endif // Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa // field as a maskable pointer with other data around it. #if (!__LP64__ || TARGET_OS_WIN32 || \ (TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC)) # define SUPPORT_PACKED_ISA 0 #else # define SUPPORT_PACKED_ISA 1 #endif // Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something // in the isa field that is not a raw pointer. #if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA # define SUPPORT_NONPOINTER_ISA 0 #else # define SUPPORT_NONPOINTER_ISA 1 #endif // Define SUPPORT_FIXUP=1 to repair calls sites for fixup dispatch. // Fixup messaging itself is no longer supported. // Be sure to edit objc-abi.h as well (objc_msgSend*_fixup) #if !(defined(__x86_64__) && (TARGET_OS_OSX || TARGET_OS_SIMULATOR)) # define SUPPORT_FIXUP 0 #else # define SUPPORT_FIXUP 1 #endif // Define SUPPORT_ZEROCOST_EXCEPTIONS to use "zero-cost" exceptions for OBJC2. // Be sure to edit objc-exception.h as well (objc_add/removeExceptionHandler) #if !__OBJC2__ || (defined(__arm__) && __USING_SJLJ_EXCEPTIONS__) # define SUPPORT_ZEROCOST_EXCEPTIONS 0 #else # define SUPPORT_ZEROCOST_EXCEPTIONS 1 #endif // Define SUPPORT_ALT_HANDLERS if you're using zero-cost exceptions // but also need to support AppKit's alt-handler scheme // Be sure to edit objc-exception.h as well (objc_add/removeExceptionHandler) #if !SUPPORT_ZEROCOST_EXCEPTIONS || !TARGET_OS_OSX # define SUPPORT_ALT_HANDLERS 0 #else # define SUPPORT_ALT_HANDLERS 1 #endif // Define SUPPORT_RETURN_AUTORELEASE to optimize autoreleased return values #if TARGET_OS_WIN32 # define SUPPORT_RETURN_AUTORELEASE 0 #else # define SUPPORT_RETURN_AUTORELEASE 1 #endif // Define SUPPORT_STRET on architectures that need separate struct-return ABI. #if defined(__arm64__) # define SUPPORT_STRET 0 #else # define SUPPORT_STRET 1 #endif // Define SUPPORT_MESSAGE_LOGGING to enable NSObjCMessageLoggingEnabled #if !TARGET_OS_OSX # define SUPPORT_MESSAGE_LOGGING 0 #else # define SUPPORT_MESSAGE_LOGGING 1 #endif // OBJC_INSTRUMENTED controls whether message dispatching is dynamically // monitored. Monitoring introduces substantial overhead. // NOTE: To define this condition, do so in the build command, NOT by // uncommenting the line here. This is because objc-class.h heeds this // condition, but objc-class.h can not #include this file (objc-config.h) // because objc-class.h is public and objc-config.h is not. //#define OBJC_INSTRUMENTED #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-env.h ================================================ // -*- truncate-lines: t; -*- // OPTION(var, env, help) OPTION( PrintImages, OBJC_PRINT_IMAGES, "log image and library names as they are loaded") OPTION( PrintImageTimes, OBJC_PRINT_IMAGE_TIMES, "measure duration of image loading steps") OPTION( PrintLoading, OBJC_PRINT_LOAD_METHODS, "log calls to class and category +load methods") OPTION( PrintInitializing, OBJC_PRINT_INITIALIZE_METHODS, "log calls to class +initialize methods") OPTION( PrintResolving, OBJC_PRINT_RESOLVED_METHODS, "log methods created by +resolveClassMethod: and +resolveInstanceMethod:") OPTION( PrintConnecting, OBJC_PRINT_CLASS_SETUP, "log progress of class and category setup") OPTION( PrintProtocols, OBJC_PRINT_PROTOCOL_SETUP, "log progress of protocol setup") OPTION( PrintIvars, OBJC_PRINT_IVAR_SETUP, "log processing of non-fragile ivars") OPTION( PrintVtables, OBJC_PRINT_VTABLE_SETUP, "log processing of class vtables") OPTION( PrintVtableImages, OBJC_PRINT_VTABLE_IMAGES, "print vtable images showing overridden methods") OPTION( PrintCaches, OBJC_PRINT_CACHE_SETUP, "log processing of method caches") OPTION( PrintFuture, OBJC_PRINT_FUTURE_CLASSES, "log use of future classes for toll-free bridging") OPTION( PrintPreopt, OBJC_PRINT_PREOPTIMIZATION, "log preoptimization courtesy of dyld shared cache") OPTION( PrintCxxCtors, OBJC_PRINT_CXX_CTORS, "log calls to C++ ctors and dtors for instance variables") OPTION( PrintExceptions, OBJC_PRINT_EXCEPTIONS, "log exception handling") OPTION( PrintExceptionThrow, OBJC_PRINT_EXCEPTION_THROW, "log backtrace of every objc_exception_throw()") OPTION( PrintAltHandlers, OBJC_PRINT_ALT_HANDLERS, "log processing of exception alt handlers") OPTION( PrintReplacedMethods, OBJC_PRINT_REPLACED_METHODS, "log methods replaced by category implementations") OPTION( PrintDeprecation, OBJC_PRINT_DEPRECATION_WARNINGS, "warn about calls to deprecated runtime functions") OPTION( PrintPoolHiwat, OBJC_PRINT_POOL_HIGHWATER, "log high-water marks for autorelease pools") OPTION( PrintCustomRR, OBJC_PRINT_CUSTOM_RR, "log classes with un-optimized custom retain/release methods") OPTION( PrintCustomAWZ, OBJC_PRINT_CUSTOM_AWZ, "log classes with un-optimized custom allocWithZone methods") OPTION( PrintRawIsa, OBJC_PRINT_RAW_ISA, "log classes that require raw pointer isa fields") OPTION( DebugUnload, OBJC_DEBUG_UNLOAD, "warn about poorly-behaving bundles when unloaded") OPTION( DebugFragileSuperclasses, OBJC_DEBUG_FRAGILE_SUPERCLASSES, "warn about subclasses that may have been broken by subsequent changes to superclasses") OPTION( DebugNilSync, OBJC_DEBUG_NIL_SYNC, "warn about @synchronized(nil), which does no synchronization") OPTION( DebugNonFragileIvars, OBJC_DEBUG_NONFRAGILE_IVARS, "capriciously rearrange non-fragile ivars") OPTION( DebugAltHandlers, OBJC_DEBUG_ALT_HANDLERS, "record more info about bad alt handler use") OPTION( DebugMissingPools, OBJC_DEBUG_MISSING_POOLS, "warn about autorelease with no pool in place, which may be a leak") OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION, "halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools") OPTION( DebugDuplicateClasses, OBJC_DEBUG_DUPLICATE_CLASSES, "halt when multiple classes with the same name are present") OPTION( DebugDontCrash, OBJC_DEBUG_DONT_CRASH, "halt the process by exiting instead of crashing") OPTION( DisableVtables, OBJC_DISABLE_VTABLES, "disable vtable dispatch") OPTION( DisablePreopt, OBJC_DISABLE_PREOPTIMIZATION, "disable preoptimization courtesy of dyld shared cache") OPTION( DisableTaggedPointers, OBJC_DISABLE_TAGGED_POINTERS, "disable tagged pointer optimization of NSNumber et al.") OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers") OPTION( DisableNonpointerIsa, OBJC_DISABLE_NONPOINTER_ISA, "disable non-pointer isa fields") OPTION( DisableInitializeForkSafety, OBJC_DISABLE_INITIALIZE_FORK_SAFETY, "disable safety checks for +initialize after fork") ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-errors.mm ================================================ /* * Copyright (c) 1999-2003, 2005-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc-errors.m * Copyright 1988-2001, NeXT Software, Inc., Apple Computer, Inc. */ #include "objc-private.h" #if TARGET_OS_WIN32 #include void _objc_inform_on_crash(const char *fmt, ...) { } void _objc_inform(const char *fmt, ...) { va_list args; va_start(args, fmt); _vcprintf(fmt, args); va_end(args); _cprintf("\n"); } void _objc_fatal(const char *fmt, ...) { va_list args; va_start(args, fmt); _vcprintf(fmt, args); va_end(args); _cprintf("\n"); abort(); } void __objc_error(id rcv, const char *fmt, ...) { va_list args; va_start(args, fmt); _vcprintf(fmt, args); va_end(args); abort(); } void _objc_error(id rcv, const char *fmt, va_list args) { _vcprintf(fmt, args); abort(); } #else #include <_simple.h> // Return true if c is a UTF8 continuation byte static bool isUTF8Continuation(char c) { return (c & 0xc0) == 0x80; // continuation byte is 0b10xxxxxx } // Add "message" to any forthcoming crash log. mutex_t crashlog_lock; static void _objc_crashlog(const char *message) { char *newmsg; #if 0 { // for debugging at BOOT time. extern char **_NSGetProgname(void); FILE *crashlog = fopen("/_objc_crash.log", "a"); setbuf(crashlog, NULL); fprintf(crashlog, "[%s] %s\n", *_NSGetProgname(), message); fclose(crashlog); sync(); } #endif mutex_locker_t lock(crashlog_lock); char *oldmsg = (char *)CRGetCrashLogMessage(); size_t oldlen; const size_t limit = 8000; if (!oldmsg) { newmsg = strdup(message); } else if ((oldlen = strlen(oldmsg)) > limit) { // limit total length by dropping old contents char *truncmsg = oldmsg + oldlen - limit; // advance past partial UTF-8 bytes while (isUTF8Continuation(*truncmsg)) truncmsg++; asprintf(&newmsg, "... %s\n%s", truncmsg, message); } else { asprintf(&newmsg, "%s\n%s", oldmsg, message); } if (newmsg) { // Strip trailing newline char *c = &newmsg[strlen(newmsg)-1]; if (*c == '\n') *c = '\0'; if (oldmsg) free(oldmsg); CRSetCrashLogMessage(newmsg); } } // Returns true if logs should be sent to stderr as well as syslog. // Copied from CFUtilities.c static bool also_do_stderr(void) { struct stat st; int ret = fstat(STDERR_FILENO, &st); if (ret < 0) return false; mode_t m = st.st_mode & S_IFMT; if (m == S_IFREG || m == S_IFSOCK || m == S_IFIFO || m == S_IFCHR) { return true; } return false; } // Print "message" to the console. static void _objc_syslog(const char *message) { _simple_asl_log(ASL_LEVEL_ERR, nil, message); if (also_do_stderr()) { write(STDERR_FILENO, message, strlen(message)); } } /* * _objc_error is the default *_error handler. */ #if !__OBJC2__ // used by ExceptionHandling.framework #endif __attribute__((noreturn)) void _objc_error(id self, const char *fmt, va_list ap) { char *buf; vasprintf(&buf, fmt, ap); _objc_fatal("%s: %s", object_getClassName(self), buf); } /* * this routine handles errors that involve an object (or class). */ void __objc_error(id rcv, const char *fmt, ...) { va_list vp; va_start(vp,fmt); #if !__OBJC2__ (*_error)(rcv, fmt, vp); #endif _objc_error (rcv, fmt, vp); /* In case (*_error)() returns. */ va_end(vp); } static __attribute__((noreturn)) void _objc_fatalv(uint64_t reason, uint64_t flags, const char *fmt, va_list ap) { char *buf1; vasprintf(&buf1, fmt, ap); char *buf2; asprintf(&buf2, "objc[%d]: %s\n", getpid(), buf1); _objc_syslog(buf2); if (DebugDontCrash) { char *buf3; asprintf(&buf3, "objc[%d]: HALTED\n", getpid()); _objc_syslog(buf3); _Exit(1); } else { abort_with_reason(OS_REASON_OBJC, reason, buf1, flags); } } void _objc_fatal_with_reason(uint64_t reason, uint64_t flags, const char *fmt, ...) { va_list ap; va_start(ap, fmt); _objc_fatalv(reason, flags, fmt, ap); } void _objc_fatal(const char *fmt, ...) { va_list ap; va_start(ap,fmt); _objc_fatalv(OBJC_EXIT_REASON_UNSPECIFIED, OS_REASON_FLAG_ONE_TIME_FAILURE, fmt, ap); } /* * this routine handles soft runtime errors...like not being able * add a category to a class (because it wasn't linked in). */ void _objc_inform(const char *fmt, ...) { va_list ap; char *buf1; char *buf2; va_start (ap,fmt); vasprintf(&buf1, fmt, ap); va_end (ap); asprintf(&buf2, "objc[%d]: %s\n", getpid(), buf1); _objc_syslog(buf2); free(buf2); free(buf1); } /* * Like _objc_inform(), but prints the message only in any * forthcoming crash log, not to the console. */ void _objc_inform_on_crash(const char *fmt, ...) { va_list ap; char *buf1; char *buf2; va_start (ap,fmt); vasprintf(&buf1, fmt, ap); va_end (ap); asprintf(&buf2, "objc[%d]: %s\n", getpid(), buf1); _objc_crashlog(buf2); free(buf2); free(buf1); } /* * Like calling both _objc_inform and _objc_inform_on_crash. */ void _objc_inform_now_and_on_crash(const char *fmt, ...) { va_list ap; char *buf1; char *buf2; va_start (ap,fmt); vasprintf(&buf1, fmt, ap); va_end (ap); asprintf(&buf2, "objc[%d]: %s\n", getpid(), buf1); _objc_crashlog(buf2); _objc_syslog(buf2); free(buf2); free(buf1); } #endif BREAKPOINT_FUNCTION( void _objc_warn_deprecated(void) ); void _objc_inform_deprecated(const char *oldf, const char *newf) { if (PrintDeprecation) { if (newf) { _objc_inform("The function %s is obsolete. Use %s instead. Set a breakpoint on _objc_warn_deprecated to find the culprit.", oldf, newf); } else { _objc_inform("The function %s is obsolete. Do not use it. Set a breakpoint on _objc_warn_deprecated to find the culprit.", oldf); } } _objc_warn_deprecated(); } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-exception.h ================================================ /* * Copyright (c) 2002-2003, 2006-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __OBJC_EXCEPTION_H_ #define __OBJC_EXCEPTION_H_ #include #include #if !__OBJC2__ // compiler reserves a setjmp buffer + 4 words as localExceptionData OBJC_EXPORT void objc_exception_throw(id _Nonnull exception) __OSX_AVAILABLE(10.3) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_exception_try_enter(void * _Nonnull localExceptionData) __OSX_AVAILABLE(10.3) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_exception_try_exit(void * _Nonnull localExceptionData) __OSX_AVAILABLE(10.3) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id _Nonnull objc_exception_extract(void * _Nonnull localExceptionData) __OSX_AVAILABLE(10.3) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT int objc_exception_match(Class _Nonnull exceptionClass, id _Nonnull exception) __OSX_AVAILABLE(10.3) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; typedef struct { int version; void (* _Nonnull throw_exc)(id _Nonnull); // version 0 void (* _Nonnull try_enter)(void * _Nonnull); // version 0 void (* _Nonnull try_exit)(void * _Nonnull); // version 0 id _Nonnull (* _Nonnull extract)(void * _Nonnull); // version 0 int (* _Nonnull match)(Class _Nonnull, id _Nonnull); // version 0 } objc_exception_functions_t; // get table; version tells how many OBJC_EXPORT void objc_exception_get_functions(objc_exception_functions_t * _Nullable table) __OSX_AVAILABLE(10.3) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // set table OBJC_EXPORT void objc_exception_set_functions(objc_exception_functions_t * _Nullable table) __OSX_AVAILABLE(10.3) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // !__OBJC2__ #else // __OBJC2__ typedef id _Nonnull (*objc_exception_preprocessor)(id _Nonnull exception); typedef int (*objc_exception_matcher)(Class _Nonnull catch_type, id _Nonnull exception); typedef void (*objc_uncaught_exception_handler)(id _Null_unspecified /* _Nonnull */ exception); typedef void (*objc_exception_handler)(id _Nullable unused, void * _Nullable context); /** * Throw a runtime exception. This function is inserted by the compiler * where \c @throw would otherwise be. * * @param exception The exception to be thrown. */ OBJC_EXPORT void objc_exception_throw(id _Nonnull exception) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_exception_rethrow(void) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nonnull objc_begin_catch(void * _Nonnull exc_buf) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_end_catch(void) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_terminate(void) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); OBJC_EXPORT objc_exception_preprocessor _Nonnull objc_setExceptionPreprocessor(objc_exception_preprocessor _Nonnull fn) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT objc_exception_matcher _Nonnull objc_setExceptionMatcher(objc_exception_matcher _Nonnull fn) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT objc_uncaught_exception_handler _Nonnull objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler _Nonnull fn) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // Not for iOS. OBJC_EXPORT uintptr_t objc_addExceptionHandler(objc_exception_handler _Nonnull fn, void * _Nullable context) __OSX_AVAILABLE(10.5) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_removeExceptionHandler(uintptr_t token) __OSX_AVAILABLE(10.5) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // __OBJC2__ #endif #endif // __OBJC_EXCEPTION_H_ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-exception.mm ================================================ /* * Copyright (c) 2002-2007 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #if !__OBJC2__ /*********************************************************************** * 32-bit implementation **********************************************************************/ #include "objc-private.h" #include #include #include #include "objc-exception.h" static objc_exception_functions_t xtab; // forward declaration static void set_default_handlers(); /* * Exported functions */ // get table; version tells how many void objc_exception_get_functions(objc_exception_functions_t *table) { // only version 0 supported at this point if (table && table->version == 0) *table = xtab; } // set table void objc_exception_set_functions(objc_exception_functions_t *table) { // only version 0 supported at this point if (table && table->version == 0) xtab = *table; } /* * The following functions are * synthesized by the compiler upon encountering language constructs */ void objc_exception_throw(id exception) { if (!xtab.throw_exc) { set_default_handlers(); } if (PrintExceptionThrow) { _objc_inform("EXCEPTIONS: throwing %p (%s)", (void*)exception, object_getClassName(exception)); void* callstack[500]; int frameCount = backtrace(callstack, 500); backtrace_symbols_fd(callstack, frameCount, fileno(stderr)); } OBJC_RUNTIME_OBJC_EXCEPTION_THROW(exception); // dtrace probe to log throw activity. xtab.throw_exc(exception); _objc_fatal("objc_exception_throw failed"); } void objc_exception_try_enter(void *localExceptionData) { if (!xtab.throw_exc) { set_default_handlers(); } xtab.try_enter(localExceptionData); } void objc_exception_try_exit(void *localExceptionData) { if (!xtab.throw_exc) { set_default_handlers(); } xtab.try_exit(localExceptionData); } id objc_exception_extract(void *localExceptionData) { if (!xtab.throw_exc) { set_default_handlers(); } return xtab.extract(localExceptionData); } int objc_exception_match(Class exceptionClass, id exception) { if (!xtab.throw_exc) { set_default_handlers(); } return xtab.match(exceptionClass, exception); } // quick and dirty exception handling code // default implementation - mostly a toy for use outside/before Foundation // provides its implementation // Perhaps the default implementation should just complain loudly and quit extern void _objc_inform(const char *fmt, ...); typedef struct { jmp_buf buf; void *pointers[4]; } LocalData_t; typedef struct _threadChain { LocalData_t *topHandler; objc_thread_t perThreadID; struct _threadChain *next; } ThreadChainLink_t; static ThreadChainLink_t ThreadChainLink; static ThreadChainLink_t *getChainLink() { // follow links until thread_self() found (someday) XXX objc_thread_t self = thread_self(); ThreadChainLink_t *walker = &ThreadChainLink; while (walker->perThreadID != self) { if (walker->next != nil) { walker = walker->next; continue; } // create a new one // XXX not thread safe (!) // XXX Also, we don't register to deallocate on thread death walker->next = (ThreadChainLink_t *)malloc(sizeof(ThreadChainLink_t)); walker = walker->next; walker->next = nil; walker->topHandler = nil; walker->perThreadID = self; } return walker; } static void default_try_enter(void *localExceptionData) { LocalData_t *data = (LocalData_t *)localExceptionData; ThreadChainLink_t *chainLink = getChainLink(); data->pointers[1] = chainLink->topHandler; chainLink->topHandler = data; if (PrintExceptions) _objc_inform("EXCEPTIONS: entered try block %p\n", chainLink->topHandler); } static void default_throw(id value) { ThreadChainLink_t *chainLink = getChainLink(); LocalData_t *led; if (value == nil) { if (PrintExceptions) _objc_inform("EXCEPTIONS: objc_exception_throw with nil value\n"); return; } if (chainLink == nil) { if (PrintExceptions) _objc_inform("EXCEPTIONS: No handler in place!\n"); return; } if (PrintExceptions) _objc_inform("EXCEPTIONS: exception thrown, going to handler block %p\n", chainLink->topHandler); led = chainLink->topHandler; chainLink->topHandler = (LocalData_t *) led->pointers[1]; // pop top handler led->pointers[0] = value; // store exception that is thrown #if TARGET_OS_WIN32 longjmp(led->buf, 1); #else _longjmp(led->buf, 1); #endif } static void default_try_exit(void *led) { ThreadChainLink_t *chainLink = getChainLink(); if (!chainLink || led != chainLink->topHandler) { if (PrintExceptions) _objc_inform("EXCEPTIONS: *** mismatched try block exit handlers\n"); return; } if (PrintExceptions) _objc_inform("EXCEPTIONS: removing try block handler %p\n", chainLink->topHandler); chainLink->topHandler = (LocalData_t *) chainLink->topHandler->pointers[1]; // pop top handler } static id default_extract(void *localExceptionData) { LocalData_t *led = (LocalData_t *)localExceptionData; return (id)led->pointers[0]; } static int default_match(Class exceptionClass, id exception) { //return [exception isKindOfClass:exceptionClass]; Class cls; for (cls = exception->getIsa(); nil != cls; cls = cls->superclass) if (cls == exceptionClass) return 1; return 0; } static void set_default_handlers() { objc_exception_functions_t default_functions = { 0, default_throw, default_try_enter, default_try_exit, default_extract, default_match }; // should this always print? if (PrintExceptions) _objc_inform("EXCEPTIONS: *** Setting default (non-Foundation) exception mechanism\n"); objc_exception_set_functions(&default_functions); } void exception_init(void) { // nothing to do } void _destroyAltHandlerList(struct alt_handler_list *list) { // nothing to do } // !__OBJC2__ #else // __OBJC2__ /*********************************************************************** * 64-bit implementation. **********************************************************************/ #include "objc-private.h" #include #include #include #include // unwind library types and functions // Mostly adapted from Itanium C++ ABI: Exception Handling // http://www.codesourcery.com/cxx-abi/abi-eh.html struct _Unwind_Exception; struct _Unwind_Context; typedef int _Unwind_Action; enum : _Unwind_Action { _UA_SEARCH_PHASE = 1, _UA_CLEANUP_PHASE = 2, _UA_HANDLER_FRAME = 4, _UA_FORCE_UNWIND = 8 }; typedef int _Unwind_Reason_Code; enum : _Unwind_Reason_Code { _URC_NO_REASON = 0, _URC_FOREIGN_EXCEPTION_CAUGHT = 1, _URC_FATAL_PHASE2_ERROR = 2, _URC_FATAL_PHASE1_ERROR = 3, _URC_NORMAL_STOP = 4, _URC_END_OF_STACK = 5, _URC_HANDLER_FOUND = 6, _URC_INSTALL_CONTEXT = 7, _URC_CONTINUE_UNWIND = 8 }; struct dwarf_eh_bases { uintptr_t tbase; uintptr_t dbase; uintptr_t func; }; OBJC_EXTERN uintptr_t _Unwind_GetIP (struct _Unwind_Context *); OBJC_EXTERN uintptr_t _Unwind_GetCFA (struct _Unwind_Context *); OBJC_EXTERN uintptr_t _Unwind_GetLanguageSpecificData(struct _Unwind_Context *); // C++ runtime types and functions // copied from cxxabi.h OBJC_EXTERN void *__cxa_allocate_exception(size_t thrown_size); OBJC_EXTERN void __cxa_throw(void *exc, void *typeinfo, void (*destructor)(void *)) __attribute__((noreturn)); OBJC_EXTERN void *__cxa_begin_catch(void *exc); OBJC_EXTERN void __cxa_end_catch(void); OBJC_EXTERN void __cxa_rethrow(void); OBJC_EXTERN void *__cxa_current_exception_type(void); #if SUPPORT_ZEROCOST_EXCEPTIONS # define CXX_PERSONALITY __gxx_personality_v0 #else # define CXX_PERSONALITY __gxx_personality_sj0 #endif OBJC_EXTERN _Unwind_Reason_Code CXX_PERSONALITY(int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context); // objc's internal exception types and data struct objc_typeinfo { // Position of vtable and name fields must match C++ typeinfo object const void ** __ptrauth_cxx_vtable_pointer vtable; // objc_ehtype_vtable+2 const char *name; // c++ typeinfo string Class cls_unremapped; }; struct objc_exception { id obj; struct objc_typeinfo tinfo; }; extern "C" void _objc_exception_noop(void) { } extern "C" bool _objc_exception_false(void) { return 0; } // extern "C" bool _objc_exception_true(void) { return 1; } extern "C" void _objc_exception_abort1(void) { _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 1); } extern "C" void _objc_exception_abort2(void) { _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 2); } extern "C" void _objc_exception_abort3(void) { _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 3); } extern "C" void _objc_exception_abort4(void) { _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 4); } extern "C" bool _objc_exception_do_catch(struct objc_typeinfo *catch_tinfo, struct objc_typeinfo *throw_tinfo, void **throw_obj_p, unsigned outer); // C++ pointers to vtables are signed with no extra data. // C++ vtable entries are signed with a number derived from the function name. // For this fake vtable, we hardcode number as deciphered from the // assembly output during libc++abi's build. #if __has_feature(ptrauth_calls) # define VTABLE_PTR_AUTH "@AUTH(da, 0)" # define VTABLE_ENTRY_AUTH(x) "@AUTH(ia," #x ",addr)" #else # define VTABLE_PTR_AUTH "" # define VTABLE_ENTRY_AUTH(x) "" #endif #if __LP64__ # define PTR ".quad " # define TWOPTRSIZE "16" #else # define PTR ".long " # define TWOPTRSIZE "8" #endif // Hand-built vtable for objc exception typeinfo. // "OLD" is GNU libcpp, "NEW" is libc++abi. asm( "\n .cstring" "\n l_.id_str: .asciz \"id\"" "\n .section __DATA,__const" "\n .globl _OBJC_EHTYPE_id" "\n .globl _objc_ehtype_vtable" "\n .p2align 4" "\n _OBJC_EHTYPE_id:" "\n " PTR "(_objc_ehtype_vtable+" TWOPTRSIZE ") " VTABLE_PTR_AUTH "\n " PTR "l_.id_str" "\n " PTR "0" "\n _objc_ehtype_vtable:" "\n " PTR "0" // typeinfo's typeinfo - fixme hack "\n " PTR "_OBJC_EHTYPE_id" // destructor and in-place destructor "\n " PTR "__objc_exception_noop" VTABLE_ENTRY_AUTH(52634) "\n " PTR "__objc_exception_noop" VTABLE_ENTRY_AUTH(10344) // OLD __is_pointer_p "\n " PTR "__objc_exception_noop" VTABLE_ENTRY_AUTH(6889) // OLD __is_function_p "\n " PTR "__objc_exception_noop" VTABLE_ENTRY_AUTH(23080) // OLD __do_catch, NEW can_catch "\n " PTR "__objc_exception_do_catch" VTABLE_ENTRY_AUTH(27434) // OLD __do_upcast, NEW search_above_dst "\n " PTR "__objc_exception_false" VTABLE_ENTRY_AUTH(48481) // NEW search_below_dst "\n " PTR "__objc_exception_false" VTABLE_ENTRY_AUTH(41165) // NEW has_unambiguous_public_base (fixme need this?) "\n " PTR "__objc_exception_abort1" VTABLE_ENTRY_AUTH(14357) // paranoia: die if libcxxabi adds anything else "\n " PTR "__objc_exception_abort2" "\n " PTR "__objc_exception_abort3" "\n " PTR "__objc_exception_abort4" ); /*********************************************************************** * Foundation customization **********************************************************************/ /*********************************************************************** * _objc_default_exception_preprocessor * Default exception preprocessor. Expected to be overridden by Foundation. **********************************************************************/ static id _objc_default_exception_preprocessor(id exception) { return exception; } static objc_exception_preprocessor exception_preprocessor = _objc_default_exception_preprocessor; /*********************************************************************** * _objc_default_exception_matcher * Default exception matcher. Expected to be overridden by Foundation. **********************************************************************/ static int _objc_default_exception_matcher(Class catch_cls, id exception) { Class cls; for (cls = exception->getIsa(); cls != nil; cls = cls->superclass) { if (cls == catch_cls) return 1; } return 0; } static objc_exception_matcher exception_matcher = _objc_default_exception_matcher; /*********************************************************************** * _objc_default_uncaught_exception_handler * Default uncaught exception handler. Expected to be overridden by Foundation. **********************************************************************/ static void _objc_default_uncaught_exception_handler(id exception) { } static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler; /*********************************************************************** * objc_setExceptionPreprocessor * Set a handler for preprocessing Objective-C exceptions. * Returns the previous handler. **********************************************************************/ objc_exception_preprocessor objc_setExceptionPreprocessor(objc_exception_preprocessor fn) { objc_exception_preprocessor result = exception_preprocessor; exception_preprocessor = fn; return result; } /*********************************************************************** * objc_setExceptionMatcher * Set a handler for matching Objective-C exceptions. * Returns the previous handler. **********************************************************************/ objc_exception_matcher objc_setExceptionMatcher(objc_exception_matcher fn) { objc_exception_matcher result = exception_matcher; exception_matcher = fn; return result; } /*********************************************************************** * objc_setUncaughtExceptionHandler * Set a handler for uncaught Objective-C exceptions. * Returns the previous handler. **********************************************************************/ objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn) { objc_uncaught_exception_handler result = uncaught_handler; uncaught_handler = fn; return result; } /*********************************************************************** * Exception personality **********************************************************************/ static void call_alt_handlers(struct _Unwind_Context *ctx); _Unwind_Reason_Code __objc_personality_v0(int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { bool unwinding = ((actions & _UA_CLEANUP_PHASE) || (actions & _UA_FORCE_UNWIND)); if (PrintExceptions) { _objc_inform("EXCEPTIONS: %s through frame [ip=%p sp=%p] " "for exception %p", unwinding ? "unwinding" : "searching", (void*)(_Unwind_GetIP(context)-1), (void*)_Unwind_GetCFA(context), exceptionObject); } // If we're executing the unwind, call this frame's alt handlers, if any. if (unwinding) { call_alt_handlers(context); } // Let C++ handle the unwind itself. return CXX_PERSONALITY(version, actions, exceptionClass, exceptionObject, context); } /*********************************************************************** * Compiler ABI **********************************************************************/ static void _objc_exception_destructor(void *exc_gen) { // Release the retain from objc_exception_throw(). struct objc_exception *exc = (struct objc_exception *)exc_gen; id obj = exc->obj; if (PrintExceptions) { _objc_inform("EXCEPTIONS: releasing completed exception %p (object %p, a %s)", exc, obj, object_getClassName(obj)); } [obj release]; } void objc_exception_throw(id obj) { struct objc_exception *exc = (struct objc_exception *) __cxa_allocate_exception(sizeof(struct objc_exception)); obj = (*exception_preprocessor)(obj); // Retain the exception object during unwinding // because otherwise an autorelease pool pop can cause a crash [obj retain]; exc->obj = obj; exc->tinfo.vtable = objc_ehtype_vtable+2; exc->tinfo.name = object_getClassName(obj); exc->tinfo.cls_unremapped = obj ? obj->getIsa() : Nil; if (PrintExceptions) { _objc_inform("EXCEPTIONS: throwing %p (object %p, a %s)", exc, (void*)obj, object_getClassName(obj)); } if (PrintExceptionThrow) { if (!PrintExceptions) _objc_inform("EXCEPTIONS: throwing %p (object %p, a %s)", exc, (void*)obj, object_getClassName(obj)); void* callstack[500]; int frameCount = backtrace(callstack, 500); backtrace_symbols_fd(callstack, frameCount, fileno(stderr)); } OBJC_RUNTIME_OBJC_EXCEPTION_THROW(obj); // dtrace probe to log throw activity __cxa_throw(exc, &exc->tinfo, &_objc_exception_destructor); __builtin_trap(); } void objc_exception_rethrow(void) { // exception_preprocessor doesn't get another bite of the apple if (PrintExceptions) { _objc_inform("EXCEPTIONS: rethrowing current exception"); } OBJC_RUNTIME_OBJC_EXCEPTION_RETHROW(); // dtrace probe to log throw activity. __cxa_rethrow(); __builtin_trap(); } id objc_begin_catch(void *exc_gen) { if (PrintExceptions) { _objc_inform("EXCEPTIONS: handling exception %p at %p", exc_gen, __builtin_return_address(0)); } // NOT actually an id in the catch(...) case! return (id)__cxa_begin_catch(exc_gen); } void objc_end_catch(void) { if (PrintExceptions) { _objc_inform("EXCEPTIONS: finishing handler"); } __cxa_end_catch(); } // `outer` is not passed by the new libcxxabi bool _objc_exception_do_catch(struct objc_typeinfo *catch_tinfo, struct objc_typeinfo *throw_tinfo, void **throw_obj_p, unsigned outer UNAVAILABLE_ATTRIBUTE) { id exception; if (throw_tinfo->vtable != objc_ehtype_vtable+2) { // Only objc types can be caught here. if (PrintExceptions) _objc_inform("EXCEPTIONS: skipping catch(?)"); return false; } // Adjust exception pointer. // Old libcppabi: we lied about __is_pointer_p() so we have to do it here // New libcxxabi: we have to do it here regardless *throw_obj_p = **(void***)throw_obj_p; // `catch (id)` always catches objc types. if (catch_tinfo == &OBJC_EHTYPE_id) { if (PrintExceptions) _objc_inform("EXCEPTIONS: catch(id)"); return true; } exception = *(id *)throw_obj_p; Class handler_cls = _class_remap(catch_tinfo->cls_unremapped); if (!handler_cls) { // catch handler's class is weak-linked and missing. Not a match. } else if ((*exception_matcher)(handler_cls, exception)) { if (PrintExceptions) _objc_inform("EXCEPTIONS: catch(%s)", handler_cls->nameForLogging()); return true; } if (PrintExceptions) _objc_inform("EXCEPTIONS: skipping catch(%s)", handler_cls->nameForLogging()); return false; } /*********************************************************************** * _objc_terminate * Custom std::terminate handler. * * The uncaught exception callback is implemented as a std::terminate handler. * 1. Check if there's an active exception * 2. If so, check if it's an Objective-C exception * 3. If so, call our registered callback with the object. * 4. Finally, call the previous terminate handler. **********************************************************************/ static void (*old_terminate)(void) = nil; static void _objc_terminate(void) { if (PrintExceptions) { _objc_inform("EXCEPTIONS: terminating"); } if (! __cxa_current_exception_type()) { // No current exception. (*old_terminate)(); } else { // There is a current exception. Check if it's an objc exception. @try { __cxa_rethrow(); } @catch (id e) { // It's an objc object. Call Foundation's handler, if any. (*uncaught_handler)((id)e); (*old_terminate)(); } @catch (...) { // It's not an objc object. Continue to C++ terminate. (*old_terminate)(); } } } /*********************************************************************** * objc_terminate * Calls std::terminate for clients who don't link to C++ themselves. * Called by the compiler if an exception is thrown * from a context where exceptions may not be thrown. **********************************************************************/ void objc_terminate(void) { std::terminate(); } /*********************************************************************** * alt handler support - zerocost implementation only **********************************************************************/ #if !SUPPORT_ALT_HANDLERS void _destroyAltHandlerList(struct alt_handler_list *list) { } static void call_alt_handlers(struct _Unwind_Context *ctx) { // unsupported in sjlj environments } #else #include #include #include // Dwarf eh data encodings #define DW_EH_PE_omit 0xff // no data follows #define DW_EH_PE_absptr 0x00 #define DW_EH_PE_uleb128 0x01 #define DW_EH_PE_udata2 0x02 #define DW_EH_PE_udata4 0x03 #define DW_EH_PE_udata8 0x04 #define DW_EH_PE_sleb128 0x09 #define DW_EH_PE_sdata2 0x0A #define DW_EH_PE_sdata4 0x0B #define DW_EH_PE_sdata8 0x0C #define DW_EH_PE_pcrel 0x10 #define DW_EH_PE_textrel 0x20 #define DW_EH_PE_datarel 0x30 #define DW_EH_PE_funcrel 0x40 #define DW_EH_PE_aligned 0x50 // fixme #define DW_EH_PE_indirect 0x80 // gcc extension /*********************************************************************** * read_uleb * Read a LEB-encoded unsigned integer from the address stored in *pp. * Increments *pp past the bytes read. * Adapted from DWARF Debugging Information Format 1.1, appendix 4 **********************************************************************/ static uintptr_t read_uleb(uintptr_t *pp) { uintptr_t result = 0; uintptr_t shift = 0; unsigned char byte; do { byte = *(const unsigned char *)(*pp)++; result |= (byte & 0x7f) << shift; shift += 7; } while (byte & 0x80); return result; } /*********************************************************************** * read_sleb * Read a LEB-encoded signed integer from the address stored in *pp. * Increments *pp past the bytes read. * Adapted from DWARF Debugging Information Format 1.1, appendix 4 **********************************************************************/ static intptr_t read_sleb(uintptr_t *pp) { uintptr_t result = 0; uintptr_t shift = 0; unsigned char byte; do { byte = *(const unsigned char *)(*pp)++; result |= (byte & 0x7f) << shift; shift += 7; } while (byte & 0x80); if ((shift < 8*sizeof(intptr_t)) && (byte & 0x40)) { result |= ((intptr_t)-1) << shift; } return result; } /*********************************************************************** * read_address * Reads an encoded address from the address stored in *pp. * Increments *pp past the bytes read. * The data is interpreted according to the given dwarf encoding * and base addresses. **********************************************************************/ static uintptr_t read_address(uintptr_t *pp, const struct dwarf_eh_bases *bases, unsigned char encoding) { uintptr_t result = 0; uintptr_t oldp = *pp; // fixme need DW_EH_PE_aligned? #define READ(type) \ result = *(type *)(*pp); \ *pp += sizeof(type); if (encoding == DW_EH_PE_omit) return 0; switch (encoding & 0x0f) { case DW_EH_PE_absptr: READ(uintptr_t); break; case DW_EH_PE_uleb128: result = read_uleb(pp); break; case DW_EH_PE_udata2: READ(uint16_t); break; case DW_EH_PE_udata4: READ(uint32_t); break; #if __LP64__ case DW_EH_PE_udata8: READ(uint64_t); break; #endif case DW_EH_PE_sleb128: result = read_sleb(pp); break; case DW_EH_PE_sdata2: READ(int16_t); break; case DW_EH_PE_sdata4: READ(int32_t); break; #if __LP64__ case DW_EH_PE_sdata8: READ(int64_t); break; #endif default: _objc_inform("unknown DWARF EH encoding 0x%x at %p", encoding, (void *)*pp); break; } #undef READ if (result) { switch (encoding & 0x70) { case DW_EH_PE_pcrel: // fixme correct? result += (uintptr_t)oldp; break; case DW_EH_PE_textrel: result += bases->tbase; break; case DW_EH_PE_datarel: result += bases->dbase; break; case DW_EH_PE_funcrel: result += bases->func; break; case DW_EH_PE_aligned: _objc_inform("unknown DWARF EH encoding 0x%x at %p", encoding, (void *)*pp); break; default: // no adjustment break; } if (encoding & DW_EH_PE_indirect) { result = *(uintptr_t *)result; } } return (uintptr_t)result; } struct frame_ips { uintptr_t start; uintptr_t end; }; struct frame_range { uintptr_t ip_start; uintptr_t ip_end; uintptr_t cfa; // precise ranges within ip_start..ip_end; nil or {0,0} terminated frame_ips *ips; }; static bool isObjCExceptionCatcher(uintptr_t lsda, uintptr_t ip, const struct dwarf_eh_bases* bases, struct frame_range *frame) { unsigned char LPStart_enc = *(const unsigned char *)lsda++; if (LPStart_enc != DW_EH_PE_omit) { read_address(&lsda, bases, LPStart_enc); // LPStart } unsigned char TType_enc = *(const unsigned char *)lsda++; if (TType_enc != DW_EH_PE_omit) { read_uleb(&lsda); // TType } unsigned char call_site_enc = *(const unsigned char *)lsda++; uintptr_t length = read_uleb(&lsda); uintptr_t call_site_table = lsda; uintptr_t call_site_table_end = call_site_table + length; uintptr_t action_record_table = call_site_table_end; uintptr_t action_record = 0; uintptr_t p = call_site_table; uintptr_t try_start; uintptr_t try_end; uintptr_t try_landing_pad; while (p < call_site_table_end) { uintptr_t start = read_address(&p, bases, call_site_enc)+bases->func; uintptr_t len = read_address(&p, bases, call_site_enc); uintptr_t pad = read_address(&p, bases, call_site_enc); uintptr_t action = read_uleb(&p); if (ip < start) { // no more source ranges return false; } else if (ip < start + len) { // found the range if (!pad) return false; // ...but it has no landing pad // found the landing pad action_record = action ? action_record_table + action - 1 : 0; try_start = start; try_end = start + len; try_landing_pad = pad; break; } } if (!action_record) return false; // no catch handlers // has handlers, destructors, and/or throws specifications // Use this frame if it has any handlers bool has_handler = false; p = action_record; intptr_t offset; do { intptr_t filter = read_sleb(&p); uintptr_t temp = p; offset = read_sleb(&temp); p += offset; if (filter < 0) { // throws specification - ignore } else if (filter == 0) { // destructor - ignore } else /* filter >= 0 */ { // catch handler - use this frame has_handler = true; break; } } while (offset); if (!has_handler) return false; // Count the number of source ranges with the same landing pad as our match unsigned int range_count = 0; p = call_site_table; while (p < call_site_table_end) { /*start*/ read_address(&p, bases, call_site_enc)/*+bases->func*/; /*len*/ read_address(&p, bases, call_site_enc); uintptr_t pad = read_address(&p, bases, call_site_enc); /*action*/ read_uleb(&p); if (pad == try_landing_pad) { range_count++; } } if (range_count == 1) { // No other source ranges with the same landing pad. We're done here. frame->ips = nil; } else { // Record all ranges with the same landing pad as our match. frame->ips = (frame_ips *) malloc((range_count + 1) * sizeof(frame->ips[0])); unsigned int r = 0; p = call_site_table; while (p < call_site_table_end) { uintptr_t start = read_address(&p, bases, call_site_enc)+bases->func; uintptr_t len = read_address(&p, bases, call_site_enc); uintptr_t pad = read_address(&p, bases, call_site_enc); /*action*/ read_uleb(&p); if (pad == try_landing_pad) { if (start < try_start) try_start = start; if (start+len > try_end) try_end = start+len; frame->ips[r].start = start; frame->ips[r].end = start+len; r++; } } frame->ips[r].start = 0; frame->ips[r].end = 0; } frame->ip_start = try_start; frame->ip_end = try_end; return true; } static struct frame_range findHandler(void) { // walk stack looking for frame with objc catch handler unw_context_t uc; unw_cursor_t cursor; unw_proc_info_t info; unw_getcontext(&uc); unw_init_local(&cursor, &uc); while ( (unw_step(&cursor) > 0) && (unw_get_proc_info(&cursor, &info) == UNW_ESUCCESS) ) { // must use objc personality handler if ( info.handler != (uintptr_t)__objc_personality_v0 ) continue; // must have landing pad if ( info.lsda == 0 ) continue; // must have landing pad that catches objc exceptions struct dwarf_eh_bases bases; bases.tbase = 0; // from unwind-dw2-fde-darwin.c:examine_objects() bases.dbase = 0; // from unwind-dw2-fde-darwin.c:examine_objects() bases.func = info.start_ip; unw_word_t ip; unw_get_reg(&cursor, UNW_REG_IP, &ip); ip -= 1; struct frame_range try_range = {0, 0, 0, 0}; if ( isObjCExceptionCatcher(info.lsda, ip, &bases, &try_range) ) { unw_word_t cfa; unw_get_reg(&cursor, UNW_REG_SP, &cfa); try_range.cfa = cfa; return try_range; } } return (struct frame_range){0, 0, 0, 0}; } // This data structure assumes the number of // active alt handlers per frame is small. // for OBJC_DEBUG_ALT_HANDLERS, record the call to objc_addExceptionHandler. #define BACKTRACE_COUNT 46 #define THREADNAME_COUNT 64 struct alt_handler_debug { uintptr_t token; int backtraceSize; void *backtrace[BACKTRACE_COUNT]; char thread[THREADNAME_COUNT]; char queue[THREADNAME_COUNT]; }; struct alt_handler_data { struct frame_range frame; objc_exception_handler fn; void *context; struct alt_handler_debug *debug; }; struct alt_handler_list { unsigned int allocated; unsigned int used; struct alt_handler_data *handlers; struct alt_handler_list *next_DEBUGONLY; }; static struct alt_handler_list *DebugLists; static uintptr_t DebugCounter; __attribute__((noinline, noreturn)) void alt_handler_error(uintptr_t token); static struct alt_handler_list * fetch_handler_list(bool create) { _objc_pthread_data *data = _objc_fetch_pthread_data(create); if (!data) return nil; struct alt_handler_list *list = data->handlerList; if (!list) { if (!create) return nil; list = (struct alt_handler_list *)calloc(1, sizeof(*list)); data->handlerList = list; if (DebugAltHandlers) { // Save this list so the debug code can find it from other threads mutex_locker_t lock(AltHandlerDebugLock); list->next_DEBUGONLY = DebugLists; DebugLists = list; } } return list; } void _destroyAltHandlerList(struct alt_handler_list *list) { if (list) { if (DebugAltHandlers) { // Detach from the list-of-lists. mutex_locker_t lock(AltHandlerDebugLock); struct alt_handler_list **listp = &DebugLists; while (*listp && *listp != list) listp = &(*listp)->next_DEBUGONLY; if (*listp) *listp = (*listp)->next_DEBUGONLY; } if (list->handlers) { for (unsigned int i = 0; i < list->allocated; i++) { if (list->handlers[i].frame.ips) { free(list->handlers[i].frame.ips); } } free(list->handlers); } free(list); } } uintptr_t objc_addExceptionHandler(objc_exception_handler fn, void *context) { // Find the closest enclosing frame with objc catch handlers struct frame_range target_frame = findHandler(); if (!target_frame.ip_start) { // No suitable enclosing handler found. return 0; } // Record this alt handler for the discovered frame. struct alt_handler_list *list = fetch_handler_list(YES); unsigned int i = 0; if (list->used == list->allocated) { list->allocated = list->allocated*2 ?: 4; list->handlers = (struct alt_handler_data *) realloc(list->handlers, list->allocated * sizeof(list->handlers[0])); bzero(&list->handlers[list->used], (list->allocated - list->used) * sizeof(list->handlers[0])); i = list->used; } else { for (i = 0; i < list->allocated; i++) { if (list->handlers[i].frame.ip_start == 0 && list->handlers[i].frame.ip_end == 0 && list->handlers[i].frame.cfa == 0) { break; } } if (i == list->allocated) { _objc_fatal("alt handlers in objc runtime are buggy!"); } } struct alt_handler_data *data = &list->handlers[i]; data->frame = target_frame; data->fn = fn; data->context = context; list->used++; uintptr_t token = i+1; if (DebugAltHandlers) { // Record backtrace in case this handler is misused later. mutex_locker_t lock(AltHandlerDebugLock); token = DebugCounter++; if (token == 0) token = DebugCounter++; if (!data->debug) { data->debug = (struct alt_handler_debug *) calloc(sizeof(*data->debug), 1); } else { bzero(data->debug, sizeof(*data->debug)); } pthread_getname_np(pthread_self(), data->debug->thread, THREADNAME_COUNT); strlcpy(data->debug->queue, dispatch_queue_get_label(dispatch_get_current_queue()), THREADNAME_COUNT); data->debug->backtraceSize = backtrace(data->debug->backtrace, BACKTRACE_COUNT); data->debug->token = token; } if (PrintAltHandlers) { _objc_inform("ALT HANDLERS: installing alt handler #%lu %p(%p) on " "frame [ip=%p..%p sp=%p]", (unsigned long)token, data->fn, data->context, (void *)data->frame.ip_start, (void *)data->frame.ip_end, (void *)data->frame.cfa); if (data->frame.ips) { unsigned int r = 0; while (1) { uintptr_t start = data->frame.ips[r].start; uintptr_t end = data->frame.ips[r].end; r++; if (start == 0 && end == 0) break; _objc_inform("ALT HANDLERS: ip=%p..%p", (void*)start, (void*)end); } } } if (list->used > 1000) { static int warned = 0; if (!warned) { _objc_inform("ALT HANDLERS: *** over 1000 alt handlers installed; " "this is probably a bug"); warned = 1; } } return token; } void objc_removeExceptionHandler(uintptr_t token) { if (!token) { // objc_addExceptionHandler failed return; } struct alt_handler_list *list = fetch_handler_list(NO); if (!list || !list->handlers) { // no alt handlers active alt_handler_error(token); } uintptr_t i = token-1; if (DebugAltHandlers) { // search for the token instead of using token-1 for (i = 0; i < list->allocated; i++) { struct alt_handler_data *data = &list->handlers[i]; if (data->debug && data->debug->token == token) break; } } if (i >= list->allocated) { // token out of range alt_handler_error(token); } struct alt_handler_data *data = &list->handlers[i]; if (data->frame.ip_start == 0 && data->frame.ip_end == 0 && data->frame.cfa == 0) { // token in range, but invalid alt_handler_error(token); } if (PrintAltHandlers) { _objc_inform("ALT HANDLERS: removing alt handler #%lu %p(%p) on " "frame [ip=%p..%p sp=%p]", (unsigned long)token, data->fn, data->context, (void *)data->frame.ip_start, (void *)data->frame.ip_end, (void *)data->frame.cfa); } if (data->debug) free(data->debug); if (data->frame.ips) free(data->frame.ips); bzero(data, sizeof(*data)); list->used--; } BREAKPOINT_FUNCTION( void objc_alt_handler_error(void)); __attribute__((noinline, noreturn)) void alt_handler_error(uintptr_t token) { _objc_inform ("objc_removeExceptionHandler() called with unknown alt handler; " "this is probably a bug in multithreaded AppKit use. " "Set environment variable OBJC_DEBUG_ALT_HANDLERS=YES " "or break in objc_alt_handler_error() to debug."); if (DebugAltHandlers) { AltHandlerDebugLock.lock(); // Search other threads' alt handler lists for this handler. struct alt_handler_list *list; for (list = DebugLists; list; list = list->next_DEBUGONLY) { unsigned h; for (h = 0; h < list->allocated; h++) { struct alt_handler_data *data = &list->handlers[h]; if (data->debug && data->debug->token == token) { // found it int i; // Build a string from the recorded backtrace char *symbolString; char **symbols = backtrace_symbols(data->debug->backtrace, data->debug->backtraceSize); size_t len = 1; for (i = 0; i < data->debug->backtraceSize; i++){ len += 4 + strlen(symbols[i]) + 1; } symbolString = (char *)calloc(len, 1); for (i = 0; i < data->debug->backtraceSize; i++){ strcat(symbolString, " "); strcat(symbolString, symbols[i]); strcat(symbolString, "\n"); } free(symbols); _objc_inform_now_and_on_crash ("The matching objc_addExceptionHandler() was called " "by:\nThread '%s': Dispatch queue: '%s': \n%s", data->debug->thread, data->debug->queue, symbolString); goto done; } } } done: AltHandlerDebugLock.unlock(); } objc_alt_handler_error(); _objc_fatal ("objc_removeExceptionHandler() called with unknown alt handler; " "this is probably a bug in multithreaded AppKit use. "); } // called in order registered, to match 32-bit _NSAddAltHandler2 // fixme reverse registration order matches c++ destructors better static void call_alt_handlers(struct _Unwind_Context *ctx) { uintptr_t ip = _Unwind_GetIP(ctx) - 1; uintptr_t cfa = _Unwind_GetCFA(ctx); unsigned int i; struct alt_handler_list *list = fetch_handler_list(NO); if (!list || list->used == 0) return; for (i = 0; i < list->allocated; i++) { struct alt_handler_data *data = &list->handlers[i]; if (ip >= data->frame.ip_start && ip < data->frame.ip_end && data->frame.cfa == cfa) { if (data->frame.ips) { unsigned int r = 0; bool found; while (1) { uintptr_t start = data->frame.ips[r].start; uintptr_t end = data->frame.ips[r].end; r++; if (start == 0 && end == 0) { found = false; break; } if (ip >= start && ip < end) { found = true; break; } } if (!found) continue; } // Copy and clear before the callback, in case the // callback manipulates the alt handler list. struct alt_handler_data copy = *data; bzero(data, sizeof(*data)); list->used--; if (PrintExceptions || PrintAltHandlers) { _objc_inform("EXCEPTIONS: calling alt handler %p(%p) from " "frame [ip=%p..%p sp=%p]", copy.fn, copy.context, (void *)copy.frame.ip_start, (void *)copy.frame.ip_end, (void *)copy.frame.cfa); } if (copy.fn) (*copy.fn)(nil, copy.context); if (copy.frame.ips) free(copy.frame.ips); } } } // SUPPORT_ALT_HANDLERS #endif /*********************************************************************** * exception_init * Initialize libobjc's exception handling system. * Called by map_images(). **********************************************************************/ void exception_init(void) { old_terminate = std::set_terminate(&_objc_terminate); } // __OBJC2__ #endif // Define this everywhere even if it isn't used, to simplify fork() safety code mutex_t AltHandlerDebugLock; ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-file-old.h ================================================ /* * Copyright (c) 2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_FILE_OLD_H #define _OBJC_FILE_OLD_H #if !__OBJC2__ #include "objc-os.h" struct objc_module; struct old_protocol; struct old_class; __BEGIN_DECLS extern struct objc_module *_getObjcModules(const header_info *hi, size_t *nmodules); extern SEL *_getObjcSelectorRefs(const header_info *hi, size_t *nmess); extern struct old_protocol **_getObjcProtocols(const header_info *hi, size_t *nprotos); extern Class *_getObjcClassRefs(const header_info *hi, size_t *nclasses); extern const char *_getObjcClassNames(const header_info *hi, size_t *size); using UnsignedInitializer = void(*)(void); extern UnsignedInitializer* getLibobjcInitializers(const headerType *mhdr, size_t *count); __END_DECLS #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-file-old.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ // Copyright 1988-1996 NeXT Software, Inc. #if !__OBJC2__ #include "objc-private.h" #include "objc-runtime-old.h" #include "objc-file-old.h" #if TARGET_OS_WIN32 /* Module _getObjcModules(const header_info *hi, size_t *nmodules) { if (nmodules) *nmodules = hi->moduleCount; return hi->modules; } */ SEL * _getObjcSelectorRefs(const header_info *hi, size_t *nmess) { if (nmess) *nmess = hi->selrefCount; return hi->selrefs; } struct old_protocol ** _getObjcProtocols(const header_info *hi, size_t *nprotos) { if (nprotos) *nprotos = hi->protocolCount; return hi->protocols; } Class* _getObjcClassRefs(const header_info *hi, size_t *nclasses) { if (nclasses) *nclasses = hi->clsrefCount; return (Class*)hi->clsrefs; } // __OBJC,__class_names section only emitted by CodeWarrior rdar://4951638 const char * _getObjcClassNames(const header_info *hi, size_t *size) { if (size) *size = 0; return NULL; } #else #define GETSECT(name, type, segname, sectname) \ type *name(const headerType *mhdr, size_t *outCount) \ { \ unsigned long byteCount = 0; \ type *data = (type *) \ getsectiondata(mhdr, segname, sectname, &byteCount); \ *outCount = byteCount / sizeof(type); \ return data; \ } \ type *name(const header_info *hi, size_t *outCount) \ { \ return name(hi->mhdr(), outCount); \ } GETSECT(_getObjcModules, objc_module, "__OBJC", "__module_info"); GETSECT(_getObjcSelectorRefs, SEL, "__OBJC", "__message_refs"); GETSECT(_getObjcClassRefs, Class, "__OBJC", "__cls_refs"); GETSECT(_getObjcClassNames, const char, "__OBJC", "__class_names"); // __OBJC,__class_names section only emitted by CodeWarrior rdar://4951638 GETSECT(getLibobjcInitializers, UnsignedInitializer, "__DATA", "__objc_init_func"); objc_image_info * _getObjcImageInfo(const headerType *mhdr, size_t *outBytes) { unsigned long byteCount = 0; objc_image_info *info = (objc_image_info *) getsectiondata(mhdr, SEG_OBJC, "__image_info", &byteCount); *outBytes = byteCount; return info; } struct old_protocol ** _getObjcProtocols(const header_info *hi, size_t *nprotos) { unsigned long size = 0; struct old_protocol *protos = (struct old_protocol *) getsectiondata(hi->mhdr(), SEG_OBJC, "__protocol", &size); *nprotos = size / sizeof(struct old_protocol); if (!hi->proto_refs && *nprotos) { size_t i; header_info *whi = (header_info *)hi; whi->proto_refs = (struct old_protocol **) malloc(*nprotos * sizeof(*hi->proto_refs)); for (i = 0; i < *nprotos; i++) { hi->proto_refs[i] = protos+i; } } return hi->proto_refs; } static const segmentType * getsegbynamefromheader(const headerType *head, const char *segname) { const segmentType *sgp; unsigned long i; sgp = (const segmentType *) (head + 1); for (i = 0; i < head->ncmds; i++){ if (sgp->cmd == SEGMENT_CMD) { if (strncmp(sgp->segname, segname, sizeof(sgp->segname)) == 0) { return sgp; } } sgp = (const segmentType *)((char *)sgp + sgp->cmdsize); } return NULL; } bool _hasObjcContents(const header_info *hi) { // Look for an __OBJC,* section other than __OBJC,__image_info const segmentType *seg = getsegbynamefromheader(hi->mhdr(), "__OBJC"); const sectionType *sect; uint32_t i; for (i = 0; i < seg->nsects; i++) { sect = ((const sectionType *)(seg+1))+i; if (0 != strncmp(sect->sectname, "__image_info", 12)) { return YES; } } return NO; } #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-file.h ================================================ /* * Copyright (c) 2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_FILE_NEW_H #define _OBJC_FILE_NEW_H #if __OBJC2__ #include "objc-runtime-new.h" // classref_t is not fixed up at launch; use remapClass() to convert extern SEL *_getObjc2SelectorRefs(const header_info *hi, size_t *count); extern message_ref_t *_getObjc2MessageRefs(const header_info *hi, size_t *count); extern Class*_getObjc2ClassRefs(const header_info *hi, size_t *count); extern Class*_getObjc2SuperRefs(const header_info *hi, size_t *count); extern classref_t *_getObjc2ClassList(const header_info *hi, size_t *count); extern classref_t *_getObjc2NonlazyClassList(const header_info *hi, size_t *count); extern category_t **_getObjc2CategoryList(const header_info *hi, size_t *count); extern category_t **_getObjc2NonlazyCategoryList(const header_info *hi, size_t *count); extern protocol_t **_getObjc2ProtocolList(const header_info *hi, size_t *count); extern protocol_t **_getObjc2ProtocolRefs(const header_info *hi, size_t *count); // FIXME: rdar://29241917&33734254 clang doesn't sign static initializers. struct UnsignedInitializer { private: uintptr_t storage; public: void operator () () const { using Initializer = void(*)(); Initializer init = ptrauth_sign_unauthenticated((Initializer)storage, ptrauth_key_function_pointer, 0); init(); } }; extern UnsignedInitializer *getLibobjcInitializers(const header_info *hi, size_t *count); extern classref_t *_getObjc2NonlazyClassList(const headerType *mhdr, size_t *count); extern category_t **_getObjc2NonlazyCategoryList(const headerType *mhdr, size_t *count); extern UnsignedInitializer *getLibobjcInitializers(const headerType *mhdr, size_t *count); static inline void foreach_data_segment(const headerType *mhdr, std::function code) { intptr_t slide = 0; // compute VM slide const segmentType *seg = (const segmentType *) (mhdr + 1); for (unsigned long i = 0; i < mhdr->ncmds; i++) { if (seg->cmd == SEGMENT_CMD && segnameEquals(seg->segname, "__TEXT")) { slide = (char *)mhdr - (char *)seg->vmaddr; break; } seg = (const segmentType *)((char *)seg + seg->cmdsize); } // enumerate __DATA* segments seg = (const segmentType *) (mhdr + 1); for (unsigned long i = 0; i < mhdr->ncmds; i++) { if (seg->cmd == SEGMENT_CMD && segnameStartsWith(seg->segname, "__DATA")) { code(seg, slide); } seg = (const segmentType *)((char *)seg + seg->cmdsize); } } #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-file.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #if __OBJC2__ #include "objc-private.h" #include "objc-file.h" // Look for a __DATA or __DATA_CONST or __DATA_DIRTY section // with the given name that stores an array of T. template T* getDataSection(const headerType *mhdr, const char *sectname, size_t *outBytes, size_t *outCount) { unsigned long byteCount = 0; T* data = (T*)getsectiondata(mhdr, "__DATA", sectname, &byteCount); if (!data) { data = (T*)getsectiondata(mhdr, "__DATA_CONST", sectname, &byteCount); } if (!data) { data = (T*)getsectiondata(mhdr, "__DATA_DIRTY", sectname, &byteCount); } if (outBytes) *outBytes = byteCount; if (outCount) *outCount = byteCount / sizeof(T); return data; } #define GETSECT(name, type, sectname) \ type *name(const headerType *mhdr, size_t *outCount) { \ return getDataSection(mhdr, sectname, nil, outCount); \ } \ type *name(const header_info *hi, size_t *outCount) { \ return getDataSection(hi->mhdr(), sectname, nil, outCount); \ } // function name content type section name GETSECT(_getObjc2SelectorRefs, SEL, "__objc_selrefs"); GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs"); GETSECT(_getObjc2ClassRefs, Class, "__objc_classrefs"); GETSECT(_getObjc2SuperRefs, Class, "__objc_superrefs"); GETSECT(_getObjc2ClassList, classref_t, "__objc_classlist"); GETSECT(_getObjc2NonlazyClassList, classref_t, "__objc_nlclslist"); GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist"); GETSECT(_getObjc2NonlazyCategoryList, category_t *, "__objc_nlcatlist"); GETSECT(_getObjc2ProtocolList, protocol_t *, "__objc_protolist"); GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs"); GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func"); objc_image_info * _getObjcImageInfo(const headerType *mhdr, size_t *outBytes) { return getDataSection(mhdr, "__objc_imageinfo", outBytes, nil); } // Look for an __objc* section other than __objc_imageinfo static bool segmentHasObjcContents(const segmentType *seg) { for (uint32_t i = 0; i < seg->nsects; i++) { const sectionType *sect = ((const sectionType *)(seg+1))+i; if (sectnameStartsWith(sect->sectname, "__objc_") && !sectnameEquals(sect->sectname, "__objc_imageinfo")) { return true; } } return false; } // Look for an __objc* section other than __objc_imageinfo bool _hasObjcContents(const header_info *hi) { bool foundObjC = false; foreach_data_segment(hi->mhdr(), [&](const segmentType *seg, intptr_t slide) { if (segmentHasObjcContents(seg)) foundObjC = true; }); return foundObjC; } // OBJC2 #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-gdb.h ================================================ /* * Copyright (c) 2008 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_GDB_H #define _OBJC_GDB_H /* * WARNING DANGER HAZARD BEWARE EEK * * Everything in this file is for debugger and developer tool use only. * These will change in arbitrary OS updates and in unpredictable ways. * When your program breaks, you get to keep both pieces. */ #ifdef __APPLE_API_PRIVATE #ifndef _OBJC_PRIVATE_H_ # define _OBJC_PRIVATE_H_ #endif #include #include #include __BEGIN_DECLS /*********************************************************************** * Class pointer preflighting **********************************************************************/ // Return cls if it's a valid class, or crash. OBJC_EXPORT Class _Nonnull gdb_class_getClass(Class _Nonnull cls) #if __OBJC2__ OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); #else OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0, 2.0); #endif // Same as gdb_class_getClass(object_getClass(cls)). OBJC_EXPORT Class _Nonnull gdb_object_getClass(id _Nullable obj) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /*********************************************************************** * Class lists for heap. **********************************************************************/ #if __OBJC2__ // Maps class name to Class, for in-use classes only. NXStrValueMapPrototype. OBJC_EXPORT NXMapTable * _Nullable gdb_objc_realized_classes OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); #else // Hashes Classes, for all known classes. Custom prototype. OBJC_EXPORT NXHashTable * _Nullable _objc_debug_class_hash __OSX_AVAILABLE(10.2) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; #endif /*********************************************************************** * Non-pointer isa **********************************************************************/ #if __OBJC2__ // Extract isa pointer from an isa field. // (Class)(isa & mask) == class pointer OBJC_EXPORT const uintptr_t objc_debug_isa_class_mask OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0, 2.0); // Extract magic cookie from an isa field. // (isa & magic_mask) == magic_value OBJC_EXPORT const uintptr_t objc_debug_isa_magic_mask OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT const uintptr_t objc_debug_isa_magic_value OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0, 2.0); // Use indexed ISAs for targets which store index of the class in the ISA. // This index can be used to index the array of classes. OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_magic_mask; OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_magic_value; // Then these are used to extract the index from the ISA. OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_index_mask; OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_index_shift; // And then we can use that index to get the class from this array. Note // the size is provided so that clients can ensure the index they get is in // bounds and not read off the end of the array. OBJC_EXPORT Class _Nullable objc_indexed_classes[]; // When we don't have enough bits to store a class*, we can instead store an // index in to this array. Classes are added here when they are realized. // Note, an index of 0 is illegal. OBJC_EXPORT uintptr_t objc_indexed_classes_count; // Absolute symbols for some of the above values are in objc-abi.h. #endif /*********************************************************************** * Class structure decoding **********************************************************************/ #if __OBJC2__ // Mask for the pointer from class struct to class rw data. // Other bits may be used for flags. // Use 0x00007ffffffffff8UL or 0xfffffffcUL when this variable is unavailable. OBJC_EXPORT const uintptr_t objc_debug_class_rw_data_mask OBJC_AVAILABLE(10.13, 11.0, 11.0, 4.0, 2.0); #endif /*********************************************************************** * Tagged pointer decoding **********************************************************************/ #if __OBJC2__ // Basic tagged pointers (7 classes, 60-bit payload). // if (obj & mask) obj is a tagged pointer object OBJC_EXPORT uintptr_t objc_debug_taggedpointer_mask OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // tagged pointers are obfuscated by XORing with a random value // decoded_obj = (obj ^ obfuscator) OBJC_EXPORT uintptr_t objc_debug_taggedpointer_obfuscator OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); // tag_slot = (obj >> slot_shift) & slot_mask OBJC_EXPORT unsigned int objc_debug_taggedpointer_slot_shift OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT uintptr_t objc_debug_taggedpointer_slot_mask OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // class = classes[tag_slot] OBJC_EXPORT Class _Nullable objc_debug_taggedpointer_classes[] OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // payload = (decoded_obj << payload_lshift) >> payload_rshift // Payload signedness is determined by the signedness of the right-shift. OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_lshift OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_rshift OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // Extended tagged pointers (255 classes, 52-bit payload). // If you interrogate an extended tagged pointer using the basic // tagged pointer scheme alone, it will appear to have an isa // that is either nil or class __NSUnrecognizedTaggedPointer. // if (ext_mask != 0 && (decoded_obj & ext_mask) == ext_mask) // obj is a ext tagged pointer object OBJC_EXPORT uintptr_t objc_debug_taggedpointer_ext_mask OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); // ext_tag_slot = (obj >> ext_slot_shift) & ext_slot_mask OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_slot_shift OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT uintptr_t objc_debug_taggedpointer_ext_slot_mask OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); // class = ext_classes[ext_tag_slot] OBJC_EXPORT Class _Nullable objc_debug_taggedpointer_ext_classes[] OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); // payload = (decoded_obj << ext_payload_lshift) >> ext_payload_rshift // Payload signedness is determined by the signedness of the right-shift. OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_lshift OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_rshift OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); #endif __END_DECLS // APPLE_API_PRIVATE #endif // _OBJC_GDB_H #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-initialize.h ================================================ /* * Copyright (c) 2005-2006 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_INITIALIZE_H #define _OBJC_INITIALIZE_H #include "objc-private.h" __BEGIN_DECLS struct _objc_initializing_classes; extern void _class_initialize(Class cls); extern void _destroyInitializingClassList(struct _objc_initializing_classes *list); extern bool _thisThreadIsInitializingClass(Class cls); __END_DECLS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-initialize.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-initialize.m * +initialize support **********************************************************************/ /*********************************************************************** * Thread-safety during class initialization (GrP 2001-9-24) * * Initial state: CLS_INITIALIZING and CLS_INITIALIZED both clear. * During initialization: CLS_INITIALIZING is set * After initialization: CLS_INITIALIZING clear and CLS_INITIALIZED set. * CLS_INITIALIZING and CLS_INITIALIZED are never set at the same time. * CLS_INITIALIZED is never cleared once set. * * Only one thread is allowed to actually initialize a class and send * +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING. * * Additionally, threads trying to send messages to a class must wait for * +initialize to finish. During initialization of a class, that class's * method cache is kept empty. objc_msgSend will revert to * class_lookupMethodAndLoadCache, which checks CLS_INITIALIZED before * messaging. If CLS_INITIALIZED is clear but CLS_INITIALIZING is set, * the thread must block, unless it is the thread that started * initializing the class in the first place. * * Each thread keeps a list of classes it's initializing. * The global classInitLock is used to synchronize changes to CLS_INITIALIZED * and CLS_INITIALIZING: the transition to CLS_INITIALIZING must be * an atomic test-and-set with respect to itself and the transition * to CLS_INITIALIZED. * The global classInitWaitCond is used to block threads waiting for an * initialization to complete. The classInitLock synchronizes * condition checking and the condition variable. **********************************************************************/ /*********************************************************************** * +initialize deadlock case when a class is marked initializing while * its superclass is initialized. Solved by completely initializing * superclasses before beginning to initialize a class. * * OmniWeb class hierarchy: * OBObject * | ` OBPostLoader * OFObject * / \ * OWAddressEntry OWController * | * OWConsoleController * * Thread 1 (evil testing thread): * initialize OWAddressEntry * super init OFObject * super init OBObject * [OBObject initialize] runs OBPostLoader, which inits lots of classes... * initialize OWConsoleController * super init OWController - wait for Thread 2 to finish OWController init * * Thread 2 (normal OmniWeb thread): * initialize OWController * super init OFObject - wait for Thread 1 to finish OFObject init * * deadlock! * * Solution: fully initialize super classes before beginning to initialize * a subclass. Then the initializing+initialized part of the class hierarchy * will be a contiguous subtree starting at the root, so other threads * can't jump into the middle between two initializing classes, and we won't * get stuck while a superclass waits for its subclass which waits for the * superclass. **********************************************************************/ #include "objc-private.h" #include "message.h" #include "objc-initialize.h" /* classInitLock protects CLS_INITIALIZED and CLS_INITIALIZING, and * is signalled when any class is done initializing. * Threads that are waiting for a class to finish initializing wait on this. */ monitor_t classInitLock; /*********************************************************************** * struct _objc_initializing_classes * Per-thread list of classes currently being initialized by that thread. * During initialization, that thread is allowed to send messages to that * class, but other threads have to wait. * The list is a simple array of metaclasses (the metaclass stores * the initialization state). **********************************************************************/ typedef struct _objc_initializing_classes { int classesAllocated; Class *metaclasses; } _objc_initializing_classes; /*********************************************************************** * _fetchInitializingClassList * Return the list of classes being initialized by this thread. * If create == YES, create the list when no classes are being initialized by this thread. * If create == NO, return nil when no classes are being initialized by this thread. **********************************************************************/ static _objc_initializing_classes *_fetchInitializingClassList(bool create) { _objc_pthread_data *data; _objc_initializing_classes *list; Class *classes; data = _objc_fetch_pthread_data(create); if (data == nil) return nil; list = data->initializingClasses; if (list == nil) { if (!create) { return nil; } else { list = (_objc_initializing_classes *) calloc(1, sizeof(_objc_initializing_classes)); data->initializingClasses = list; } } classes = list->metaclasses; if (classes == nil) { // If _objc_initializing_classes exists, allocate metaclass array, // even if create == NO. // Allow 4 simultaneous class inits on this thread before realloc. list->classesAllocated = 4; classes = (Class *) calloc(list->classesAllocated, sizeof(Class)); list->metaclasses = classes; } return list; } /*********************************************************************** * _destroyInitializingClassList * Deallocate memory used by the given initialization list. * Any part of the list may be nil. * Called from _objc_pthread_destroyspecific(). **********************************************************************/ void _destroyInitializingClassList(struct _objc_initializing_classes *list) { if (list != nil) { if (list->metaclasses != nil) { free(list->metaclasses); } free(list); } } /*********************************************************************** * _thisThreadIsInitializingClass * Return TRUE if this thread is currently initializing the given class. **********************************************************************/ bool _thisThreadIsInitializingClass(Class cls) { int i; _objc_initializing_classes *list = _fetchInitializingClassList(NO); if (list) { cls = cls->getMeta(); for (i = 0; i < list->classesAllocated; i++) { if (cls == list->metaclasses[i]) return YES; } } // no list or not found in list return NO; } /*********************************************************************** * _setThisThreadIsInitializingClass * Record that this thread is currently initializing the given class. * This thread will be allowed to send messages to the class, but * other threads will have to wait. **********************************************************************/ static void _setThisThreadIsInitializingClass(Class cls) { int i; _objc_initializing_classes *list = _fetchInitializingClassList(YES); cls = cls->getMeta(); // paranoia: explicitly disallow duplicates for (i = 0; i < list->classesAllocated; i++) { if (cls == list->metaclasses[i]) { _objc_fatal("thread is already initializing this class!"); return; // already the initializer } } for (i = 0; i < list->classesAllocated; i++) { if (! list->metaclasses[i]) { list->metaclasses[i] = cls; return; } } // class list is full - reallocate list->classesAllocated = list->classesAllocated * 2 + 1; list->metaclasses = (Class *) realloc(list->metaclasses, list->classesAllocated * sizeof(Class)); // zero out the new entries list->metaclasses[i++] = cls; for ( ; i < list->classesAllocated; i++) { list->metaclasses[i] = nil; } } /*********************************************************************** * _setThisThreadIsNotInitializingClass * Record that this thread is no longer initializing the given class. **********************************************************************/ static void _setThisThreadIsNotInitializingClass(Class cls) { int i; _objc_initializing_classes *list = _fetchInitializingClassList(NO); if (list) { cls = cls->getMeta(); for (i = 0; i < list->classesAllocated; i++) { if (cls == list->metaclasses[i]) { list->metaclasses[i] = nil; return; } } } // no list or not found in list _objc_fatal("thread is not initializing this class!"); } typedef struct PendingInitialize { Class subclass; struct PendingInitialize *next; } PendingInitialize; static NXMapTable *pendingInitializeMap; /*********************************************************************** * _finishInitializing * cls has completed its +initialize method, and so has its superclass. * Mark cls as initialized as well, then mark any of cls's subclasses * that have already finished their own +initialize methods. **********************************************************************/ static void _finishInitializing(Class cls, Class supercls) { PendingInitialize *pending; classInitLock.assertLocked(); assert(!supercls || supercls->isInitialized()); if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: %s is fully +initialized", pthread_self(), cls->nameForLogging()); } // mark this class as fully +initialized cls->setInitialized(); classInitLock.notifyAll(); _setThisThreadIsNotInitializingClass(cls); // mark any subclasses that were merely waiting for this class if (!pendingInitializeMap) return; pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls); if (!pending) return; NXMapRemove(pendingInitializeMap, cls); // Destroy the pending table if it's now empty, to save memory. if (NXCountMapTable(pendingInitializeMap) == 0) { NXFreeMapTable(pendingInitializeMap); pendingInitializeMap = nil; } while (pending) { PendingInitialize *next = pending->next; if (pending->subclass) _finishInitializing(pending->subclass, cls); free(pending); pending = next; } } /*********************************************************************** * _finishInitializingAfter * cls has completed its +initialize method, but its superclass has not. * Wait until supercls finishes before marking cls as initialized. **********************************************************************/ static void _finishInitializingAfter(Class cls, Class supercls) { PendingInitialize *pending; classInitLock.assertLocked(); if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: class %s will be marked as fully " "+initialized after superclass +[%s initialize] completes", pthread_self(), cls->nameForLogging(), supercls->nameForLogging()); } if (!pendingInitializeMap) { pendingInitializeMap = NXCreateMapTable(NXPtrValueMapPrototype, 10); // fixme pre-size this table for CF/NSObject +initialize } pending = (PendingInitialize *)malloc(sizeof(*pending)); pending->subclass = cls; pending->next = (PendingInitialize *) NXMapGet(pendingInitializeMap, supercls); NXMapInsert(pendingInitializeMap, supercls, pending); } // Provide helpful messages in stack traces. OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) void waitForInitializeToComplete(Class cls) asm("_WAITING_FOR_ANOTHER_THREAD_TO_FINISH_CALLING_+initialize"); OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) void callInitialize(Class cls) asm("_CALLING_SOME_+initialize_METHOD"); void waitForInitializeToComplete(Class cls) { if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: blocking until +[%s initialize] " "completes", pthread_self(), cls->nameForLogging()); } monitor_locker_t lock(classInitLock); while (!cls->isInitialized()) { classInitLock.wait(); } asm(""); } void callInitialize(Class cls) { ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); asm(""); } /*********************************************************************** * classHasTrivialInitialize * Returns true if the class has no +initialize implementation or * has a +initialize implementation that looks empty. * Any root class +initialize implemetation is assumed to be trivial. **********************************************************************/ static bool classHasTrivialInitialize(Class cls) { if (cls->isRootClass() || cls->isRootMetaclass()) return true; Class rootCls = cls->ISA()->ISA()->superclass; IMP rootImp = lookUpImpOrNil(rootCls->ISA(), SEL_initialize, rootCls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); IMP imp = lookUpImpOrNil(cls->ISA(), SEL_initialize, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); return (imp == nil || imp == (IMP)&objc_noop_imp || imp == rootImp); } /*********************************************************************** * lockAndFinishInitializing * Mark a class as finished initializing and notify waiters, or queue for later. * If the superclass is also done initializing, then update * the info bits and notify waiting threads. * If not, update them later. (This can happen if this +initialize * was itself triggered from inside a superclass +initialize.) **********************************************************************/ static void lockAndFinishInitializing(Class cls, Class supercls) { monitor_locker_t lock(classInitLock); if (!supercls || supercls->isInitialized()) { _finishInitializing(cls, supercls); } else { _finishInitializingAfter(cls, supercls); } } /*********************************************************************** * performForkChildInitialize * +initialize after fork() is problematic. It's possible for the * fork child process to call some +initialize that would deadlock waiting * for another +initialize in the parent process. * We wouldn't know how much progress it made therein, so we can't * act as if +initialize completed nor can we restart +initialize * from scratch. * * Instead we proceed introspectively. If the class has some * +initialize implementation, we halt. If the class has no * +initialize implementation of its own, we continue. Root * class +initialize is assumed to be empty if it exists. * * We apply this rule even if the child's +initialize does not appear * to be blocked by anything. This prevents races wherein the +initialize * deadlock only rarely hits. Instead we disallow it even when we "won" * the race. * * Exception: processes that are single-threaded when fork() is called * have no restrictions on +initialize in the child. Examples: sshd and httpd. * * Classes that wish to implement +initialize and be callable after * fork() must use an atfork() handler to provoke +initialize in fork prepare. **********************************************************************/ // Called before halting when some +initialize // method can't be called after fork(). BREAKPOINT_FUNCTION( void objc_initializeAfterForkError(Class cls) ); void performForkChildInitialize(Class cls, Class supercls) { if (classHasTrivialInitialize(cls)) { if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: skipping trivial +[%s " "initialize] in fork() child process", pthread_self(), cls->nameForLogging()); } lockAndFinishInitializing(cls, supercls); } else { if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: refusing to call +[%s " "initialize] in fork() child process because " "it may have been in progress when fork() was called", pthread_self(), cls->nameForLogging()); } _objc_inform_now_and_on_crash ("+[%s initialize] may have been in progress in another thread " "when fork() was called.", cls->nameForLogging()); objc_initializeAfterForkError(cls); _objc_fatal ("+[%s initialize] may have been in progress in another thread " "when fork() was called. We cannot safely call it or " "ignore it in the fork() child process. Crashing instead. " "Set a breakpoint on objc_initializeAfterForkError to debug.", cls->nameForLogging()); } } /*********************************************************************** * class_initialize. Send the '+initialize' message on demand to any * uninitialized class. Force initialization of superclasses first. **********************************************************************/ void _class_initialize(Class cls) { assert(!cls->isMetaClass()); Class supercls; bool reallyInitialize = NO; // Make sure super is done initializing BEFORE beginning to initialize cls. // See note about deadlock above. supercls = cls->superclass; if (supercls && !supercls->isInitialized()) { _class_initialize(supercls); } // Try to atomically set CLS_INITIALIZING. { monitor_locker_t lock(classInitLock); if (!cls->isInitialized() && !cls->isInitializing()) { cls->setInitializing(); reallyInitialize = YES; } } if (reallyInitialize) { // We successfully set the CLS_INITIALIZING bit. Initialize the class. // Record that we're initializing this class so we can message it. _setThisThreadIsInitializingClass(cls); if (MultithreadedForkChild) { // LOL JK we don't really call +initialize methods after fork(). performForkChildInitialize(cls, supercls); return; } // Send the +initialize message. // Note that +initialize is sent to the superclass (again) if // this class doesn't implement +initialize. 2157218 if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]", pthread_self(), cls->nameForLogging()); } // Exceptions: A +initialize call that throws an exception // is deemed to be a complete and successful +initialize. // // Only __OBJC2__ adds these handlers. !__OBJC2__ has a // bootstrapping problem of this versus CF's call to // objc_exception_set_functions(). #if __OBJC2__ @try #endif { callInitialize(cls); if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]", pthread_self(), cls->nameForLogging()); } } #if __OBJC2__ @catch (...) { if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: +[%s initialize] " "threw an exception", pthread_self(), cls->nameForLogging()); } @throw; } @finally #endif { // Done initializing. lockAndFinishInitializing(cls, supercls); } return; } else if (cls->isInitializing()) { // We couldn't set INITIALIZING because INITIALIZING was already set. // If this thread set it earlier, continue normally. // If some other thread set it, block until initialize is done. // It's ok if INITIALIZING changes to INITIALIZED while we're here, // because we safely check for INITIALIZED inside the lock // before blocking. if (_thisThreadIsInitializingClass(cls)) { return; } else if (!MultithreadedForkChild) { waitForInitializeToComplete(cls); return; } else { // We're on the child side of fork(), facing a class that // was initializing by some other thread when fork() was called. _setThisThreadIsInitializingClass(cls); performForkChildInitialize(cls, supercls); } } else if (cls->isInitialized()) { // Set CLS_INITIALIZING failed because someone else already // initialized the class. Continue normally. // NOTE this check must come AFTER the ISINITIALIZING case. // Otherwise: Another thread is initializing this class. ISINITIALIZED // is false. Skip this clause. Then the other thread finishes // initialization and sets INITIALIZING=no and INITIALIZED=yes. // Skip the ISINITIALIZING clause. Die horribly. return; } else { // We shouldn't be here. _objc_fatal("thread-safe class init in objc runtime is buggy!"); } } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-internal.h ================================================ /* * Copyright (c) 2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_INTERNAL_H #define _OBJC_INTERNAL_H /* * WARNING DANGER HAZARD BEWARE EEK * * Everything in this file is for Apple Internal use only. * These will change in arbitrary OS updates and in unpredictable ways. * When your program breaks, you get to keep both pieces. */ /* * objc-internal.h: Private SPI for use by other system frameworks. */ #include #include #include #include #include #include // Termination reasons in the OS_REASON_OBJC namespace. #define OBJC_EXIT_REASON_UNSPECIFIED 1 #define OBJC_EXIT_REASON_GC_NOT_SUPPORTED 2 // This is the allocation size required for each of the class and the metaclass // with objc_initializeClassPair() and objc_readClassPair(). // The runtime's class structure will never grow beyond this. #define OBJC_MAX_CLASS_SIZE (32*sizeof(void*)) __BEGIN_DECLS // In-place construction of an Objective-C class. // cls and metacls must each be OBJC_MAX_CLASS_SIZE bytes. // Returns nil if a class with the same name already exists. // Returns nil if the superclass is under construction. // Call objc_registerClassPair() when you are done. OBJC_EXPORT Class _Nullable objc_initializeClassPair(Class _Nullable superclass, const char * _Nonnull name, Class _Nonnull cls, Class _Nonnull metacls) OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0); // Class and metaclass construction from a compiler-generated memory image. // cls and cls->isa must each be OBJC_MAX_CLASS_SIZE bytes. // Extra bytes not used the the metadata must be zero. // info is the same objc_image_info that would be emitted by a static compiler. // Returns nil if a class with the same name already exists. // Returns nil if the superclass is nil and the class is not marked as a root. // Returns nil if the superclass is under construction. // Do not call objc_registerClassPair(). #if __OBJC2__ struct objc_image_info; OBJC_EXPORT Class _Nullable objc_readClassPair(Class _Nonnull cls, const struct objc_image_info * _Nonnull info) OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0, 2.0); #endif // Batch object allocation using malloc_zone_batch_malloc(). OBJC_EXPORT unsigned class_createInstances(Class _Nullable cls, size_t extraBytes, id _Nonnull * _Nonnull results, unsigned num_requested) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; // Get the isa pointer written into objects just before being freed. OBJC_EXPORT Class _Nonnull _objc_getFreedObjectClass(void) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); // env NSObjCMessageLoggingEnabled OBJC_EXPORT void instrumentObjcMessageSends(BOOL flag) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); // Initializer called by libSystem OBJC_EXPORT void _objc_init(void) #if __OBJC2__ OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); #else OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); #endif // fork() safety called by libSystem OBJC_EXPORT void _objc_atfork_prepare(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT void _objc_atfork_parent(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT void _objc_atfork_child(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); // Return YES if GC is on and `object` is a GC allocation. OBJC_EXPORT BOOL objc_isAuto(id _Nullable object) __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // GC debugging OBJC_EXPORT BOOL objc_dumpHeap(char * _Nonnull filename, unsigned long length) __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // GC startup callback from Foundation OBJC_EXPORT malloc_zone_t * _Nullable objc_collect_init(int (* _Nonnull callback)(void)) __OSX_DEPRECATED(10.4, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // Plainly-implemented GC barriers. Rosetta used to use these. OBJC_EXPORT id _Nullable objc_assign_strongCast_generic(id _Nullable value, id _Nullable * _Nonnull dest) UNAVAILABLE_ATTRIBUTE; OBJC_EXPORT id _Nullable objc_assign_global_generic(id _Nullable value, id _Nullable * _Nonnull dest) UNAVAILABLE_ATTRIBUTE; OBJC_EXPORT id _Nullable objc_assign_threadlocal_generic(id _Nullable value, id _Nullable * _Nonnull dest) UNAVAILABLE_ATTRIBUTE; OBJC_EXPORT id _Nullable objc_assign_ivar_generic(id _Nullable value, id _Nonnull dest, ptrdiff_t offset) UNAVAILABLE_ATTRIBUTE; // GC preflight for an app executable. // 1: some slice requires GC // 0: no slice requires GC // -1: I/O or file format error OBJC_EXPORT int objc_appRequiresGC(int fd) __OSX_AVAILABLE(10.11) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // Install missing-class callback. Used by the late unlamented ZeroLink. OBJC_EXPORT void _objc_setClassLoader(BOOL (* _Nonnull newClassLoader)(const char * _Nonnull)) OBJC2_UNAVAILABLE; #if !(TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__) OBJC_EXPORT void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) (Class _Nonnull oldClass, Class _Nonnull newClass)); // fixme work around bug in Swift // OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) #endif // Install handler for allocation failures. // Handler may abort, or throw, or provide an object to return. OBJC_EXPORT void _objc_setBadAllocHandler(id _Nullable (* _Nonnull newHandler) (Class _Nullable isa)) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); // Used by ExceptionHandling.framework #if !__OBJC2__ OBJC_EXPORT void _objc_error(id _Nullable rcv, const char * _Nonnull fmt, va_list args) __attribute__((noreturn)) __OSX_DEPRECATED(10.0, 10.5, "use other logging facilities instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; #endif /** * Returns the names of all the classes within a library. * * @param image The mach header for library or framework you are inquiring about. * @param outCount The number of class names returned. * * @return An array of C strings representing the class names. */ OBJC_EXPORT const char * _Nonnull * _Nullable objc_copyClassNamesForImageHeader(const struct mach_header * _Nonnull mh, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); // Tagged pointer objects. #if __LP64__ #define OBJC_HAVE_TAGGED_POINTERS 1 #endif #if OBJC_HAVE_TAGGED_POINTERS // Tagged pointer layout and usage is subject to change on different OS versions. // Tag indexes 0..<7 have a 60-bit payload. // Tag index 7 is reserved. // Tag indexes 8..<264 have a 52-bit payload. // Tag index 264 is reserved. #if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L enum objc_tag_index_t : uint16_t #else typedef uint16_t objc_tag_index_t; enum #endif { // 60-bit payloads OBJC_TAG_NSAtom = 0, OBJC_TAG_1 = 1, OBJC_TAG_NSString = 2, OBJC_TAG_NSNumber = 3, OBJC_TAG_NSIndexPath = 4, OBJC_TAG_NSManagedObjectID = 5, OBJC_TAG_NSDate = 6, // 60-bit reserved OBJC_TAG_RESERVED_7 = 7, // 52-bit payloads OBJC_TAG_Photos_1 = 8, OBJC_TAG_Photos_2 = 9, OBJC_TAG_Photos_3 = 10, OBJC_TAG_Photos_4 = 11, OBJC_TAG_XPC_1 = 12, OBJC_TAG_XPC_2 = 13, OBJC_TAG_XPC_3 = 14, OBJC_TAG_XPC_4 = 15, OBJC_TAG_First60BitPayload = 0, OBJC_TAG_Last60BitPayload = 6, OBJC_TAG_First52BitPayload = 8, OBJC_TAG_Last52BitPayload = 263, OBJC_TAG_RESERVED_264 = 264 }; #if __has_feature(objc_fixed_enum) && !defined(__cplusplus) typedef enum objc_tag_index_t objc_tag_index_t; #endif // Returns true if tagged pointers are enabled. // The other functions below must not be called if tagged pointers are disabled. static inline bool _objc_taggedPointersEnabled(void); // Register a class for a tagged pointer tag. // Aborts if the tag is invalid or already in use. OBJC_EXPORT void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class _Nonnull cls) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // Returns the registered class for the given tag. // Returns nil if the tag is valid but has no registered class. // Aborts if the tag is invalid. OBJC_EXPORT Class _Nullable _objc_getClassForTag(objc_tag_index_t tag) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // Create a tagged pointer object with the given tag and payload. // Assumes the tag is valid. // Assumes tagged pointers are enabled. // The payload will be silently truncated to fit. static inline void * _Nonnull _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t payload); // Return true if ptr is a tagged pointer object. // Does not check the validity of ptr's class. static inline bool _objc_isTaggedPointer(const void * _Nullable ptr); // Extract the tag value from the given tagged pointer object. // Assumes ptr is a valid tagged pointer object. // Does not check the validity of ptr's tag. static inline objc_tag_index_t _objc_getTaggedPointerTag(const void * _Nullable ptr); // Extract the payload from the given tagged pointer object. // Assumes ptr is a valid tagged pointer object. // The payload value is zero-extended. static inline uintptr_t _objc_getTaggedPointerValue(const void * _Nullable ptr); // Extract the payload from the given tagged pointer object. // Assumes ptr is a valid tagged pointer object. // The payload value is sign-extended. static inline intptr_t _objc_getTaggedPointerSignedValue(const void * _Nullable ptr); // Don't use the values below. Use the declarations above. #if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__ // 64-bit Mac - tag bit is LSB # define OBJC_MSB_TAGGED_POINTERS 0 #else // Everything else - tag bit is MSB # define OBJC_MSB_TAGGED_POINTERS 1 #endif #define _OBJC_TAG_INDEX_MASK 0x7 // array slot includes the tag bit itself #define _OBJC_TAG_SLOT_COUNT 16 #define _OBJC_TAG_SLOT_MASK 0xf #define _OBJC_TAG_EXT_INDEX_MASK 0xff // array slot has no extra bits #define _OBJC_TAG_EXT_SLOT_COUNT 256 #define _OBJC_TAG_EXT_SLOT_MASK 0xff #if OBJC_MSB_TAGGED_POINTERS # define _OBJC_TAG_MASK (1UL<<63) # define _OBJC_TAG_INDEX_SHIFT 60 # define _OBJC_TAG_SLOT_SHIFT 60 # define _OBJC_TAG_PAYLOAD_LSHIFT 4 # define _OBJC_TAG_PAYLOAD_RSHIFT 4 # define _OBJC_TAG_EXT_MASK (0xfUL<<60) # define _OBJC_TAG_EXT_INDEX_SHIFT 52 # define _OBJC_TAG_EXT_SLOT_SHIFT 52 # define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12 # define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12 #else # define _OBJC_TAG_MASK 1UL # define _OBJC_TAG_INDEX_SHIFT 1 # define _OBJC_TAG_SLOT_SHIFT 0 # define _OBJC_TAG_PAYLOAD_LSHIFT 0 # define _OBJC_TAG_PAYLOAD_RSHIFT 4 # define _OBJC_TAG_EXT_MASK 0xfUL # define _OBJC_TAG_EXT_INDEX_SHIFT 4 # define _OBJC_TAG_EXT_SLOT_SHIFT 4 # define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0 # define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12 #endif extern uintptr_t objc_debug_taggedpointer_obfuscator; static inline void * _Nonnull _objc_encodeTaggedPointer(uintptr_t ptr) { return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr); } static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr) { return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; } static inline bool _objc_taggedPointersEnabled(void) { extern uintptr_t objc_debug_taggedpointer_mask; return (objc_debug_taggedpointer_mask != 0); } static inline void * _Nonnull _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) { // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts. // They are reversed here for payload insertion. // assert(_objc_taggedPointersEnabled()); if (tag <= OBJC_TAG_Last60BitPayload) { // assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value); uintptr_t result = (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)); return _objc_encodeTaggedPointer(result); } else { // assert(tag >= OBJC_TAG_First52BitPayload); // assert(tag <= OBJC_TAG_Last52BitPayload); // assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value); uintptr_t result = (_OBJC_TAG_EXT_MASK | ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) | ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT)); return _objc_encodeTaggedPointer(result); } } static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) { return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; } static inline objc_tag_index_t _objc_getTaggedPointerTag(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); uintptr_t value = _objc_decodeTaggedPointer(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; uintptr_t extTag = (value >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload); } else { return (objc_tag_index_t)basicTag; } } static inline uintptr_t _objc_getTaggedPointerValue(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); uintptr_t value = _objc_decodeTaggedPointer(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; } else { return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; } } static inline intptr_t _objc_getTaggedPointerSignedValue(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); uintptr_t value = _objc_decodeTaggedPointer(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { return ((intptr_t)value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; } else { return ((intptr_t)value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; } } // OBJC_HAVE_TAGGED_POINTERS #endif /** * Returns the method implementation of an object. * * @param obj An Objective-C object. * @param name An Objective-C selector. * * @return The IMP corresponding to the instance method implemented by * the class of \e obj. * * @note Equivalent to: * * class_getMethodImplementation(object_getClass(obj), name); */ OBJC_EXPORT IMP _Nonnull object_getMethodImplementation(id _Nullable obj, SEL _Nonnull name) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT IMP _Nonnull object_getMethodImplementation_stret(id _Nullable obj, SEL _Nonnull name) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; /** * Adds multiple methods to a class in bulk. This amortizes overhead that can be * expensive when adding methods one by one with class_addMethod. * * @param cls The class to which to add the methods. * @param names An array of selectors for the methods to add. * @param imps An array of functions which implement the new methods. * @param types An array of strings that describe the types of each method's * arguments. * @param count The number of items in the names, imps, and types arrays. * @param outFiledCount Upon return, contains the number of failed selectors in * the returned array. * * @return A NULL-terminated C array of selectors which could not be added. A * method cannot be added when a method of that name already exists on that * class. When no failures occur, the return value is \c NULL. When a non-NULL * value is returned, the caller must free the array with \c free(). * */ #if __OBJC2__ OBJC_EXPORT _Nullable SEL * _Nullable class_addMethodsBulk(_Nullable Class cls, _Nonnull const SEL * _Nonnull names, _Nonnull const IMP * _Nonnull imps, const char * _Nonnull * _Nonnull types, uint32_t count, uint32_t * _Nullable outFailedCount) OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); #endif /** * Replaces multiple methods in a class in bulk. This amortizes overhead that * can be expensive when adding methods one by one with class_replaceMethod. * * @param cls The class to modify. * @param names An array of selectors for the methods to replace. * @param imps An array of functions will be the new method implementantations. * @param types An array of strings that describe the types of each method's * arguments. * @param count The number of items in the names, imps, and types arrays. */ #if __OBJC2__ OBJC_EXPORT void class_replaceMethodsBulk(_Nullable Class cls, _Nonnull const SEL * _Nonnull names, _Nonnull const IMP * _Nonnull imps, const char * _Nonnull * _Nonnull types, uint32_t count) OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); #endif // Instance-specific instance variable layout. This is no longer implemented. OBJC_EXPORT void _class_setIvarLayoutAccessor(Class _Nullable cls, const uint8_t* _Nullable (* _Nonnull accessor) (id _Nullable object)) UNAVAILABLE_ATTRIBUTE; OBJC_EXPORT const uint8_t * _Nullable _object_getIvarLayout(Class _Nullable cls, id _Nullable object) UNAVAILABLE_ATTRIBUTE; /* "Unknown" includes non-object ivars and non-ARC non-__weak ivars "Strong" includes ARC __strong ivars "Weak" includes ARC and new MRC __weak ivars "Unretained" includes ARC __unsafe_unretained and old GC+MRC __weak ivars */ typedef enum { objc_ivar_memoryUnknown, // unknown / unknown objc_ivar_memoryStrong, // direct access / objc_storeStrong objc_ivar_memoryWeak, // objc_loadWeak[Retained] / objc_storeWeak objc_ivar_memoryUnretained // direct access / direct access } objc_ivar_memory_management_t; OBJC_EXPORT objc_ivar_memory_management_t _class_getIvarMemoryManagement(Class _Nullable cls, Ivar _Nonnull ivar) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT BOOL _class_isFutureClass(Class _Nullable cls) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // API to only be called by root classes like NSObject or NSProxy OBJC_EXPORT id _Nonnull _objc_rootRetain(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void _objc_rootRelease(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT bool _objc_rootReleaseWasZero(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT bool _objc_rootTryRetain(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT bool _objc_rootIsDeallocating(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nonnull _objc_rootAutorelease(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT uintptr_t _objc_rootRetainCount(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nonnull _objc_rootInit(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable _objc_rootAllocWithZone(Class _Nonnull cls, malloc_zone_t * _Nullable zone) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable _objc_rootAlloc(Class _Nonnull cls) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void _objc_rootDealloc(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void _objc_rootFinalize(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT malloc_zone_t * _Nonnull _objc_rootZone(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT uintptr_t _objc_rootHash(id _Nonnull obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void * _Nonnull objc_autoreleasePoolPush(void) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_autoreleasePoolPop(void * _Nonnull context) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable objc_alloc(Class _Nullable cls) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable objc_allocWithZone(Class _Nullable cls) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable objc_retain(id _Nullable obj) __asm__("_objc_retain") OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_release(id _Nullable obj) __asm__("_objc_release") OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable objc_autorelease(id _Nullable obj) __asm__("_objc_autorelease") OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Prepare a value at +1 for return through a +0 autoreleasing convention. OBJC_EXPORT id _Nullable objc_autoreleaseReturnValue(id _Nullable obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Prepare a value at +0 for return through a +0 autoreleasing convention. OBJC_EXPORT id _Nullable objc_retainAutoreleaseReturnValue(id _Nullable obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Accept a value returned through a +0 autoreleasing convention for use at +1. OBJC_EXPORT id _Nullable objc_retainAutoreleasedReturnValue(id _Nullable obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Accept a value returned through a +0 autoreleasing convention for use at +0. OBJC_EXPORT id _Nullable objc_unsafeClaimAutoreleasedReturnValue(id _Nullable obj) OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_storeStrong(id _Nullable * _Nonnull location, id _Nullable obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable objc_retainAutorelease(id _Nullable obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // obsolete. OBJC_EXPORT id _Nullable objc_retain_autorelease(id _Nullable obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable objc_loadWeakRetained(id _Nullable * _Nonnull location) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT id _Nullable objc_initWeak(id _Nullable * _Nonnull location, id _Nullable val) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Like objc_storeWeak, but stores nil if the new object is deallocating // or the new object's class does not support weak references. // Returns the value stored (either the new object or nil). OBJC_EXPORT id _Nullable objc_storeWeakOrNil(id _Nullable * _Nonnull location, id _Nullable obj) OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0, 2.0); // Like objc_initWeak, but stores nil if the new object is deallocating // or the new object's class does not support weak references. // Returns the value stored (either the new object or nil). OBJC_EXPORT id _Nullable objc_initWeakOrNil(id _Nullable * _Nonnull location, id _Nullable val) OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_destroyWeak(id _Nullable * _Nonnull location) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_copyWeak(id _Nullable * _Nonnull to, id _Nullable * _Nonnull from) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_moveWeak(id _Nullable * _Nonnull to, id _Nullable * _Nonnull from) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void _objc_autoreleasePoolPrint(void) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT BOOL objc_should_deallocate(id _Nonnull object) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void objc_clear_deallocating(id _Nonnull object) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // to make CF link for now OBJC_EXPORT void * _Nonnull _objc_autoreleasePoolPush(void) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void _objc_autoreleasePoolPop(void * _Nonnull context) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Extra @encode data for XPC, or NULL OBJC_EXPORT const char * _Nullable _protocol_getMethodTypeEncoding(Protocol * _Nonnull proto, SEL _Nonnull sel, BOOL isRequiredMethod, BOOL isInstanceMethod) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); // API to only be called by classes that provide their own reference count storage OBJC_EXPORT void _objc_deallocOnMainThreadHelper(void * _Nullable context) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // On async versus sync deallocation and the _dealloc2main flag // // Theory: // // If order matters, then code must always: [self dealloc]. // If order doesn't matter, then always async should be safe. // // Practice: // // The _dealloc2main bit is set for GUI objects that may be retained by other // threads. Once deallocation begins on the main thread, doing more async // deallocation will at best cause extra UI latency and at worst cause // use-after-free bugs in unretained delegate style patterns. Yes, this is // extremely fragile. Yes, in the long run, developers should switch to weak // references. // // Note is NOT safe to do any equality check against the result of // dispatch_get_current_queue(). The main thread can and does drain more than // one dispatch queue. That is why we call pthread_main_np(). // typedef enum { _OBJC_RESURRECT_OBJECT = -1, /* _logicBlock has called -retain, and scheduled a -release for later. */ _OBJC_DEALLOC_OBJECT_NOW = 1, /* call [self dealloc] immediately. */ _OBJC_DEALLOC_OBJECT_LATER = 2 /* call [self dealloc] on the main queue. */ } _objc_object_disposition_t; #define _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC_BLOCK(_rc_ivar, _logicBlock) \ -(id)retain { \ /* this will fail to compile if _rc_ivar is an unsigned type */ \ int _retain_count_ivar_must_not_be_unsigned[0L - (__typeof__(_rc_ivar))-1] __attribute__((unused)); \ __typeof__(_rc_ivar) _prev = __sync_fetch_and_add(&_rc_ivar, 2); \ if (_prev < -2) { /* specifically allow resurrection from logical 0. */ \ __builtin_trap(); /* BUG: retain of over-released ref */ \ } \ return self; \ } \ -(oneway void)release { \ __typeof__(_rc_ivar) _prev = __sync_fetch_and_sub(&_rc_ivar, 2); \ if (_prev > 0) { \ return; \ } else if (_prev < 0) { \ __builtin_trap(); /* BUG: over-release */ \ } \ _objc_object_disposition_t fate = _logicBlock(self); \ if (fate == _OBJC_RESURRECT_OBJECT) { \ return; \ } \ /* mark the object as deallocating. */ \ if (!__sync_bool_compare_and_swap(&_rc_ivar, -2, 1)) { \ __builtin_trap(); /* BUG: dangling ref did a retain */ \ } \ if (fate == _OBJC_DEALLOC_OBJECT_NOW) { \ [self dealloc]; \ } else if (fate == _OBJC_DEALLOC_OBJECT_LATER) { \ dispatch_barrier_async_f(dispatch_get_main_queue(), self, \ _objc_deallocOnMainThreadHelper); \ } else { \ __builtin_trap(); /* BUG: bogus fate value */ \ } \ } \ -(NSUInteger)retainCount { \ return (_rc_ivar + 2) >> 1; \ } \ -(BOOL)_tryRetain { \ __typeof__(_rc_ivar) _prev; \ do { \ _prev = _rc_ivar; \ if (_prev & 1) { \ return 0; \ } else if (_prev == -2) { \ return 0; \ } else if (_prev < -2) { \ __builtin_trap(); /* BUG: over-release elsewhere */ \ } \ } while ( ! __sync_bool_compare_and_swap(&_rc_ivar, _prev, _prev + 2)); \ return 1; \ } \ -(BOOL)_isDeallocating { \ if (_rc_ivar == -2) { \ return 1; \ } else if (_rc_ivar < -2) { \ __builtin_trap(); /* BUG: over-release elsewhere */ \ } \ return _rc_ivar & 1; \ } #define _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, _dealloc2main) \ _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC_BLOCK(_rc_ivar, (^(id _self_ __attribute__((unused))) { \ if (_dealloc2main && !pthread_main_np()) { \ return _OBJC_DEALLOC_OBJECT_LATER; \ } else { \ return _OBJC_DEALLOC_OBJECT_NOW; \ } \ })) #define _OBJC_SUPPORTED_INLINE_REFCNT(_rc_ivar) _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, 0) #define _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_rc_ivar) _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, 1) __END_DECLS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-layout.mm ================================================ /* * Copyright (c) 2004-2008 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include "objc-private.h" /********************************************************************** * Object Layouts. * * Layouts are used by the garbage collector to identify references from * the object to other objects. * * Layout information is in the form of a '\0' terminated byte string. * Each byte contains a word skip count in the high nibble and a * consecutive references count in the low nibble. Counts that exceed 15 are * continued in the succeeding byte with a zero in the opposite nibble. * Objects that should be scanned conservatively will have a NULL layout. * Objects that have no references have a empty byte string. * * Example; * * For a class with pointers at offsets 4,12, 16, 32-128 * the layout is { 0x11, 0x12, 0x3f, 0x0a, 0x00 } or * skip 1 - 1 reference (4) * skip 1 - 2 references (12, 16) * skip 3 - 15 references (32-88) * no skip - 10 references (92-128) * end * **********************************************************************/ /********************************************************************** * compress_layout * Allocates and returns a compressed string matching the given layout bitmap. **********************************************************************/ static unsigned char * compress_layout(const uint8_t *bits, size_t bitmap_bits, bool weak) { bool all_set = YES; bool none_set = YES; unsigned char *result; // overallocate a lot; reallocate at correct size later unsigned char * const layout = (unsigned char *) calloc(bitmap_bits + 1, 1); unsigned char *l = layout; size_t i = 0; while (i < bitmap_bits) { size_t skip = 0; size_t scan = 0; // Count one range each of skip and scan. while (i < bitmap_bits) { uint8_t bit = (uint8_t)((bits[i/8] >> (i % 8)) & 1); if (bit) break; i++; skip++; } while (i < bitmap_bits) { uint8_t bit = (uint8_t)((bits[i/8] >> (i % 8)) & 1); if (!bit) break; i++; scan++; none_set = NO; } // Record skip and scan if (skip) all_set = NO; if (scan) none_set = NO; while (skip > 0xf) { *l++ = 0xf0; skip -= 0xf; } if (skip || scan) { *l = (uint8_t)(skip << 4); // NOT incremented - merges with scan while (scan > 0xf) { *l++ |= 0x0f; // May merge with short skip; must calloc scan -= 0xf; } *l++ |= scan; // NOT checked for zero - always increments // May merge with short skip; must calloc } } // insert terminating byte *l++ = '\0'; // return result if (none_set && weak) { result = NULL; // NULL weak layout means none-weak } else if (all_set && !weak) { result = NULL; // NULL ivar layout means all-scanned } else { result = (unsigned char *)strdup((char *)layout); } free(layout); return result; } static void set_bits(layout_bitmap bits, size_t which, size_t count) { // fixme optimize for byte/word at a time size_t bit; for (bit = which; bit < which + count && bit < bits.bitCount; bit++) { bits.bits[bit/8] |= 1 << (bit % 8); } if (bit == bits.bitCount && bit < which + count) { // couldn't fit full type in bitmap _objc_fatal("layout bitmap too short"); } } static void clear_bits(layout_bitmap bits, size_t which, size_t count) { // fixme optimize for byte/word at a time size_t bit; for (bit = which; bit < which + count && bit < bits.bitCount; bit++) { bits.bits[bit/8] &= ~(1 << (bit % 8)); } if (bit == bits.bitCount && bit < which + count) { // couldn't fit full type in bitmap _objc_fatal("layout bitmap too short"); } } static void move_bits(layout_bitmap bits, size_t src, size_t dst, size_t count) { // fixme optimize for byte/word at a time if (dst == src) { return; } else if (dst > src) { // Copy backwards in case of overlap size_t pos = count; while (pos--) { size_t srcbit = src + pos; size_t dstbit = dst + pos; if (bits.bits[srcbit/8] & (1 << (srcbit % 8))) { bits.bits[dstbit/8] |= 1 << (dstbit % 8); } else { bits.bits[dstbit/8] &= ~(1 << (dstbit % 8)); } } } else { // Copy forwards in case of overlap size_t pos; for (pos = 0; pos < count; pos++) { size_t srcbit = src + pos; size_t dstbit = dst + pos; if (bits.bits[srcbit/8] & (1 << (srcbit % 8))) { bits.bits[dstbit/8] |= 1 << (dstbit % 8); } else { bits.bits[dstbit/8] &= ~(1 << (dstbit % 8)); } } } } // emacs autoindent hack - it doesn't like the loop in set_bits/clear_bits #if 0 } } #endif static void decompress_layout(const unsigned char *layout_string, layout_bitmap bits) { unsigned char c; size_t bit = 0; while ((c = *layout_string++)) { unsigned char skip = (c & 0xf0) >> 4; unsigned char scan = (c & 0x0f); bit += skip; set_bits(bits, bit, scan); bit += scan; } } /*********************************************************************** * layout_bitmap_create * Allocate a layout bitmap. * The new bitmap spans the given instance size bytes. * The start of the bitmap is filled from the given layout string (which * spans an instance size of layoutStringSize); the rest is zero-filled. * The returned bitmap must be freed with layout_bitmap_free(). **********************************************************************/ layout_bitmap layout_bitmap_create(const unsigned char *layout_string, size_t layoutStringInstanceSize, size_t instanceSize, bool weak) { layout_bitmap result; size_t words = instanceSize / sizeof(id); result.weak = weak; result.bitCount = words; result.bitsAllocated = words; result.bits = (uint8_t *)calloc((words+7)/8, 1); if (!layout_string) { if (!weak) { // NULL ivar layout means all-scanned // (but only up to layoutStringSize instance size) set_bits(result, 0, layoutStringInstanceSize/sizeof(id)); } else { // NULL weak layout means none-weak. } } else { decompress_layout(layout_string, result); } return result; } /*********************************************************************** * layout_bitmap_create_empty * Allocate a layout bitmap. * The new bitmap spans the given instance size bytes. * The bitmap is empty, to represent an object whose ivars are completely unscanned. * The returned bitmap must be freed with layout_bitmap_free(). **********************************************************************/ layout_bitmap layout_bitmap_create_empty(size_t instanceSize, bool weak) { layout_bitmap result; size_t words = instanceSize / sizeof(id); result.weak = weak; result.bitCount = words; result.bitsAllocated = words; result.bits = (uint8_t *)calloc((words+7)/8, 1); return result; } void layout_bitmap_free(layout_bitmap bits) { if (bits.bits) free(bits.bits); } const unsigned char * layout_string_create(layout_bitmap bits) { const unsigned char *result = compress_layout(bits.bits, bits.bitCount, bits.weak); #if DEBUG // paranoia: cycle to bitmap and back to string again, and compare layout_bitmap check = layout_bitmap_create(result, bits.bitCount*sizeof(id), bits.bitCount*sizeof(id), bits.weak); unsigned char *result2 = compress_layout(check.bits, check.bitCount, check.weak); if (result != result2 && 0 != strcmp((char*)result, (char *)result2)) { layout_bitmap_print(bits); layout_bitmap_print(check); _objc_fatal("libobjc bug: mishandled layout bitmap"); } free(result2); layout_bitmap_free(check); #endif return result; } void layout_bitmap_set_ivar(layout_bitmap bits, const char *type, size_t offset) { // fixme only handles some types size_t bit = offset / sizeof(id); if (!type) return; if (type[0] == '@' || 0 == strcmp(type, "^@")) { // id // id * // Block ("@?") set_bits(bits, bit, 1); } else if (type[0] == '[') { // id[] char *t; unsigned long count = strtoul(type+1, &t, 10); if (t && t[0] == '@') { set_bits(bits, bit, count); } } else if (strchr(type, '@')) { _objc_inform("warning: failing to set GC layout for '%s'\n", type); } } /*********************************************************************** * layout_bitmap_grow * Expand a layout bitmap to span newCount bits. * The new bits are undefined. **********************************************************************/ void layout_bitmap_grow(layout_bitmap *bits, size_t newCount) { if (bits->bitCount >= newCount) return; bits->bitCount = newCount; if (bits->bitsAllocated < newCount) { size_t newAllocated = bits->bitsAllocated * 2; if (newAllocated < newCount) newAllocated = newCount; bits->bits = (uint8_t *) realloc(bits->bits, (newAllocated+7) / 8); bits->bitsAllocated = newAllocated; } assert(bits->bitsAllocated >= bits->bitCount); assert(bits->bitsAllocated >= newCount); } /*********************************************************************** * layout_bitmap_slide * Slide the end of a layout bitmap farther from the start. * Slides bits [oldPos, bits.bitCount) to [newPos, bits.bitCount+newPos-oldPos) * Bits [oldPos, newPos) are zero-filled. * The bitmap is expanded and bitCount updated if necessary. * newPos >= oldPos. **********************************************************************/ void layout_bitmap_slide(layout_bitmap *bits, size_t oldPos, size_t newPos) { size_t shift; size_t count; if (oldPos == newPos) return; if (oldPos > newPos) _objc_fatal("layout bitmap sliding backwards"); shift = newPos - oldPos; count = bits->bitCount - oldPos; layout_bitmap_grow(bits, bits->bitCount + shift); move_bits(*bits, oldPos, newPos, count); // slide clear_bits(*bits, oldPos, shift); // zero-fill } /*********************************************************************** * layout_bitmap_slide_anywhere * Slide the end of a layout bitmap relative to the start. * Like layout_bitmap_slide, but can slide backwards too. * The end of the bitmap is truncated. **********************************************************************/ void layout_bitmap_slide_anywhere(layout_bitmap *bits, size_t oldPos, size_t newPos) { size_t shift; size_t count; if (oldPos == newPos) return; if (oldPos < newPos) { layout_bitmap_slide(bits, oldPos, newPos); return; } shift = oldPos - newPos; count = bits->bitCount - oldPos; move_bits(*bits, oldPos, newPos, count); // slide bits->bitCount -= shift; } /*********************************************************************** * layout_bitmap_splat * Pastes the contents of bitmap src to the start of bitmap dst. * dst bits between the end of src and oldSrcInstanceSize are zeroed. * dst must be at least as long as src. * Returns YES if any of dst's bits were changed. **********************************************************************/ bool layout_bitmap_splat(layout_bitmap dst, layout_bitmap src, size_t oldSrcInstanceSize) { bool changed; size_t oldSrcBitCount; size_t bit; if (dst.bitCount < src.bitCount) _objc_fatal("layout bitmap too short"); changed = NO; oldSrcBitCount = oldSrcInstanceSize / sizeof(id); // fixme optimize for byte/word at a time for (bit = 0; bit < oldSrcBitCount; bit++) { int dstset = dst.bits[bit/8] & (1 << (bit % 8)); int srcset = (bit < src.bitCount) ? src.bits[bit/8] & (1 << (bit % 8)) : 0; if (dstset != srcset) { changed = YES; if (srcset) { dst.bits[bit/8] |= 1 << (bit % 8); } else { dst.bits[bit/8] &= ~(1 << (bit % 8)); } } } return changed; } /*********************************************************************** * layout_bitmap_or * Set dst=dst|src. * dst must be at least as long as src. * Returns YES if any of dst's bits were changed. **********************************************************************/ bool layout_bitmap_or(layout_bitmap dst, layout_bitmap src, const char *msg) { bool changed = NO; size_t bit; if (dst.bitCount < src.bitCount) { _objc_fatal("layout_bitmap_or: layout bitmap too short%s%s", msg ? ": " : "", msg ? msg : ""); } // fixme optimize for byte/word at a time for (bit = 0; bit < src.bitCount; bit++) { int dstset = dst.bits[bit/8] & (1 << (bit % 8)); int srcset = src.bits[bit/8] & (1 << (bit % 8)); if (srcset && !dstset) { changed = YES; dst.bits[bit/8] |= 1 << (bit % 8); } } return changed; } /*********************************************************************** * layout_bitmap_clear * Set dst=dst&~src. * dst must be at least as long as src. * Returns YES if any of dst's bits were changed. **********************************************************************/ bool layout_bitmap_clear(layout_bitmap dst, layout_bitmap src, const char *msg) { bool changed = NO; size_t bit; if (dst.bitCount < src.bitCount) { _objc_fatal("layout_bitmap_clear: layout bitmap too short%s%s", msg ? ": " : "", msg ? msg : ""); } // fixme optimize for byte/word at a time for (bit = 0; bit < src.bitCount; bit++) { int dstset = dst.bits[bit/8] & (1 << (bit % 8)); int srcset = src.bits[bit/8] & (1 << (bit % 8)); if (srcset && dstset) { changed = YES; dst.bits[bit/8] &= ~(1 << (bit % 8)); } } return changed; } void layout_bitmap_print(layout_bitmap bits) { size_t i; printf("%zu: ", bits.bitCount); for (i = 0; i < bits.bitCount; i++) { int set = bits.bits[i/8] & (1 << (i % 8)); printf("%c", set ? '#' : '.'); } printf("\n"); } #if 0 // The code below may be useful when interpreting ivar types more precisely. /********************************************************************** * mark_offset_for_layout * * Marks the appropriate bit in the bits array cooresponding to a the * offset of a reference. If we are scanning a nested pointer structure * then the bits array will be NULL then this function does nothing. * **********************************************************************/ static void mark_offset_for_layout(long offset, long bits_size, unsigned char *bits) { // references are ignored if bits is NULL if (bits) { long slot = offset / sizeof(long); // determine byte index using (offset / 8 bits per byte) long i_byte = slot >> 3; // if the byte index is valid if (i_byte < bits_size) { // set the (offset / 8 bits per byte)th bit bits[i_byte] |= 1 << (slot & 7); } else { // offset not within instance size _objc_inform ("layout - offset exceeds instance size"); } } } /********************************************************************** * skip_ivar_type_name * * Skip over the name of a field/class in an ivar type string. Names * are in the form of a double-quoted string. Returns the remaining * string. * **********************************************************************/ static char *skip_ivar_type_name(char *type) { // current character char ch; // if there is an open quote if (*type == '\"') { // skip quote type++; // while no closing quote while ((ch = *type) != '\"') { // if end of string return end of string if (!ch) return type; // skip character type++; } // skip closing quote type++; } // return remaining string return type; } /********************************************************************** * skip_ivar_struct_name * * Skip over the name of a struct in an ivar type string. Names * may be followed by an equals sign. Returns the remaining string. * **********************************************************************/ static char *skip_ivar_struct_name(char *type) { // get first character char ch = *type; if (ch == _C_UNDEF) { // skip undefined name type++; } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_') { // if alphabetic // scan alphanumerics do { // next character ch = *++type; } while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || (ch >= '0' && ch <= '9')); } else { // no struct name present return type; } // skip equals sign if (*type == '=') type++; return type; } /********************************************************************** * scan_basic_ivar_type * * Determines the size and alignment of a basic ivar type. If the basic * type is a possible reference to another garbage collected type the * is_reference is set to true (false otherwise.) Returns the remaining * string. * **********************************************************************/ static char *scan_ivar_type_for_layout(char *type, long offset, long bits_size, unsigned char *bits, long *next_offset); static char *scan_basic_ivar_type(char *type, long *size, long *alignment, bool *is_reference) { // assume it is a non-reference type *is_reference = NO; // get the first character (advancing string) const char *full_type = type; char ch = *type++; // GCC 4 uses for const type*. if (ch == _C_CONST) ch = *type++; // act on first character switch (ch) { case _C_ID: { // ID type // skip over optional class name type = skip_ivar_type_name(type); // size and alignment of an id type *size = sizeof(id); *alignment = __alignof(id); // is a reference type *is_reference = YES; break; } case _C_PTR: { // C pointer type // skip underlying type long ignored_offset; type = scan_ivar_type_for_layout(type, 0, 0, NULL, &ignored_offset); // size and alignment of a generic pointer type *size = sizeof(void *); *alignment = __alignof(void *); // is a reference type *is_reference = YES; break; } case _C_CHARPTR: { // C string // size and alignment of a char pointer type *size = sizeof(char *); *alignment = __alignof(char *); // is a reference type *is_reference = YES; break; } case _C_CLASS: case _C_SEL: { // classes and selectors are ignored for now *size = sizeof(void *); *alignment = __alignof(void *); break; } case _C_CHR: case _C_UCHR: { // char and unsigned char *size = sizeof(char); *alignment = __alignof(char); break; } case _C_SHT: case _C_USHT: { // short and unsigned short *size = sizeof(short); *alignment = __alignof(short); break; } case _C_ATOM: case _C_INT: case _C_UINT: { // int and unsigned int *size = sizeof(int); *alignment = __alignof(int); break; } case _C_LNG: case _C_ULNG: { // long and unsigned long *size = sizeof(long); *alignment = __alignof(long); break; } case _C_LNG_LNG: case _C_ULNG_LNG: { // long long and unsigned long long *size = sizeof(long long); *alignment = __alignof(long long); break; } case _C_VECTOR: { // vector *size = 16; *alignment = 16; break; } case _C_FLT: { // float *size = sizeof(float); *alignment = __alignof(float); break; } case _C_DBL: { // double *size = sizeof(double); *alignment = __alignof(double); break; } case _C_BFLD: { // bit field // get number of bits in bit field (advance type string) long lng = strtol(type, &type, 10); // while next type is a bit field while (*type == _C_BFLD) { // skip over _C_BFLD type++; // get next bit field length long next_lng = strtol(type, &type, 10); // if spans next word then align to next word if ((lng & ~31) != ((lng + next_lng) & ~31)) lng = (lng + 31) & ~31; // increment running length lng += next_lng; // skip over potential field name type = skip_ivar_type_name(type); } // determine number of bytes bits represent *size = (lng + 7) / 8; // byte alignment *alignment = __alignof(char); break; } case _C_BOOL: { // double *size = sizeof(BOOL); *alignment = __alignof(BOOL); break; } case _C_VOID: { // skip void types *size = 0; *alignment = __alignof(char); break; } case _C_UNDEF: { *size = 0; *alignment = __alignof(char); break; } default: { // unhandled type _objc_fatal("unrecognized character \'%c\' in ivar type: \"%s\"", ch, full_type); } } return type; } /********************************************************************** * scan_ivar_type_for_layout * * Scan an ivar type string looking for references. The offset indicates * where the ivar begins. bits is a byte array of size bits_size used to * contain the references bit map. next_offset is the offset beyond the * ivar. Returns the remaining string. * **********************************************************************/ static char *scan_ivar_type_for_layout(char *type, long offset, long bits_size, unsigned char *bits, long *next_offset) { long size; // size of a basic type long alignment; // alignment of the basic type bool is_reference; // true if the type indicates a reference to a garbage collected object // get the first character char ch = *type; // GCC 4 uses for const type*. if (ch == _C_CONST) ch = *++type; // act on first character switch (ch) { case _C_ARY_B: { // array type // get the array length long lng = strtol(type + 1, &type, 10); // next type will be where to advance the type string once the array is processed char *next_type = type; // repeat the next type x lng if (!lng) { next_type = scan_ivar_type_for_layout(type, 0, 0, NULL, &offset); } else { while (lng--) { // repeatedly scan the same type next_type = scan_ivar_type_for_layout(type, offset, bits_size, bits, &offset); } } // advance the type now type = next_type; // after the end of the array *next_offset = offset; // advance over closing bracket if (*type == _C_ARY_E) type++; else _objc_inform("missing \'%c\' in ivar type.", _C_ARY_E); break; } case _C_UNION_B: { // union type // skip over possible union name type = skip_ivar_struct_name(type + 1); // need to accumulate the maximum element offset long max_offset = 0; // while not closing paren while ((ch = *type) && ch != _C_UNION_E) { // skip over potential field name type = skip_ivar_type_name(type); // scan type long union_offset; type = scan_ivar_type_for_layout(type, offset, bits_size, bits, &union_offset); // adjust the maximum element offset if (max_offset < union_offset) max_offset = union_offset; } // after the largest element *next_offset = max_offset; // advance over closing paren if (ch == _C_UNION_E) { type++; } else { _objc_inform("missing \'%c\' in ivar type", _C_UNION_E); } break; } case _C_STRUCT_B: { // struct type // skip over possible struct name type = skip_ivar_struct_name(type + 1); // while not closing brace while ((ch = *type) && ch != _C_STRUCT_E) { // skip over potential field name type = skip_ivar_type_name(type); // scan type type = scan_ivar_type_for_layout(type, offset, bits_size, bits, &offset); } // after the end of the struct *next_offset = offset; // advance over closing brace if (ch == _C_STRUCT_E) type++; else _objc_inform("missing \'%c\' in ivar type", _C_STRUCT_E); break; } default: { // basic type // scan type type = scan_basic_ivar_type(type, &size, &alignment, &is_reference); // create alignment mask alignment--; // align offset offset = (offset + alignment) & ~alignment; // if is a reference then mark in the bit map if (is_reference) mark_offset_for_layout(offset, bits_size, bits); // after the basic type *next_offset = offset + size; break; } } // return remainder of type string return type; } #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-load.h ================================================ /* * Copyright (c) 1999-2001, 2005-2006 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc-load.h * Copyright 1988-1996, NeXT Software, Inc. */ #ifndef _OBJC_LOAD_H_ #define _OBJC_LOAD_H_ #include #include /* dynamically loading Mach-O object files that contain Objective-C code */ OBJC_EXPORT long objc_loadModules ( char * _Nullable modlist[_Nullable], void * _Nullable errStream, void (* _Nullable class_callback) (Class _Nullable, Category _Nullable), /*headerType*/ struct mach_header * _Nullable * _Nullable hdr_addr, char * _Nullable debug_file ) OBJC2_UNAVAILABLE; OBJC_EXPORT int objc_loadModule ( char * _Nullable moduleName, void (* _Nullable class_callback) (Class _Nullable, Category _Nullable), int * _Nullable errorCode ) OBJC2_UNAVAILABLE; OBJC_EXPORT long objc_unloadModules( void * _Nullable errorStream, /* input (optional) */ void (* _Nullable unloadCallback)(Class _Nullable, Category _Nullable) /* input (optional) */ ) OBJC2_UNAVAILABLE; #endif /* _OBJC_LOAD_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-load.mm ================================================ /* * Copyright (c) 1999-2001, 2004-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc-load.m * Copyright 1988-1996, NeXT Software, Inc. * Author: s. naroff * */ #include "objc-private.h" #include "objc-load.h" #if !__OBJC2__ && !TARGET_OS_WIN32 extern void (*callbackFunction)( Class, Category ); /********************************************************************************** * objc_loadModule. * * NOTE: Loading isn't really thread safe. If a load message recursively calls * objc_loadModules() both sets will be loaded correctly, but if the original * caller calls objc_unloadModules() it will probably unload the wrong modules. * If a load message calls objc_unloadModules(), then it will unload * the modules currently being loaded, which will probably cause a crash. * * Error handling is still somewhat crude. If we encounter errors while * linking up classes or categories, we will not recover correctly. * * I removed attempts to lock the class hashtable, since this introduced * deadlock which was hard to remove. The only way you can get into trouble * is if one thread loads a module while another thread tries to access the * loaded classes (using objc_lookUpClass) before the load is complete. **********************************************************************************/ int objc_loadModule(char *moduleName, void (*class_callback) (Class, Category), int *errorCode) { int successFlag = 1; int locErrorCode; NSObjectFileImage objectFileImage; NSObjectFileImageReturnCode code; // So we don't have to check this everywhere if (errorCode == NULL) errorCode = &locErrorCode; if (moduleName == NULL) { *errorCode = NSObjectFileImageInappropriateFile; return 0; } if (_dyld_present () == 0) { *errorCode = NSObjectFileImageFailure; return 0; } callbackFunction = class_callback; code = NSCreateObjectFileImageFromFile (moduleName, &objectFileImage); if (code != NSObjectFileImageSuccess) { *errorCode = code; return 0; } if (NSLinkModule(objectFileImage, moduleName, NSLINKMODULE_OPTION_RETURN_ON_ERROR) == NULL) { NSLinkEditErrors error; int errorNum; const char *fileName, *errorString; NSLinkEditError(&error, &errorNum, &fileName, &errorString); // These errors may overlap with other errors that objc_loadModule returns in other failure cases. *errorCode = error; return 0; } callbackFunction = NULL; return successFlag; } /********************************************************************************** * objc_loadModules. **********************************************************************************/ /* Lock for dynamic loading and unloading. */ // static OBJC_DECLARE_LOCK (loadLock); long objc_loadModules (char * modlist[], void * errStream, void (*class_callback) (Class, Category), headerType ** hdr_addr, char * debug_file) { char ** modules; int code; int itWorked; if (modlist == 0) return 0; for (modules = &modlist[0]; *modules != 0; modules++) { itWorked = objc_loadModule (*modules, class_callback, &code); if (itWorked == 0) { //if (errStream) // NXPrintf ((NXStream *) errStream, "objc_loadModules(%s) code = %d\n", *modules, code); return 1; } if (hdr_addr) *(hdr_addr++) = 0; } return 0; } /********************************************************************************** * objc_unloadModules. * * NOTE: Unloading isn't really thread safe. If an unload message calls * objc_loadModules() or objc_unloadModules(), then the current call * to objc_unloadModules() will probably unload the wrong stuff. **********************************************************************************/ long objc_unloadModules (void * errStream, void (*unload_callback) (Class, Category)) { headerType * header_addr = 0; int errflag = 0; // TODO: to make unloading work, should get the current header if (header_addr) { ; // TODO: unload the current header } else { errflag = 1; } return errflag; } #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-loadmethod.h ================================================ /* * Copyright (c) 2004-2006 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-loadmethod.h * Support for +load methods. **********************************************************************/ #ifndef _OBJC_LOADMETHOD_H #define _OBJC_LOADMETHOD_H #include "objc-private.h" __BEGIN_DECLS extern void add_class_to_loadable_list(Class cls); extern void add_category_to_loadable_list(Category cat); extern void remove_class_from_loadable_list(Class cls); extern void remove_category_from_loadable_list(Category cat); extern void call_load_methods(void); __END_DECLS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-loadmethod.mm ================================================ /* * Copyright (c) 2004-2006 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-loadmethod.m * Support for +load methods. **********************************************************************/ #include "objc-loadmethod.h" #include "objc-private.h" typedef void(*load_method_t)(id, SEL); struct loadable_class { Class cls; // may be nil IMP method; }; struct loadable_category { Category cat; // may be nil IMP method; }; // List of classes that need +load called (pending superclass +load) // This list always has superclasses first because of the way it is constructed static struct loadable_class *loadable_classes = nil; static int loadable_classes_used = 0; static int loadable_classes_allocated = 0; // List of categories that need +load called (pending parent class +load) static struct loadable_category *loadable_categories = nil; static int loadable_categories_used = 0; static int loadable_categories_allocated = 0; /*********************************************************************** * add_class_to_loadable_list * Class cls has just become connected. Schedule it for +load if * it implements a +load method. **********************************************************************/ void add_class_to_loadable_list(Class cls) { IMP method; loadMethodLock.assertLocked(); method = cls->getLoadMethod(); if (!method) return; // Don't bother if cls has no +load method if (PrintLoading) { _objc_inform("LOAD: class '%s' scheduled for +load", cls->nameForLogging()); } if (loadable_classes_used == loadable_classes_allocated) { loadable_classes_allocated = loadable_classes_allocated*2 + 16; loadable_classes = (struct loadable_class *) realloc(loadable_classes, loadable_classes_allocated * sizeof(struct loadable_class)); } loadable_classes[loadable_classes_used].cls = cls; loadable_classes[loadable_classes_used].method = method; loadable_classes_used++; } /*********************************************************************** * add_category_to_loadable_list * Category cat's parent class exists and the category has been attached * to its class. Schedule this category for +load after its parent class * becomes connected and has its own +load method called. **********************************************************************/ void add_category_to_loadable_list(Category cat) { IMP method; loadMethodLock.assertLocked(); method = _category_getLoadMethod(cat); // Don't bother if cat has no +load method if (!method) return; if (PrintLoading) { _objc_inform("LOAD: category '%s(%s)' scheduled for +load", _category_getClassName(cat), _category_getName(cat)); } if (loadable_categories_used == loadable_categories_allocated) { loadable_categories_allocated = loadable_categories_allocated*2 + 16; loadable_categories = (struct loadable_category *) realloc(loadable_categories, loadable_categories_allocated * sizeof(struct loadable_category)); } loadable_categories[loadable_categories_used].cat = cat; loadable_categories[loadable_categories_used].method = method; loadable_categories_used++; } /*********************************************************************** * remove_class_from_loadable_list * Class cls may have been loadable before, but it is now no longer * loadable (because its image is being unmapped). **********************************************************************/ void remove_class_from_loadable_list(Class cls) { loadMethodLock.assertLocked(); if (loadable_classes) { int i; for (i = 0; i < loadable_classes_used; i++) { if (loadable_classes[i].cls == cls) { loadable_classes[i].cls = nil; if (PrintLoading) { _objc_inform("LOAD: class '%s' unscheduled for +load", cls->nameForLogging()); } return; } } } } /*********************************************************************** * remove_category_from_loadable_list * Category cat may have been loadable before, but it is now no longer * loadable (because its image is being unmapped). **********************************************************************/ void remove_category_from_loadable_list(Category cat) { loadMethodLock.assertLocked(); if (loadable_categories) { int i; for (i = 0; i < loadable_categories_used; i++) { if (loadable_categories[i].cat == cat) { loadable_categories[i].cat = nil; if (PrintLoading) { _objc_inform("LOAD: category '%s(%s)' unscheduled for +load", _category_getClassName(cat), _category_getName(cat)); } return; } } } } /*********************************************************************** * call_class_loads * Call all pending class +load methods. * If new classes become loadable, +load is NOT called for them. * * Called only by call_load_methods(). **********************************************************************/ static void call_class_loads(void) { int i; // Detach current loadable list. struct loadable_class *classes = loadable_classes; int used = loadable_classes_used; loadable_classes = nil; loadable_classes_allocated = 0; loadable_classes_used = 0; // Call all +loads for the detached list. for (i = 0; i < used; i++) { Class cls = classes[i].cls; load_method_t load_method = (load_method_t)classes[i].method; if (!cls) continue; if (PrintLoading) { _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging()); } (*load_method)(cls, SEL_load); } // Destroy the detached list. if (classes) free(classes); } /*********************************************************************** * call_category_loads * Call some pending category +load methods. * The parent class of the +load-implementing categories has all of * its categories attached, in case some are lazily waiting for +initalize. * Don't call +load unless the parent class is connected. * If new categories become loadable, +load is NOT called, and they * are added to the end of the loadable list, and we return TRUE. * Return FALSE if no new categories became loadable. * * Called only by call_load_methods(). **********************************************************************/ static bool call_category_loads(void) { int i, shift; bool new_categories_added = NO; // Detach current loadable list. struct loadable_category *cats = loadable_categories; int used = loadable_categories_used; int allocated = loadable_categories_allocated; loadable_categories = nil; loadable_categories_allocated = 0; loadable_categories_used = 0; // Call all +loads for the detached list. for (i = 0; i < used; i++) { Category cat = cats[i].cat; load_method_t load_method = (load_method_t)cats[i].method; Class cls; if (!cat) continue; cls = _category_getClass(cat); if (cls && cls->isLoadable()) { if (PrintLoading) { _objc_inform("LOAD: +[%s(%s) load]\n", cls->nameForLogging(), _category_getName(cat)); } (*load_method)(cls, SEL_load); cats[i].cat = nil; } } // Compact detached list (order-preserving) shift = 0; for (i = 0; i < used; i++) { if (cats[i].cat) { cats[i-shift] = cats[i]; } else { shift++; } } used -= shift; // Copy any new +load candidates from the new list to the detached list. new_categories_added = (loadable_categories_used > 0); for (i = 0; i < loadable_categories_used; i++) { if (used == allocated) { allocated = allocated*2 + 16; cats = (struct loadable_category *) realloc(cats, allocated * sizeof(struct loadable_category)); } cats[used++] = loadable_categories[i]; } // Destroy the new list. if (loadable_categories) free(loadable_categories); // Reattach the (now augmented) detached list. // But if there's nothing left to load, destroy the list. if (used) { loadable_categories = cats; loadable_categories_used = used; loadable_categories_allocated = allocated; } else { if (cats) free(cats); loadable_categories = nil; loadable_categories_used = 0; loadable_categories_allocated = 0; } if (PrintLoading) { if (loadable_categories_used != 0) { _objc_inform("LOAD: %d categories still waiting for +load\n", loadable_categories_used); } } return new_categories_added; } /*********************************************************************** * call_load_methods * Call all pending class and category +load methods. * Class +load methods are called superclass-first. * Category +load methods are not called until after the parent class's +load. * * This method must be RE-ENTRANT, because a +load could trigger * more image mapping. In addition, the superclass-first ordering * must be preserved in the face of re-entrant calls. Therefore, * only the OUTERMOST call of this function will do anything, and * that call will handle all loadable classes, even those generated * while it was running. * * The sequence below preserves +load ordering in the face of * image loading during a +load, and make sure that no * +load method is forgotten because it was added during * a +load call. * Sequence: * 1. Repeatedly call class +loads until there aren't any more * 2. Call category +loads ONCE. * 3. Run more +loads if: * (a) there are more classes to load, OR * (b) there are some potential category +loads that have * still never been attempted. * Category +loads are only run once to ensure "parent class first" * ordering, even if a category +load triggers a new loadable class * and a new loadable category attached to that class. * * Locking: loadMethodLock must be held by the caller * All other locks must not be held. **********************************************************************/ void call_load_methods(void) { static bool loading = NO; bool more_categories; loadMethodLock.assertLocked(); // Re-entrant calls do nothing; the outermost call will finish the job. if (loading) return; loading = YES; void *pool = objc_autoreleasePoolPush(); do { // 1. Repeatedly call class +loads until there aren't any more while (loadable_classes_used > 0) { call_class_loads(); } // 2. Call category +loads ONCE more_categories = call_category_loads(); // 3. Run more +loads if there are classes OR more untried categories } while (loadable_classes_used > 0 || more_categories); objc_autoreleasePoolPop(pool); loading = NO; } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-lockdebug.h ================================================ /* * Copyright (c) 2015 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #if LOCKDEBUG extern void lockdebug_assert_all_locks_locked(); extern void lockdebug_assert_no_locks_locked(); extern void lockdebug_setInForkPrepare(bool); extern void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock); #else static constexpr inline void lockdebug_assert_all_locks_locked() { } static constexpr inline void lockdebug_assert_no_locks_locked() { } static constexpr inline void lockdebug_setInForkPrepare(bool) { } static constexpr inline void lockdebug_lock_precedes_lock(const void *, const void *) { } #endif extern void lockdebug_remember_mutex(mutex_tt *lock); extern void lockdebug_mutex_lock(mutex_tt *lock); extern void lockdebug_mutex_try_lock(mutex_tt *lock); extern void lockdebug_mutex_unlock(mutex_tt *lock); extern void lockdebug_mutex_assert_locked(mutex_tt *lock); extern void lockdebug_mutex_assert_unlocked(mutex_tt *lock); static constexpr inline void lockdebug_remember_mutex(mutex_tt *lock) { } static constexpr inline void lockdebug_mutex_lock(mutex_tt *lock) { } static constexpr inline void lockdebug_mutex_try_lock(mutex_tt *lock) { } static constexpr inline void lockdebug_mutex_unlock(mutex_tt *lock) { } static constexpr inline void lockdebug_mutex_assert_locked(mutex_tt *lock) { } static constexpr inline void lockdebug_mutex_assert_unlocked(mutex_tt *lock) { } extern void lockdebug_remember_monitor(monitor_tt *lock); extern void lockdebug_monitor_enter(monitor_tt *lock); extern void lockdebug_monitor_leave(monitor_tt *lock); extern void lockdebug_monitor_wait(monitor_tt *lock); extern void lockdebug_monitor_assert_locked(monitor_tt *lock); extern void lockdebug_monitor_assert_unlocked(monitor_tt *lock); static constexpr inline void lockdebug_remember_monitor(monitor_tt *lock) { } static constexpr inline void lockdebug_monitor_enter(monitor_tt *lock) { } static constexpr inline void lockdebug_monitor_leave(monitor_tt *lock) { } static constexpr inline void lockdebug_monitor_wait(monitor_tt *lock) { } static constexpr inline void lockdebug_monitor_assert_locked(monitor_tt *lock) { } static constexpr inline void lockdebug_monitor_assert_unlocked(monitor_tt *lock) {} extern void lockdebug_remember_recursive_mutex(recursive_mutex_tt *lock); extern void lockdebug_recursive_mutex_lock(recursive_mutex_tt *lock); extern void lockdebug_recursive_mutex_unlock(recursive_mutex_tt *lock); extern void lockdebug_recursive_mutex_assert_locked(recursive_mutex_tt *lock); extern void lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock); static constexpr inline void lockdebug_remember_recursive_mutex(recursive_mutex_tt *lock) { } static constexpr inline void lockdebug_recursive_mutex_lock(recursive_mutex_tt *lock) { } static constexpr inline void lockdebug_recursive_mutex_unlock(recursive_mutex_tt *lock) { } static constexpr inline void lockdebug_recursive_mutex_assert_locked(recursive_mutex_tt *lock) { } static constexpr inline void lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock) { } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-lockdebug.mm ================================================ /* * Copyright (c) 2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-lock.m * Error-checking locks for debugging. **********************************************************************/ #include "objc-private.h" #if LOCKDEBUG && !TARGET_OS_WIN32 #include /*********************************************************************** * Thread-local bool set during _objc_atfork_prepare(). * That function is allowed to break some lock ordering rules. **********************************************************************/ static tls_key_t fork_prepare_tls; void lockdebug_setInForkPrepare(bool inForkPrepare) { INIT_ONCE_PTR(fork_prepare_tls, tls_create(nil), (void)0); tls_set(fork_prepare_tls, (void*)inForkPrepare); } static bool inForkPrepare() { INIT_ONCE_PTR(fork_prepare_tls, tls_create(nil), (void)0); return (bool)tls_get(fork_prepare_tls); } /*********************************************************************** * Lock order graph. * "lock X precedes lock Y" means that X must be acquired first. * This property is transitive. **********************************************************************/ struct lockorder { const void *l; std::vector predecessors; mutable std::unordered_map memo; lockorder(const void *newl) : l(newl) { } }; static std::unordered_map lockOrderList; // not mutex_t because we don't want lock debugging on this lock static mutex_tt lockOrderLock; static bool lockPrecedesLock(const lockorder *oldlock, const lockorder *newlock) { auto memoed = newlock->memo.find(oldlock); if (memoed != newlock->memo.end()) { return memoed->second; } bool result = false; for (const auto *pre : newlock->predecessors) { if (oldlock == pre || lockPrecedesLock(oldlock, pre)) { result = true; break; } } newlock->memo[oldlock] = result; return result; } static bool lockPrecedesLock(const void *oldlock, const void *newlock) { mutex_tt::locker lock(lockOrderLock); auto oldorder = lockOrderList.find(oldlock); auto neworder = lockOrderList.find(newlock); if (neworder == lockOrderList.end() || oldorder == lockOrderList.end()) { return false; } return lockPrecedesLock(oldorder->second, neworder->second); } static bool lockUnorderedWithLock(const void *oldlock, const void *newlock) { mutex_tt::locker lock(lockOrderLock); auto oldorder = lockOrderList.find(oldlock); auto neworder = lockOrderList.find(newlock); if (neworder == lockOrderList.end() || oldorder == lockOrderList.end()) { return true; } if (lockPrecedesLock(oldorder->second, neworder->second) || lockPrecedesLock(neworder->second, oldorder->second)) { return false; } return true; } void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock) { if (lockPrecedesLock(newlock, oldlock)) { _objc_fatal("contradiction in lock order declaration"); } mutex_tt::locker lock(lockOrderLock); auto oldorder = lockOrderList.find(oldlock); auto neworder = lockOrderList.find(newlock); if (oldorder == lockOrderList.end()) { lockOrderList[oldlock] = new lockorder(oldlock); oldorder = lockOrderList.find(oldlock); } if (neworder == lockOrderList.end()) { lockOrderList[newlock] = new lockorder(newlock); neworder = lockOrderList.find(newlock); } neworder->second->predecessors.push_back(oldorder->second); } /*********************************************************************** * Recording - per-thread list of mutexes and monitors held **********************************************************************/ enum class lockkind { MUTEX = 1, MONITOR = 2, RDLOCK = 3, WRLOCK = 4, RECURSIVE = 5 }; #define MUTEX lockkind::MUTEX #define MONITOR lockkind::MONITOR #define RDLOCK lockkind::RDLOCK #define WRLOCK lockkind::WRLOCK #define RECURSIVE lockkind::RECURSIVE struct lockcount { lockkind k; // the kind of lock it is (MUTEX, MONITOR, etc) int i; // the lock's nest count }; using objc_lock_list = std::unordered_map; // Thread-local list of locks owned by a thread. // Used by lock ownership checks. static tls_key_t lock_tls; // Global list of all locks. // Used by fork() safety check. // This can't be a static struct because of C++ initialization order problems. static objc_lock_list& AllLocks() { static objc_lock_list *locks; INIT_ONCE_PTR(locks, new objc_lock_list, (void)0); return *locks; } static void destroyLocks(void *value) { auto locks = (objc_lock_list *)value; // fixme complain about any still-held locks? if (locks) delete locks; } static objc_lock_list& ownedLocks() { // Use a dedicated tls key to prevent differences vs non-debug in // usage of objc's other tls keys (required for some unit tests). INIT_ONCE_PTR(lock_tls, tls_create(&destroyLocks), (void)0); auto locks = (objc_lock_list *)tls_get(lock_tls); if (!locks) { locks = new objc_lock_list; tls_set(lock_tls, locks); } return *locks; } static bool hasLock(objc_lock_list& locks, const void *lock, lockkind kind) { auto iter = locks.find(lock); if (iter != locks.end() && iter->second.k == kind) return true; return false; } static const char *sym(const void *lock) { Dl_info info; int ok = dladdr(lock, &info); if (ok && info.dli_sname && info.dli_sname[0]) return info.dli_sname; else return "??"; } static void setLock(objc_lock_list& locks, const void *lock, lockkind kind) { // Check if we already own this lock. auto iter = locks.find(lock); if (iter != locks.end() && iter->second.k == kind) { iter->second.i++; return; } // Newly-acquired lock. Verify lock ordering. // Locks not in AllLocks are exempt (i.e. @synchronize locks) if (&locks != &AllLocks() && AllLocks().find(lock) != AllLocks().end()) { for (auto& oldlock : locks) { if (AllLocks().find(oldlock.first) == AllLocks().end()) { // oldlock is exempt continue; } if (lockPrecedesLock(lock, oldlock.first)) { _objc_fatal("lock %p (%s) incorrectly acquired before %p (%s)", oldlock.first, sym(oldlock.first), lock, sym(lock)); } if (!inForkPrepare() && lockUnorderedWithLock(lock, oldlock.first)) { // _objc_atfork_prepare is allowed to acquire // otherwise-unordered locks, but nothing else may. _objc_fatal("lock %p (%s) acquired before %p (%s) " "with no defined lock order", oldlock.first, sym(oldlock.first), lock, sym(lock)); } } } locks[lock] = lockcount{kind, 1}; } static void clearLock(objc_lock_list& locks, const void *lock, lockkind kind) { auto iter = locks.find(lock); if (iter != locks.end()) { auto& l = iter->second; if (l.k == kind) { if (--l.i == 0) { locks.erase(iter); } return; } } _objc_fatal("lock not found!"); } /*********************************************************************** * fork() safety checking **********************************************************************/ void lockdebug_remember_mutex(mutex_t *lock) { setLock(AllLocks(), lock, MUTEX); } void lockdebug_remember_recursive_mutex(recursive_mutex_t *lock) { setLock(AllLocks(), lock, RECURSIVE); } void lockdebug_remember_monitor(monitor_t *lock) { setLock(AllLocks(), lock, MONITOR); } void lockdebug_assert_all_locks_locked() { auto& owned = ownedLocks(); for (const auto& l : AllLocks()) { if (!hasLock(owned, l.first, l.second.k)) { _objc_fatal("lock %p:%d is incorrectly not owned", l.first, l.second.k); } } } void lockdebug_assert_no_locks_locked() { auto& owned = ownedLocks(); for (const auto& l : AllLocks()) { if (hasLock(owned, l.first, l.second.k)) { _objc_fatal("lock %p:%d is incorrectly owned", l.first, l.second.k); } } } /*********************************************************************** * Mutex checking **********************************************************************/ void lockdebug_mutex_lock(mutex_t *lock) { auto& locks = ownedLocks(); if (hasLock(locks, lock, MUTEX)) { _objc_fatal("deadlock: relocking mutex"); } setLock(locks, lock, MUTEX); } // try-lock success is the only case with lockdebug effects. // try-lock when already locked is OK (will fail) // try-lock failure does nothing. void lockdebug_mutex_try_lock_success(mutex_t *lock) { auto& locks = ownedLocks(); setLock(locks, lock, MUTEX); } void lockdebug_mutex_unlock(mutex_t *lock) { auto& locks = ownedLocks(); if (!hasLock(locks, lock, MUTEX)) { _objc_fatal("unlocking unowned mutex"); } clearLock(locks, lock, MUTEX); } void lockdebug_mutex_assert_locked(mutex_t *lock) { auto& locks = ownedLocks(); if (!hasLock(locks, lock, MUTEX)) { _objc_fatal("mutex incorrectly not locked"); } } void lockdebug_mutex_assert_unlocked(mutex_t *lock) { auto& locks = ownedLocks(); if (hasLock(locks, lock, MUTEX)) { _objc_fatal("mutex incorrectly locked"); } } /*********************************************************************** * Recursive mutex checking **********************************************************************/ void lockdebug_recursive_mutex_lock(recursive_mutex_t *lock) { auto& locks = ownedLocks(); setLock(locks, lock, RECURSIVE); } void lockdebug_recursive_mutex_unlock(recursive_mutex_t *lock) { auto& locks = ownedLocks(); if (!hasLock(locks, lock, RECURSIVE)) { _objc_fatal("unlocking unowned recursive mutex"); } clearLock(locks, lock, RECURSIVE); } void lockdebug_recursive_mutex_assert_locked(recursive_mutex_t *lock) { auto& locks = ownedLocks(); if (!hasLock(locks, lock, RECURSIVE)) { _objc_fatal("recursive mutex incorrectly not locked"); } } void lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_t *lock) { auto& locks = ownedLocks(); if (hasLock(locks, lock, RECURSIVE)) { _objc_fatal("recursive mutex incorrectly locked"); } } /*********************************************************************** * Monitor checking **********************************************************************/ void lockdebug_monitor_enter(monitor_t *lock) { auto& locks = ownedLocks(); if (hasLock(locks, lock, MONITOR)) { _objc_fatal("deadlock: relocking monitor"); } setLock(locks, lock, MONITOR); } void lockdebug_monitor_leave(monitor_t *lock) { auto& locks = ownedLocks(); if (!hasLock(locks, lock, MONITOR)) { _objc_fatal("unlocking unowned monitor"); } clearLock(locks, lock, MONITOR); } void lockdebug_monitor_wait(monitor_t *lock) { auto& locks = ownedLocks(); if (!hasLock(locks, lock, MONITOR)) { _objc_fatal("waiting in unowned monitor"); } } void lockdebug_monitor_assert_locked(monitor_t *lock) { auto& locks = ownedLocks(); if (!hasLock(locks, lock, MONITOR)) { _objc_fatal("monitor incorrectly not locked"); } } void lockdebug_monitor_assert_unlocked(monitor_t *lock) { auto& locks = ownedLocks(); if (hasLock(locks, lock, MONITOR)) { _objc_fatal("monitor incorrectly held"); } } #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-locks-new.h ================================================ /* * Copyright (c) 2017 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-locks-new.h * Declarations of all locks used in the runtime. **********************************************************************/ #ifndef _OBJC_LOCKS_NEW_H #define _OBJC_LOCKS_NEW_H // fork() safety requires careful tracking of all locks used in the runtime. // Thou shalt not declare any locks outside this file. extern mutex_t runtimeLock; extern mutex_t DemangleCacheLock; #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-locks-old.h ================================================ /* * Copyright (c) 2017 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-locks-old.h * Declarations of all locks used in the runtime. **********************************************************************/ #ifndef _OBJC_LOCKS_OLD_H #define _OBJC_LOCKS_OLD_H // fork() safety requires careful tracking of all locks used in the runtime. // Thou shalt not declare any locks outside this file. extern mutex_t classLock; extern mutex_t methodListLock; extern mutex_t NXUniqueStringLock; extern spinlock_t impLock; #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-locks.h ================================================ /* * Copyright (c) 2017 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-locks.h * Declarations of all locks used in the runtime. **********************************************************************/ #ifndef _OBJC_LOCKS_H #define _OBJC_LOCKS_H // fork() safety requires careful tracking of all locks used in the runtime. // Thou shalt not declare any locks outside this file. // Lock ordering is declared in _objc_fork_prepare() // and is enforced by lockdebug. extern monitor_t classInitLock; extern mutex_t selLock; extern mutex_t cacheUpdateLock; extern recursive_mutex_t loadMethodLock; extern mutex_t crashlog_lock; extern spinlock_t objcMsgLogLock; extern mutex_t AltHandlerDebugLock; extern mutex_t AssociationsManagerLock; extern StripedMap PropertyLocks; extern StripedMap StructLocks; extern StripedMap CppObjectLocks; // SideTable lock is buried awkwardly. Call a function to manipulate it. extern void SideTableLockAll(); extern void SideTableUnlockAll(); extern void SideTableForceResetAll(); extern void SideTableDefineLockOrder(); extern void SideTableLocksPrecedeLock(const void *newlock); extern void SideTableLocksSucceedLock(const void *oldlock); extern void SideTableLocksPrecedeLocks(StripedMap& newlocks); extern void SideTableLocksSucceedLocks(StripedMap& oldlocks); #if __OBJC2__ #include "objc-locks-new.h" #else #include "objc-locks-old.h" #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-object.h ================================================ /* * Copyright (c) 2010-2012 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * Inlineable parts of NSObject / objc_object implementation **********************************************************************/ #ifndef _OBJC_OBJCOBJECT_H_ #define _OBJC_OBJCOBJECT_H_ #include "objc-private.h" enum ReturnDisposition : bool { ReturnAtPlus0 = false, ReturnAtPlus1 = true }; static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition); #if SUPPORT_TAGGED_POINTERS extern "C" { extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT*2]; extern Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT]; } #define objc_tag_classes objc_debug_taggedpointer_classes #define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes #endif #if SUPPORT_INDEXED_ISA ALWAYS_INLINE Class & classForIndex(uintptr_t index) { assert(index > 0); assert(index < (uintptr_t)objc_indexed_classes_count); return objc_indexed_classes[index]; } #endif inline bool objc_object::isClass() { if (isTaggedPointer()) return false; return ISA()->isMetaClass(); } #if SUPPORT_TAGGED_POINTERS inline Class objc_object::getIsa() { if (!isTaggedPointer()) return ISA(); uintptr_t ptr = (uintptr_t)this; if (isExtTaggedPointer()) { uintptr_t slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK; return objc_tag_ext_classes[slot]; } else { uintptr_t slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK; return objc_tag_classes[slot]; } } inline bool objc_object::isTaggedPointer() { return _objc_isTaggedPointer(this); } inline bool objc_object::isBasicTaggedPointer() { return isTaggedPointer() && !isExtTaggedPointer(); } inline bool objc_object::isExtTaggedPointer() { uintptr_t ptr = _objc_decodeTaggedPointer(this); return (ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK; } // SUPPORT_TAGGED_POINTERS #else // not SUPPORT_TAGGED_POINTERS inline Class objc_object::getIsa() { return ISA(); } inline bool objc_object::isTaggedPointer() { return false; } inline bool objc_object::isBasicTaggedPointer() { return false; } inline bool objc_object::isExtTaggedPointer() { return false; } // not SUPPORT_TAGGED_POINTERS #endif #if SUPPORT_NONPOINTER_ISA inline Class objc_object::ISA() { assert(!isTaggedPointer()); #if SUPPORT_INDEXED_ISA if (isa.nonpointer) { uintptr_t slot = isa.indexcls; return classForIndex((unsigned)slot); } return (Class)isa.bits; #else return (Class)(isa.bits & ISA_MASK); #endif } inline bool objc_object::hasNonpointerIsa() { return isa.nonpointer; } inline void objc_object::initIsa(Class cls) { initIsa(cls, false, false); } inline void objc_object::initClassIsa(Class cls) { if (DisableNonpointerIsa || cls->instancesRequireRawIsa()) { initIsa(cls, false/*not nonpointer*/, false); } else { initIsa(cls, true/*nonpointer*/, false); } } inline void objc_object::initProtocolIsa(Class cls) { return initClassIsa(cls); } inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { assert(!cls->instancesRequireRawIsa()); assert(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); } inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { assert(!isTaggedPointer()); if (!nonpointer) { isa.cls = cls; } else { assert(!DisableNonpointerIsa); assert(!cls->instancesRequireRawIsa()); isa_t newisa(0); #if SUPPORT_INDEXED_ISA assert(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.shiftcls = (uintptr_t)cls >> 3; #endif // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to // guarantee memory order w.r.t. the class index table // ...but not too atomic because we don't want to hurt instantiation isa = newisa; } } inline Class objc_object::changeIsa(Class newCls) { // This is almost always true but there are // enough edge cases that we can't assert it. // assert(newCls->isFuture() || // newCls->isInitializing() || newCls->isInitialized()); assert(!isTaggedPointer()); isa_t oldisa; isa_t newisa; bool sideTableLocked = false; bool transcribeToSideTable = false; do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); if ((oldisa.bits == 0 || oldisa.nonpointer) && !newCls->isFuture() && newCls->canAllocNonpointer()) { // 0 -> nonpointer // nonpointer -> nonpointer #if SUPPORT_INDEXED_ISA if (oldisa.bits == 0) newisa.bits = ISA_INDEX_MAGIC_VALUE; else newisa = oldisa; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = newCls->hasCxxDtor(); assert(newCls->classArrayIndex() > 0); newisa.indexcls = (uintptr_t)newCls->classArrayIndex(); #else if (oldisa.bits == 0) newisa.bits = ISA_MAGIC_VALUE; else newisa = oldisa; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = newCls->hasCxxDtor(); newisa.shiftcls = (uintptr_t)newCls >> 3; #endif } else if (oldisa.nonpointer) { // nonpointer -> raw pointer // Need to copy retain count et al to side table. // Acquire side table lock before setting isa to // prevent races such as concurrent -release. if (!sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.cls = newCls; } else { // raw pointer -> raw pointer newisa.cls = newCls; } } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); if (transcribeToSideTable) { // Copy oldisa's retain count et al to side table. // oldisa.has_assoc: nothing to do // oldisa.has_cxx_dtor: nothing to do sidetable_moveExtraRC_nolock(oldisa.extra_rc, oldisa.deallocating, oldisa.weakly_referenced); } if (sideTableLocked) sidetable_unlock(); if (oldisa.nonpointer) { #if SUPPORT_INDEXED_ISA return classForIndex(oldisa.indexcls); #else return (Class)((uintptr_t)oldisa.shiftcls << 3); #endif } else { return oldisa.cls; } } inline bool objc_object::hasAssociatedObjects() { if (isTaggedPointer()) return true; if (isa.nonpointer) return isa.has_assoc; return true; } inline void objc_object::setHasAssociatedObjects() { if (isTaggedPointer()) return; retry: isa_t oldisa = LoadExclusive(&isa.bits); isa_t newisa = oldisa; if (!newisa.nonpointer || newisa.has_assoc) { ClearExclusive(&isa.bits); return; } newisa.has_assoc = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; } inline bool objc_object::isWeaklyReferenced() { assert(!isTaggedPointer()); if (isa.nonpointer) return isa.weakly_referenced; else return sidetable_isWeaklyReferenced(); } inline void objc_object::setWeaklyReferenced_nolock() { retry: isa_t oldisa = LoadExclusive(&isa.bits); isa_t newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); sidetable_setWeaklyReferenced_nolock(); return; } if (newisa.weakly_referenced) { ClearExclusive(&isa.bits); return; } newisa.weakly_referenced = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; } inline bool objc_object::hasCxxDtor() { assert(!isTaggedPointer()); if (isa.nonpointer) return isa.has_cxx_dtor; else return isa.cls->hasCxxDtor(); } inline bool objc_object::rootIsDeallocating() { if (isTaggedPointer()) return false; if (isa.nonpointer) return isa.deallocating; return sidetable_isDeallocating(); } inline void objc_object::clearDeallocating() { if (slowpath(!isa.nonpointer)) { // Slow path for raw pointer isa. sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { // Slow path for non-pointer isa with weak refs and/or side table data. clearDeallocating_slow(); } assert(!sidetable_present()); } inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc)) { assert(!sidetable_present()); free(this); } else { object_dispose((id)this); } } // Equivalent to calling [this retain], with shortcuts if there is no override inline id objc_object::retain() { assert(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { return rootRetain(); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain); } // Base retain implementation, ignoring overrides. // This does not check isa.fast_rr; if there is an RR override then // it was already called and it chose to call [super retain]. // // tryRetain=true is the -_tryRetain path. // handleOverflow=false is the frameless fast path. // handleOverflow=true is the framed slow path including overflow to side table // The code is structured this way to prevent duplication. ALWAYS_INLINE id objc_object::rootRetain() { return rootRetain(false, false); } ALWAYS_INLINE bool objc_object::rootTryRetain() { return rootRetain(true, false) ? true : false; } ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { if (isTaggedPointer()) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); } // don't check newisa.fast_rr; we already called any RR overrides if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // newisa.extra_rc++ overflowed if (!handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; } // Equivalent to calling [this release], with shortcuts if there is no override inline void objc_object::release() { assert(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { rootRelease(); return; } ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release); } // Base release implementation, ignoring overrides. // Does not call -dealloc. // Returns true if the object should now be deallocated. // This does not check isa.fast_rr; if there is an RR override then // it was already called and it chose to call [super release]. // // handleUnderflow=false is the frameless fast path. // handleUnderflow=true is the framed slow path including side table borrow // The code is structured this way to prevent duplication. ALWAYS_INLINE bool objc_object::rootRelease() { return rootRelease(true, false); } ALWAYS_INLINE bool objc_object::rootReleaseShouldDealloc() { return rootRelease(false, false); } ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { if (isTaggedPointer()) return false; bool sideTableLocked = false; isa_t oldisa; isa_t newisa; retry: do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return sidetable_release(performDealloc); } // don't check newisa.fast_rr; we already called any RR overrides uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- if (slowpath(carry)) { // don't ClearExclusive() goto underflow; } } while (slowpath(!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(sideTableLocked)) sidetable_unlock(); return false; underflow: // newisa.extra_rc-- underflowed: borrow from side table or deallocate // abandon newisa to undo the decrement newisa = oldisa; if (slowpath(newisa.has_sidetable_rc)) { if (!handleUnderflow) { ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } // Transfer retain count from side table to inline storage. if (!sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; // Need to start over to avoid a race against // the nonpointer -> raw pointer transition. goto retry; } // Try to remove some retain counts from the side table. size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); // To avoid races, has_sidetable_rc must remain set // even if the side table count is now zero. if (borrowed > 0) { // Side table retain count decreased. // Try to add them to the inline count. newisa.extra_rc = borrowed - 1; // redo the original decrement too bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); if (!stored) { // Inline update failed. // Try it again right now. This prevents livelock on LL/SC // architectures where the side table access itself may have // dropped the reservation. isa_t oldisa2 = LoadExclusive(&isa.bits); isa_t newisa2 = oldisa2; if (newisa2.nonpointer) { uintptr_t overflow; newisa2.bits = addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow); if (!overflow) { stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, newisa2.bits); } } } if (!stored) { // Inline update failed. // Put the retains back in the side table. sidetable_addExtraRC_nolock(borrowed); goto retry; } // Decrement successful after borrowing from side table. // This decrement cannot be the deallocating decrement - the side // table lock and has_sidetable_rc bit ensure that if everyone // else tried to -release while we worked, the last one would block. sidetable_unlock(); return false; } else { // Side table is empty after all. Fall-through to the dealloc path. } } // Really deallocate. if (slowpath(newisa.deallocating)) { ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return overrelease_error(); // does not actually return } newisa.deallocating = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; if (slowpath(sideTableLocked)) sidetable_unlock(); __sync_synchronize(); if (performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return true; } // Equivalent to [this autorelease], with shortcuts if there is no override inline id objc_object::autorelease() { if (isTaggedPointer()) return (id)this; if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease(); return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease); } // Base autorelease implementation, ignoring overrides. inline id objc_object::rootAutorelease() { if (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; return rootAutorelease2(); } inline uintptr_t objc_object::rootRetainCount() { if (isTaggedPointer()) return (uintptr_t)this; sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); ClearExclusive(&isa.bits); if (bits.nonpointer) { uintptr_t rc = 1 + bits.extra_rc; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); } sidetable_unlock(); return rc; } sidetable_unlock(); return sidetable_retainCount(); } // SUPPORT_NONPOINTER_ISA #else // not SUPPORT_NONPOINTER_ISA inline Class objc_object::ISA() { assert(!isTaggedPointer()); return isa.cls; } inline bool objc_object::hasNonpointerIsa() { return false; } inline void objc_object::initIsa(Class cls) { assert(!isTaggedPointer()); isa = (uintptr_t)cls; } inline void objc_object::initClassIsa(Class cls) { initIsa(cls); } inline void objc_object::initProtocolIsa(Class cls) { initIsa(cls); } inline void objc_object::initInstanceIsa(Class cls, bool) { initIsa(cls); } inline void objc_object::initIsa(Class cls, bool, bool) { initIsa(cls); } inline Class objc_object::changeIsa(Class cls) { // This is almost always rue but there are // enough edge cases that we can't assert it. // assert(cls->isFuture() || // cls->isInitializing() || cls->isInitialized()); assert(!isTaggedPointer()); isa_t oldisa, newisa; newisa.cls = cls; do { oldisa = LoadExclusive(&isa.bits); } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); if (oldisa.cls && oldisa.cls->instancesHaveAssociatedObjects()) { cls->setInstancesHaveAssociatedObjects(); } return oldisa.cls; } inline bool objc_object::hasAssociatedObjects() { return getIsa()->instancesHaveAssociatedObjects(); } inline void objc_object::setHasAssociatedObjects() { getIsa()->setInstancesHaveAssociatedObjects(); } inline bool objc_object::isWeaklyReferenced() { assert(!isTaggedPointer()); return sidetable_isWeaklyReferenced(); } inline void objc_object::setWeaklyReferenced_nolock() { assert(!isTaggedPointer()); sidetable_setWeaklyReferenced_nolock(); } inline bool objc_object::hasCxxDtor() { assert(!isTaggedPointer()); return isa.cls->hasCxxDtor(); } inline bool objc_object::rootIsDeallocating() { if (isTaggedPointer()) return false; return sidetable_isDeallocating(); } inline void objc_object::clearDeallocating() { sidetable_clearDeallocating(); } inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; object_dispose((id)this); } // Equivalent to calling [this retain], with shortcuts if there is no override inline id objc_object::retain() { assert(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { return sidetable_retain(); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain); } // Base retain implementation, ignoring overrides. // This does not check isa.fast_rr; if there is an RR override then // it was already called and it chose to call [super retain]. inline id objc_object::rootRetain() { if (isTaggedPointer()) return (id)this; return sidetable_retain(); } // Equivalent to calling [this release], with shortcuts if there is no override inline void objc_object::release() { assert(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { sidetable_release(); return; } ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release); } // Base release implementation, ignoring overrides. // Does not call -dealloc. // Returns true if the object should now be deallocated. // This does not check isa.fast_rr; if there is an RR override then // it was already called and it chose to call [super release]. inline bool objc_object::rootRelease() { if (isTaggedPointer()) return false; return sidetable_release(true); } inline bool objc_object::rootReleaseShouldDealloc() { if (isTaggedPointer()) return false; return sidetable_release(false); } // Equivalent to [this autorelease], with shortcuts if there is no override inline id objc_object::autorelease() { if (isTaggedPointer()) return (id)this; if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease(); return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease); } // Base autorelease implementation, ignoring overrides. inline id objc_object::rootAutorelease() { if (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; return rootAutorelease2(); } // Base tryRetain implementation, ignoring overrides. // This does not check isa.fast_rr; if there is an RR override then // it was already called and it chose to call [super _tryRetain]. inline bool objc_object::rootTryRetain() { if (isTaggedPointer()) return true; return sidetable_tryRetain(); } inline uintptr_t objc_object::rootRetainCount() { if (isTaggedPointer()) return (uintptr_t)this; return sidetable_retainCount(); } // not SUPPORT_NONPOINTER_ISA #endif #if SUPPORT_RETURN_AUTORELEASE /*********************************************************************** Fast handling of return through Cocoa's +0 autoreleasing convention. The caller and callee cooperate to keep the returned object out of the autorelease pool and eliminate redundant retain/release pairs. An optimized callee looks at the caller's instructions following the return. If the caller's instructions are also optimized then the callee skips all retain count operations: no autorelease, no retain/autorelease. Instead it saves the result's current retain count (+0 or +1) in thread-local storage. If the caller does not look optimized then the callee performs autorelease or retain/autorelease as usual. An optimized caller looks at the thread-local storage. If the result is set then it performs any retain or release needed to change the result from the retain count left by the callee to the retain count desired by the caller. Otherwise the caller assumes the result is currently at +0 from an unoptimized callee and performs any retain needed for that case. There are two optimized callees: objc_autoreleaseReturnValue result is currently +1. The unoptimized path autoreleases it. objc_retainAutoreleaseReturnValue result is currently +0. The unoptimized path retains and autoreleases it. There are two optimized callers: objc_retainAutoreleasedReturnValue caller wants the value at +1. The unoptimized path retains it. objc_unsafeClaimAutoreleasedReturnValue caller wants the value at +0 unsafely. The unoptimized path does nothing. Example: Callee: // compute ret at +1 return objc_autoreleaseReturnValue(ret); Caller: ret = callee(); ret = objc_retainAutoreleasedReturnValue(ret); // use ret at +1 here Callee sees the optimized caller, sets TLS, and leaves the result at +1. Caller sees the TLS, clears it, and accepts the result at +1 as-is. The callee's recognition of the optimized caller is architecture-dependent. x86_64: Callee looks for `mov rax, rdi` followed by a call or jump instruction to objc_retainAutoreleasedReturnValue or objc_unsafeClaimAutoreleasedReturnValue. i386: Callee looks for a magic nop `movl %ebp, %ebp` (frame pointer register) armv7: Callee looks for a magic nop `mov r7, r7` (frame pointer register). arm64: Callee looks for a magic nop `mov x29, x29` (frame pointer register). Tagged pointer objects do participate in the optimized return scheme, because it saves message sends. They are not entered in the autorelease pool in the unoptimized case. **********************************************************************/ # if __x86_64__ static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void * const ra0) { const uint8_t *ra1 = (const uint8_t *)ra0; const unaligned_uint16_t *ra2; const unaligned_uint32_t *ra4 = (const unaligned_uint32_t *)ra1; const void **sym; #define PREFER_GOTPCREL 0 #if PREFER_GOTPCREL // 48 89 c7 movq %rax,%rdi // ff 15 callq *symbol@GOTPCREL(%rip) if (*ra4 != 0xffc78948) { return false; } if (ra1[4] != 0x15) { return false; } ra1 += 3; #else // 48 89 c7 movq %rax,%rdi // e8 callq symbol if (*ra4 != 0xe8c78948) { return false; } ra1 += (long)*(const unaligned_int32_t *)(ra1 + 4) + 8l; ra2 = (const unaligned_uint16_t *)ra1; // ff 25 jmpq *symbol@DYLDMAGIC(%rip) if (*ra2 != 0x25ff) { return false; } #endif ra1 += 6l + (long)*(const unaligned_int32_t *)(ra1 + 2); sym = (const void **)ra1; if (*sym != objc_retainAutoreleasedReturnValue && *sym != objc_unsafeClaimAutoreleasedReturnValue) { return false; } return true; } // __x86_64__ # elif __arm__ static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) { // if the low bit is set, we're returning to thumb mode if ((uintptr_t)ra & 1) { // 3f 46 mov r7, r7 // we mask off the low bit via subtraction // 16-bit instructions are well-aligned if (*(uint16_t *)((uint8_t *)ra - 1) == 0x463f) { return true; } } else { // 07 70 a0 e1 mov r7, r7 // 32-bit instructions may be only 16-bit aligned if (*(unaligned_uint32_t *)ra == 0xe1a07007) { return true; } } return false; } // __arm__ # elif __arm64__ static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) { // fd 03 1d aa mov fp, fp // arm64 instructions are well-aligned if (*(uint32_t *)ra == 0xaa1d03fd) { return true; } return false; } // __arm64__ # elif __i386__ static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) { // 89 ed movl %ebp, %ebp if (*(unaligned_uint16_t *)ra == 0xed89) { return true; } return false; } // __i386__ # else #warning unknown architecture static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) { return false; } // unknown architecture # endif static ALWAYS_INLINE ReturnDisposition getReturnDisposition() { return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY); } static ALWAYS_INLINE void setReturnDisposition(ReturnDisposition disposition) { tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition); } // Try to prepare for optimized return with the given disposition (+0 or +1). // Returns true if the optimized path is successful. // Otherwise the return value must be retained and/or autoreleased as usual. static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) { assert(getReturnDisposition() == ReturnAtPlus0); if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) { if (disposition) setReturnDisposition(disposition); return true; } return false; } // Try to accept an optimized return. // Returns the disposition of the returned object (+0 or +1). // An un-optimized return is +0. static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() { ReturnDisposition disposition = getReturnDisposition(); setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state return disposition; } // SUPPORT_RETURN_AUTORELEASE #else // not SUPPORT_RETURN_AUTORELEASE static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition __unused) { return false; } static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() { return ReturnAtPlus0; } // not SUPPORT_RETURN_AUTORELEASE #endif // _OBJC_OBJECT_H_ #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-opt.mm ================================================ /* * Copyright (c) 2012 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* objc-opt.mm Management of optimizations in the dyld shared cache */ #include "objc-private.h" #if !SUPPORT_PREOPT // Preoptimization not supported on this platform. struct objc_selopt_t; bool isPreoptimized(void) { return false; } bool noMissingWeakSuperclasses(void) { return false; } bool header_info::isPreoptimized() const { return false; } objc_selopt_t *preoptimizedSelectors(void) { return nil; } Protocol *getPreoptimizedProtocol(const char *name) { return nil; } unsigned int getPreoptimizedClassUnreasonableCount() { return 0; } Class getPreoptimizedClass(const char *name) { return nil; } Class* copyPreoptimizedClasses(const char *name, int *outCount) { *outCount = 0; return nil; } bool sharedRegionContains(const void *ptr) { return false; } header_info *preoptimizedHinfoForHeader(const headerType *mhdr) { return nil; } header_info_rw *getPreoptimizedHeaderRW(const struct header_info *const hdr) { return nil; } void preopt_init(void) { disableSharedCacheOptimizations(); if (PrintPreopt) { _objc_inform("PREOPTIMIZATION: is DISABLED " "(not supported on ths platform)"); } } // !SUPPORT_PREOPT #else // SUPPORT_PREOPT #include using objc_opt::objc_stringhash_offset_t; using objc_opt::objc_protocolopt_t; using objc_opt::objc_clsopt_t; using objc_opt::objc_headeropt_ro_t; using objc_opt::objc_headeropt_rw_t; using objc_opt::objc_opt_t; __BEGIN_DECLS // preopt: the actual opt used at runtime (nil or &_objc_opt_data) // _objc_opt_data: opt data possibly written by dyld // opt is initialized to ~0 to detect incorrect use before preopt_init() static const objc_opt_t *opt = (objc_opt_t *)~0; static uintptr_t shared_cache_start; static uintptr_t shared_cache_end; static bool preoptimized; extern const objc_opt_t _objc_opt_data; // in __TEXT, __objc_opt_ro /*********************************************************************** * Return YES if we have a valid optimized shared cache. **********************************************************************/ bool isPreoptimized(void) { return preoptimized; } /*********************************************************************** * Return YES if the shared cache does not have any classes with * missing weak superclasses. **********************************************************************/ bool noMissingWeakSuperclasses(void) { if (!preoptimized) return NO; // might have missing weak superclasses return opt->flags & objc_opt::NoMissingWeakSuperclasses; } /*********************************************************************** * Return YES if this image's dyld shared cache optimizations are valid. **********************************************************************/ bool header_info::isPreoptimized() const { // preoptimization disabled for some reason if (!preoptimized) return NO; // image not from shared cache, or not fixed inside shared cache if (!info()->optimizedByDyld()) return NO; return YES; } objc_selopt_t *preoptimizedSelectors(void) { return opt ? opt->selopt() : nil; } Protocol *getPreoptimizedProtocol(const char *name) { objc_protocolopt_t *protocols = opt ? opt->protocolopt() : nil; if (!protocols) return nil; return (Protocol *)protocols->getProtocol(name); } unsigned int getPreoptimizedClassUnreasonableCount() { objc_clsopt_t *classes = opt ? opt->clsopt() : nil; if (!classes) return 0; // This is an overestimate: each set of duplicates // gets double-counted in `capacity` as well. return classes->capacity + classes->duplicateCount(); } Class getPreoptimizedClass(const char *name) { objc_clsopt_t *classes = opt ? opt->clsopt() : nil; if (!classes) return nil; void *cls; void *hi; uint32_t count = classes->getClassAndHeader(name, cls, hi); if (count == 1 && ((header_info *)hi)->isLoaded()) { // exactly one matching class, and its image is loaded return (Class)cls; } else if (count > 1) { // more than one matching class - find one that is loaded void *clslist[count]; void *hilist[count]; classes->getClassesAndHeaders(name, clslist, hilist); for (uint32_t i = 0; i < count; i++) { if (((header_info *)hilist[i])->isLoaded()) { return (Class)clslist[i]; } } } // no match that is loaded return nil; } Class* copyPreoptimizedClasses(const char *name, int *outCount) { *outCount = 0; objc_clsopt_t *classes = opt ? opt->clsopt() : nil; if (!classes) return nil; void *cls; void *hi; uint32_t count = classes->getClassAndHeader(name, cls, hi); if (count == 0) return nil; Class *result = (Class *)calloc(count, sizeof(Class)); if (count == 1 && ((header_info *)hi)->isLoaded()) { // exactly one matching class, and its image is loaded result[(*outCount)++] = (Class)cls; return result; } else if (count > 1) { // more than one matching class - find those that are loaded void *clslist[count]; void *hilist[count]; classes->getClassesAndHeaders(name, clslist, hilist); for (uint32_t i = 0; i < count; i++) { if (((header_info *)hilist[i])->isLoaded()) { result[(*outCount)++] = (Class)clslist[i]; } } if (*outCount == 0) { // found multiple classes with that name, but none are loaded free(result); result = nil; } return result; } // no match that is loaded return nil; } /*********************************************************************** * Return YES if the given pointer lies within the shared cache. * If the shared cache is not set up or is not valid, **********************************************************************/ bool sharedRegionContains(const void *ptr) { uintptr_t address = (uintptr_t)ptr; return shared_cache_start <= address && address < shared_cache_end; } namespace objc_opt { struct objc_headeropt_ro_t { uint32_t count; uint32_t entsize; header_info headers[0]; // sorted by mhdr address header_info *get(const headerType *mhdr) { assert(entsize == sizeof(header_info)); int32_t start = 0; int32_t end = count; while (start <= end) { int32_t i = (start+end)/2; header_info *hi = headers+i; if (mhdr == hi->mhdr()) return hi; else if (mhdr < hi->mhdr()) end = i-1; else start = i+1; } #if DEBUG for (uint32_t i = 0; i < count; i++) { header_info *hi = headers+i; if (mhdr == hi->mhdr()) { _objc_fatal("failed to find header %p (%d/%d)", mhdr, i, count); } } #endif return nil; } }; struct objc_headeropt_rw_t { uint32_t count; uint32_t entsize; header_info_rw headers[0]; // sorted by mhdr address }; }; header_info *preoptimizedHinfoForHeader(const headerType *mhdr) { #if !__OBJC2__ // fixme old ABI shared cache doesn't prepare these properly return nil; #endif objc_headeropt_ro_t *hinfos = opt ? opt->headeropt_ro() : nil; if (hinfos) return hinfos->get(mhdr); else return nil; } header_info_rw *getPreoptimizedHeaderRW(const struct header_info *const hdr) { #if !__OBJC2__ // fixme old ABI shared cache doesn't prepare these properly return nil; #endif objc_headeropt_ro_t *hinfoRO = opt ? opt->headeropt_ro() : nil; objc_headeropt_rw_t *hinfoRW = opt ? opt->headeropt_rw() : nil; if (!hinfoRO || !hinfoRW) { _objc_fatal("preoptimized header_info missing for %s (%p %p %p)", hdr->fname(), hdr, hinfoRO, hinfoRW); } int32_t index = (int32_t)(hdr - hinfoRO->headers); assert(hinfoRW->entsize == sizeof(header_info_rw)); return &hinfoRW->headers[index]; } void preopt_init(void) { // Get the memory region occupied by the shared cache. size_t length; const void *start = _dyld_get_shared_cache_range(&length); if (start) { shared_cache_start = (uintptr_t)start; shared_cache_end = shared_cache_start + length; } else { shared_cache_start = shared_cache_end = 0; } // `opt` not set at compile time in order to detect too-early usage const char *failure = nil; opt = &_objc_opt_data; if (DisablePreopt) { // OBJC_DISABLE_PREOPTIMIZATION is set // If opt->version != VERSION then you continue at your own risk. failure = "(by OBJC_DISABLE_PREOPTIMIZATION)"; } else if (opt->version != objc_opt::VERSION) { // This shouldn't happen. You probably forgot to edit objc-sel-table.s. // If dyld really did write the wrong optimization version, // then we must halt because we don't know what bits dyld twiddled. _objc_fatal("bad objc preopt version (want %d, got %d)", objc_opt::VERSION, opt->version); } else if (!opt->selopt() || !opt->headeropt_ro()) { // One of the tables is missing. failure = "(dyld shared cache is absent or out of date)"; } if (failure) { // All preoptimized selector references are invalid. preoptimized = NO; opt = nil; disableSharedCacheOptimizations(); if (PrintPreopt) { _objc_inform("PREOPTIMIZATION: is DISABLED %s", failure); } } else { // Valid optimization data written by dyld shared cache preoptimized = YES; if (PrintPreopt) { _objc_inform("PREOPTIMIZATION: is ENABLED " "(version %d)", opt->version); } } } __END_DECLS // SUPPORT_PREOPT #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-os.h ================================================ /* * Copyright (c) 2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-os.h * OS portability layer. **********************************************************************/ #ifndef _OBJC_OS_H #define _OBJC_OS_H #include #include "objc-config.h" #ifdef __LP64__ # define WORD_SHIFT 3UL # define WORD_MASK 7UL # define WORD_BITS 64 #else # define WORD_SHIFT 2UL # define WORD_MASK 3UL # define WORD_BITS 32 #endif static inline uint32_t word_align(uint32_t x) { return (x + WORD_MASK) & ~WORD_MASK; } static inline size_t word_align(size_t x) { return (x + WORD_MASK) & ~WORD_MASK; } // Mix-in for classes that must not be copied. class nocopy_t { private: nocopy_t(const nocopy_t&) = delete; const nocopy_t& operator=(const nocopy_t&) = delete; protected: constexpr nocopy_t() = default; ~nocopy_t() = default; }; #if TARGET_OS_MAC # define OS_UNFAIR_LOCK_INLINE 1 # ifndef __STDC_LIMIT_MACROS # define __STDC_LIMIT_MACROS # endif # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # undef check # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include "objc-probes.h" // generated dtrace probe definitions. // Some libc functions call objc_msgSend() // so we can't use them without deadlocks. void syslog(int, const char *, ...) UNAVAILABLE_ATTRIBUTE; void vsyslog(int, const char *, va_list) UNAVAILABLE_ATTRIBUTE; #define ALWAYS_INLINE inline __attribute__((always_inline)) #define NEVER_INLINE inline __attribute__((noinline)) #define fastpath(x) (__builtin_expect(bool(x), 1)) #define slowpath(x) (__builtin_expect(bool(x), 0)) static ALWAYS_INLINE uintptr_t addc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) { return __builtin_addcl(lhs, rhs, carryin, carryout); } static ALWAYS_INLINE uintptr_t subc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) { return __builtin_subcl(lhs, rhs, carryin, carryout); } #if __arm64__ // Pointer-size register prefix for inline asm # if __LP64__ # define p "x" // true arm64 # else # define p "w" // arm64_32 # endif static ALWAYS_INLINE uintptr_t LoadExclusive(uintptr_t *src) { uintptr_t result; asm("ldxr %" p "0, [%x1]" : "=r" (result) : "r" (src), "m" (*src)); return result; } static ALWAYS_INLINE bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value) { uint32_t result; asm("stxr %w0, %" p "2, [%x3]" : "=&r" (result), "=m" (*dst) : "r" (value), "r" (dst)); return !result; } static ALWAYS_INLINE bool StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value) { uint32_t result; asm("stlxr %w0, %" p "2, [%x3]" : "=&r" (result), "=m" (*dst) : "r" (value), "r" (dst)); return !result; } static ALWAYS_INLINE void ClearExclusive(uintptr_t *dst) { // pretend it writes to *dst for instruction ordering purposes asm("clrex" : "=m" (*dst)); } #undef p #elif __arm__ static ALWAYS_INLINE uintptr_t LoadExclusive(uintptr_t *src) { return *src; } static ALWAYS_INLINE bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) { return OSAtomicCompareAndSwapPtr((void *)oldvalue, (void *)value, (void **)dst); } static ALWAYS_INLINE bool StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) { return OSAtomicCompareAndSwapPtrBarrier((void *)oldvalue, (void *)value, (void **)dst); } static ALWAYS_INLINE void ClearExclusive(uintptr_t *dst __unused) { } #elif __x86_64__ || __i386__ static ALWAYS_INLINE uintptr_t LoadExclusive(uintptr_t *src) { return *src; } static ALWAYS_INLINE bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) { return __sync_bool_compare_and_swap((void **)dst, (void *)oldvalue, (void *)value); } static ALWAYS_INLINE bool StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) { return StoreExclusive(dst, oldvalue, value); } static ALWAYS_INLINE void ClearExclusive(uintptr_t *dst __unused) { } #else # error unknown architecture #endif #if !TARGET_OS_IPHONE # include #else // CrashReporterClient not yet available on iOS __BEGIN_DECLS extern const char *CRSetCrashLogMessage(const char *msg); extern const char *CRGetCrashLogMessage(void); __END_DECLS #endif # if __cplusplus # include # include # include using namespace std; # endif # define PRIVATE_EXTERN __attribute__((visibility("hidden"))) # undef __private_extern__ # define __private_extern__ use_PRIVATE_EXTERN_instead # undef private_extern # define private_extern use_PRIVATE_EXTERN_instead /* Use this for functions that are intended to be breakpoint hooks. If you do not, the compiler may optimize them away. BREAKPOINT_FUNCTION( void stop_on_error(void) ); */ # define BREAKPOINT_FUNCTION(prototype) \ OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \ prototype { asm(""); } #elif TARGET_OS_WIN32 # define WINVER 0x0501 // target Windows XP and later # define _WIN32_WINNT 0x0501 // target Windows XP and later # define WIN32_LEAN_AND_MEAN // hack: windef.h typedefs BOOL as int # define BOOL WINBOOL # include # undef BOOL # include # include # include # include # include # include # include # include # if __cplusplus # include # include # include using namespace std; # define __BEGIN_DECLS extern "C" { # define __END_DECLS } # else # define __BEGIN_DECLS /*empty*/ # define __END_DECLS /*empty*/ # endif # define PRIVATE_EXTERN # define __attribute__(x) # define inline __inline /* Use this for functions that are intended to be breakpoint hooks. If you do not, the compiler may optimize them away. BREAKPOINT_FUNCTION( void MyBreakpointFunction(void) ); */ # define BREAKPOINT_FUNCTION(prototype) \ __declspec(noinline) prototype { __asm { } } /* stub out dtrace probes */ # define OBJC_RUNTIME_OBJC_EXCEPTION_RETHROW() do {} while(0) # define OBJC_RUNTIME_OBJC_EXCEPTION_THROW(arg0) do {} while(0) #else # error unknown OS #endif #include #include extern void _objc_fatal(const char *fmt, ...) __attribute__((noreturn, format (printf, 1, 2))); extern void _objc_fatal_with_reason(uint64_t reason, uint64_t flags, const char *fmt, ...) __attribute__((noreturn, format (printf, 3, 4))); #define INIT_ONCE_PTR(var, create, delete) \ do { \ if (var) break; \ typeof(var) v = create; \ while (!var) { \ if (OSAtomicCompareAndSwapPtrBarrier(0, (void*)v, (void**)&var)){ \ goto done; \ } \ } \ delete; \ done:; \ } while (0) #define INIT_ONCE_32(var, create, delete) \ do { \ if (var) break; \ typeof(var) v = create; \ while (!var) { \ if (OSAtomicCompareAndSwap32Barrier(0, v, (volatile int32_t *)&var)) { \ goto done; \ } \ } \ delete; \ done:; \ } while (0) // Thread keys reserved by libc for our use. #if defined(__PTK_FRAMEWORK_OBJC_KEY0) # define SUPPORT_DIRECT_THREAD_KEYS 1 # define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0) # define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1) # define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2) # define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3) # if SUPPORT_RETURN_AUTORELEASE # define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4) # endif #else # define SUPPORT_DIRECT_THREAD_KEYS 0 #endif #if TARGET_OS_WIN32 // Compiler compatibility // OS compatibility #define strdup _strdup #define issetugid() 0 #define MIN(x, y) ((x) < (y) ? (x) : (y)) static __inline void bcopy(const void *src, void *dst, size_t size) { memcpy(dst, src, size); } static __inline void bzero(void *dst, size_t size) { memset(dst, 0, size); } int asprintf(char **dstp, const char *format, ...); typedef void * malloc_zone_t; static __inline malloc_zone_t malloc_default_zone(void) { return (malloc_zone_t)-1; } static __inline void *malloc_zone_malloc(malloc_zone_t z, size_t size) { return malloc(size); } static __inline void *malloc_zone_calloc(malloc_zone_t z, size_t size, size_t count) { return calloc(size, count); } static __inline void *malloc_zone_realloc(malloc_zone_t z, void *p, size_t size) { return realloc(p, size); } static __inline void malloc_zone_free(malloc_zone_t z, void *p) { free(p); } static __inline malloc_zone_t malloc_zone_from_ptr(const void *p) { return (malloc_zone_t)-1; } static __inline size_t malloc_size(const void *p) { return _msize((void*)p); /* fixme invalid pointer check? */ } // OSAtomic static __inline BOOL OSAtomicCompareAndSwapLong(long oldl, long newl, long volatile *dst) { // fixme barrier is overkill long original = InterlockedCompareExchange(dst, newl, oldl); return (original == oldl); } static __inline BOOL OSAtomicCompareAndSwapPtrBarrier(void *oldp, void *newp, void * volatile *dst) { void *original = InterlockedCompareExchangePointer(dst, newp, oldp); return (original == oldp); } static __inline BOOL OSAtomicCompareAndSwap32Barrier(int32_t oldl, int32_t newl, int32_t volatile *dst) { long original = InterlockedCompareExchange((volatile long *)dst, newl, oldl); return (original == oldl); } static __inline int32_t OSAtomicDecrement32Barrier(volatile int32_t *dst) { return InterlockedDecrement((volatile long *)dst); } static __inline int32_t OSAtomicIncrement32Barrier(volatile int32_t *dst) { return InterlockedIncrement((volatile long *)dst); } // Internal data types typedef DWORD objc_thread_t; // thread ID static __inline int thread_equal(objc_thread_t t1, objc_thread_t t2) { return t1 == t2; } static __inline objc_thread_t thread_self(void) { return GetCurrentThreadId(); } typedef struct { DWORD key; void (*dtor)(void *); } tls_key_t; static __inline tls_key_t tls_create(void (*dtor)(void*)) { // fixme need dtor registry for DllMain to call on thread detach tls_key_t k; k.key = TlsAlloc(); k.dtor = dtor; return k; } static __inline void *tls_get(tls_key_t k) { return TlsGetValue(k.key); } static __inline void tls_set(tls_key_t k, void *value) { TlsSetValue(k.key, value); } typedef struct { CRITICAL_SECTION *lock; } mutex_t; #define MUTEX_INITIALIZER {0}; extern void mutex_init(mutex_t *m); static __inline int _mutex_lock_nodebug(mutex_t *m) { // fixme error check if (!m->lock) { mutex_init(m); } EnterCriticalSection(m->lock); return 0; } static __inline bool _mutex_try_lock_nodebug(mutex_t *m) { // fixme error check if (!m->lock) { mutex_init(m); } return TryEnterCriticalSection(m->lock); } static __inline int _mutex_unlock_nodebug(mutex_t *m) { // fixme error check LeaveCriticalSection(m->lock); return 0; } typedef mutex_t spinlock_t; #define spinlock_lock(l) mutex_lock(l) #define spinlock_unlock(l) mutex_unlock(l) #define SPINLOCK_INITIALIZER MUTEX_INITIALIZER typedef struct { HANDLE mutex; } recursive_mutex_t; #define RECURSIVE_MUTEX_INITIALIZER {0}; #define RECURSIVE_MUTEX_NOT_LOCKED 1 extern void recursive_mutex_init(recursive_mutex_t *m); static __inline int _recursive_mutex_lock_nodebug(recursive_mutex_t *m) { assert(m->mutex); return WaitForSingleObject(m->mutex, INFINITE); } static __inline bool _recursive_mutex_try_lock_nodebug(recursive_mutex_t *m) { assert(m->mutex); return (WAIT_OBJECT_0 == WaitForSingleObject(m->mutex, 0)); } static __inline int _recursive_mutex_unlock_nodebug(recursive_mutex_t *m) { assert(m->mutex); return ReleaseMutex(m->mutex) ? 0 : RECURSIVE_MUTEX_NOT_LOCKED; } /* typedef HANDLE mutex_t; static inline void mutex_init(HANDLE *m) { *m = CreateMutex(NULL, FALSE, NULL); } static inline void _mutex_lock(mutex_t *m) { WaitForSingleObject(*m, INFINITE); } static inline bool mutex_try_lock(mutex_t *m) { return WaitForSingleObject(*m, 0) == WAIT_OBJECT_0; } static inline void _mutex_unlock(mutex_t *m) { ReleaseMutex(*m); } */ // based on http://www.cs.wustl.edu/~schmidt/win32-cv-1.html // Vista-only CONDITION_VARIABLE would be better typedef struct { HANDLE mutex; HANDLE waiters; // semaphore for those in cond_wait() HANDLE waitersDone; // auto-reset event after everyone gets a broadcast CRITICAL_SECTION waitCountLock; // guards waitCount and didBroadcast unsigned int waitCount; int didBroadcast; } monitor_t; #define MONITOR_INITIALIZER { 0 } #define MONITOR_NOT_ENTERED 1 extern int monitor_init(monitor_t *c); static inline int _monitor_enter_nodebug(monitor_t *c) { if (!c->mutex) { int err = monitor_init(c); if (err) return err; } return WaitForSingleObject(c->mutex, INFINITE); } static inline int _monitor_leave_nodebug(monitor_t *c) { if (!ReleaseMutex(c->mutex)) return MONITOR_NOT_ENTERED; else return 0; } static inline int _monitor_wait_nodebug(monitor_t *c) { int last; EnterCriticalSection(&c->waitCountLock); c->waitCount++; LeaveCriticalSection(&c->waitCountLock); SignalObjectAndWait(c->mutex, c->waiters, INFINITE, FALSE); EnterCriticalSection(&c->waitCountLock); c->waitCount--; last = c->didBroadcast && c->waitCount == 0; LeaveCriticalSection(&c->waitCountLock); if (last) { // tell broadcaster that all waiters have awoken SignalObjectAndWait(c->waitersDone, c->mutex, INFINITE, FALSE); } else { WaitForSingleObject(c->mutex, INFINITE); } // fixme error checking return 0; } static inline int monitor_notify(monitor_t *c) { int haveWaiters; EnterCriticalSection(&c->waitCountLock); haveWaiters = c->waitCount > 0; LeaveCriticalSection(&c->waitCountLock); if (haveWaiters) { ReleaseSemaphore(c->waiters, 1, 0); } // fixme error checking return 0; } static inline int monitor_notifyAll(monitor_t *c) { EnterCriticalSection(&c->waitCountLock); if (c->waitCount == 0) { LeaveCriticalSection(&c->waitCountLock); return 0; } c->didBroadcast = 1; ReleaseSemaphore(c->waiters, c->waitCount, 0); LeaveCriticalSection(&c->waitCountLock); // fairness: wait for everyone to move from waiters to mutex WaitForSingleObject(c->waitersDone, INFINITE); // not under waitCountLock, but still under mutex c->didBroadcast = 0; // fixme error checking return 0; } typedef IMAGE_DOS_HEADER headerType; // fixme YES bundle? NO bundle? sometimes? #define headerIsBundle(hi) YES OBJC_EXTERN IMAGE_DOS_HEADER __ImageBase; #define libobjc_header ((headerType *)&__ImageBase) // Prototypes #elif TARGET_OS_MAC // OS headers #include #ifndef __LP64__ # define SEGMENT_CMD LC_SEGMENT #else # define SEGMENT_CMD LC_SEGMENT_64 #endif #ifndef VM_MEMORY_OBJC_DISPATCHERS # define VM_MEMORY_OBJC_DISPATCHERS 0 #endif // Compiler compatibility // OS compatibility static inline uint64_t nanoseconds() { return mach_absolute_time(); } // Internal data types typedef pthread_t objc_thread_t; static __inline int thread_equal(objc_thread_t t1, objc_thread_t t2) { return pthread_equal(t1, t2); } static __inline objc_thread_t thread_self(void) { return pthread_self(); } typedef pthread_key_t tls_key_t; static inline tls_key_t tls_create(void (*dtor)(void*)) { tls_key_t k; pthread_key_create(&k, dtor); return k; } static inline void *tls_get(tls_key_t k) { return pthread_getspecific(k); } static inline void tls_set(tls_key_t k, void *value) { pthread_setspecific(k, value); } #if SUPPORT_DIRECT_THREAD_KEYS #if DEBUG static bool is_valid_direct_key(tls_key_t k) { return ( k == SYNC_DATA_DIRECT_KEY || k == SYNC_COUNT_DIRECT_KEY || k == AUTORELEASE_POOL_KEY # if SUPPORT_RETURN_AUTORELEASE || k == RETURN_DISPOSITION_KEY # endif ); } #endif static inline void *tls_get_direct(tls_key_t k) { assert(is_valid_direct_key(k)); if (_pthread_has_direct_tsd()) { return _pthread_getspecific_direct(k); } else { return pthread_getspecific(k); } } static inline void tls_set_direct(tls_key_t k, void *value) { assert(is_valid_direct_key(k)); if (_pthread_has_direct_tsd()) { _pthread_setspecific_direct(k, value); } else { pthread_setspecific(k, value); } } // SUPPORT_DIRECT_THREAD_KEYS #endif static inline pthread_t pthread_self_direct() { return (pthread_t) _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF); } static inline mach_port_t mach_thread_self_direct() { return (mach_port_t)(uintptr_t) _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_MACH_THREAD_SELF); } template class mutex_tt; template class monitor_tt; template class recursive_mutex_tt; #if DEBUG # define LOCKDEBUG 1 #else # define LOCKDEBUG 0 #endif using spinlock_t = mutex_tt; using mutex_t = mutex_tt; using monitor_t = monitor_tt; using recursive_mutex_t = recursive_mutex_tt; // Use fork_unsafe_lock to get a lock that isn't // acquired and released around fork(). // All fork-safe locks are checked in debug builds. struct fork_unsafe_lock_t { constexpr fork_unsafe_lock_t() = default; }; extern const fork_unsafe_lock_t fork_unsafe_lock; #include "objc-lockdebug.h" template class mutex_tt : nocopy_t { os_unfair_lock mLock; public: constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) { lockdebug_remember_mutex(this); } constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { } void lock() { lockdebug_mutex_lock(this); os_unfair_lock_lock_with_options_inline (&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION); } void unlock() { lockdebug_mutex_unlock(this); os_unfair_lock_unlock_inline(&mLock); } void forceReset() { lockdebug_mutex_unlock(this); bzero(&mLock, sizeof(mLock)); mLock = os_unfair_lock OS_UNFAIR_LOCK_INIT; } void assertLocked() { lockdebug_mutex_assert_locked(this); } void assertUnlocked() { lockdebug_mutex_assert_unlocked(this); } // Address-ordered lock discipline for a pair of locks. static void lockTwo(mutex_tt *lock1, mutex_tt *lock2) { if (lock1 < lock2) { lock1->lock(); lock2->lock(); } else { lock2->lock(); if (lock2 != lock1) lock1->lock(); } } static void unlockTwo(mutex_tt *lock1, mutex_tt *lock2) { lock1->unlock(); if (lock2 != lock1) lock2->unlock(); } // Scoped lock and unlock class locker : nocopy_t { mutex_tt& lock; public: locker(mutex_tt& newLock) : lock(newLock) { lock.lock(); } ~locker() { lock.unlock(); } }; // Either scoped lock and unlock, or NOP. class conditional_locker : nocopy_t { mutex_tt& lock; bool didLock; public: conditional_locker(mutex_tt& newLock, bool shouldLock) : lock(newLock), didLock(shouldLock) { if (shouldLock) lock.lock(); } ~conditional_locker() { if (didLock) lock.unlock(); } }; }; using mutex_locker_t = mutex_tt::locker; using conditional_mutex_locker_t = mutex_tt::conditional_locker; template class recursive_mutex_tt : nocopy_t { os_unfair_recursive_lock mLock; public: constexpr recursive_mutex_tt() : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) { lockdebug_remember_recursive_mutex(this); } constexpr recursive_mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) { } void lock() { lockdebug_recursive_mutex_lock(this); os_unfair_recursive_lock_lock(&mLock); } void unlock() { lockdebug_recursive_mutex_unlock(this); os_unfair_recursive_lock_unlock(&mLock); } void forceReset() { lockdebug_recursive_mutex_unlock(this); bzero(&mLock, sizeof(mLock)); mLock = os_unfair_recursive_lock OS_UNFAIR_RECURSIVE_LOCK_INIT; } bool tryUnlock() { if (os_unfair_recursive_lock_tryunlock4objc(&mLock)) { lockdebug_recursive_mutex_unlock(this); return true; } return false; } void assertLocked() { lockdebug_recursive_mutex_assert_locked(this); } void assertUnlocked() { lockdebug_recursive_mutex_assert_unlocked(this); } }; template class monitor_tt { pthread_mutex_t mutex; pthread_cond_t cond; public: constexpr monitor_tt() : mutex(PTHREAD_MUTEX_INITIALIZER), cond(PTHREAD_COND_INITIALIZER) { lockdebug_remember_monitor(this); } monitor_tt(const fork_unsafe_lock_t unsafe) : mutex(PTHREAD_MUTEX_INITIALIZER), cond(PTHREAD_COND_INITIALIZER) { } void enter() { lockdebug_monitor_enter(this); int err = pthread_mutex_lock(&mutex); if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err); } void leave() { lockdebug_monitor_leave(this); int err = pthread_mutex_unlock(&mutex); if (err) _objc_fatal("pthread_mutex_unlock failed (%d)", err); } void wait() { lockdebug_monitor_wait(this); int err = pthread_cond_wait(&cond, &mutex); if (err) _objc_fatal("pthread_cond_wait failed (%d)", err); } void notify() { int err = pthread_cond_signal(&cond); if (err) _objc_fatal("pthread_cond_signal failed (%d)", err); } void notifyAll() { int err = pthread_cond_broadcast(&cond); if (err) _objc_fatal("pthread_cond_broadcast failed (%d)", err); } void forceReset() { lockdebug_monitor_leave(this); bzero(&mutex, sizeof(mutex)); bzero(&cond, sizeof(cond)); mutex = pthread_mutex_t PTHREAD_MUTEX_INITIALIZER; cond = pthread_cond_t PTHREAD_COND_INITIALIZER; } void assertLocked() { lockdebug_monitor_assert_locked(this); } void assertUnlocked() { lockdebug_monitor_assert_unlocked(this); } }; // semaphore_create formatted for INIT_ONCE use static inline semaphore_t create_semaphore(void) { semaphore_t sem; kern_return_t k; k = semaphore_create(mach_task_self(), &sem, SYNC_POLICY_FIFO, 0); if (k) _objc_fatal("semaphore_create failed (0x%x)", k); return sem; } #ifndef __LP64__ typedef struct mach_header headerType; typedef struct segment_command segmentType; typedef struct section sectionType; #else typedef struct mach_header_64 headerType; typedef struct segment_command_64 segmentType; typedef struct section_64 sectionType; #endif #define headerIsBundle(hi) (hi->mhdr()->filetype == MH_BUNDLE) #define libobjc_header ((headerType *)&_mh_dylib_header) // Prototypes /* Secure /tmp usage */ extern int secure_open(const char *filename, int flags, uid_t euid); #else #error unknown OS #endif static inline void * memdup(const void *mem, size_t len) { void *dup = malloc(len); memcpy(dup, mem, len); return dup; } // strdup that doesn't copy read-only memory static inline char * strdupIfMutable(const char *str) { size_t size = strlen(str) + 1; if (_dyld_is_memory_immutable(str, size)) { return (char *)str; } else { return (char *)memdup(str, size); } } // free strdupIfMutable() result static inline void freeIfMutable(char *str) { size_t size = strlen(str) + 1; if (_dyld_is_memory_immutable(str, size)) { // nothing } else { free(str); } } // nil-checking unsigned strdup static inline uint8_t * ustrdupMaybeNil(const uint8_t *str) { if (!str) return nil; return (uint8_t *)strdupIfMutable((char *)str); } // OS version checking: // // sdkVersion() // DYLD_OS_VERSION(mac, ios, tv, watch, bridge) // sdkIsOlderThan(mac, ios, tv, watch, bridge) // sdkIsAtLeast(mac, ios, tv, watch, bridge) // // This version order matches OBJC_AVAILABLE. #if TARGET_OS_OSX # define DYLD_OS_VERSION(x, i, t, w, b) DYLD_MACOSX_VERSION_##x # define sdkVersion() dyld_get_program_sdk_version() #elif TARGET_OS_IOS # define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##i # define sdkVersion() dyld_get_program_sdk_version() #elif TARGET_OS_TV // dyld does not currently have distinct constants for tvOS # define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##t # define sdkVersion() dyld_get_program_sdk_version() #elif TARGET_OS_BRIDGE # if TARGET_OS_WATCH # error bridgeOS 1.0 not supported # endif // fixme don't need bridgeOS versioning yet # define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##t # define sdkVersion() dyld_get_program_sdk_bridge_os_version() #elif TARGET_OS_WATCH # define DYLD_OS_VERSION(x, i, t, w, b) DYLD_WATCHOS_VERSION_##w // watchOS has its own API for compatibility reasons # define sdkVersion() dyld_get_program_sdk_watch_os_version() #else # error unknown OS #endif #define sdkIsOlderThan(x, i, t, w, b) \ (sdkVersion() < DYLD_OS_VERSION(x, i, t, w, b)) #define sdkIsAtLeast(x, i, t, w, b) \ (sdkVersion() >= DYLD_OS_VERSION(x, i, t, w, b)) // Allow bare 0 to be used in DYLD_OS_VERSION() and sdkIsOlderThan() #define DYLD_MACOSX_VERSION_0 0 #define DYLD_IOS_VERSION_0 0 #define DYLD_TVOS_VERSION_0 0 #define DYLD_WATCHOS_VERSION_0 0 #define DYLD_BRIDGEOS_VERSION_0 0 // Pretty-print a DYLD_*_VERSION_* constant. #define SDK_FORMAT "%hu.%hhu.%hhu" #define FORMAT_SDK(v) \ (unsigned short)(((uint32_t)(v))>>16), \ (unsigned char)(((uint32_t)(v))>>8), \ (unsigned char)(((uint32_t)(v))>>0) // fork() safety requires careful tracking of all locks. // Our custom lock types check this in debug builds. // Disallow direct use of all other lock types. typedef __darwin_pthread_mutex_t pthread_mutex_t UNAVAILABLE_ATTRIBUTE; typedef __darwin_pthread_rwlock_t pthread_rwlock_t UNAVAILABLE_ATTRIBUTE; typedef int32_t OSSpinLock UNAVAILABLE_ATTRIBUTE; typedef struct os_unfair_lock_s os_unfair_lock UNAVAILABLE_ATTRIBUTE; #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-os.mm ================================================ /* * Copyright (c) 2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-os.m * OS portability layer. **********************************************************************/ #include "objc-private.h" #include "objc-loadmethod.h" #if TARGET_OS_WIN32 #include "objc-runtime-old.h" #include "objcrt.h" const fork_unsafe_lock_t fork_unsafe_lock; int monitor_init(monitor_t *c) { // fixme error checking HANDLE mutex = CreateMutex(NULL, TRUE, NULL); while (!c->mutex) { // fixme memory barrier here? if (0 == InterlockedCompareExchangePointer(&c->mutex, mutex, 0)) { // we win - finish construction c->waiters = CreateSemaphore(NULL, 0, 0x7fffffff, NULL); c->waitersDone = CreateEvent(NULL, FALSE, FALSE, NULL); InitializeCriticalSection(&c->waitCountLock); c->waitCount = 0; c->didBroadcast = 0; ReleaseMutex(c->mutex); return 0; } } // someone else allocated the mutex and constructed the monitor ReleaseMutex(mutex); CloseHandle(mutex); return 0; } void mutex_init(mutex_t *m) { while (!m->lock) { CRITICAL_SECTION *newlock = malloc(sizeof(CRITICAL_SECTION)); InitializeCriticalSection(newlock); // fixme memory barrier here? if (0 == InterlockedCompareExchangePointer(&m->lock, newlock, 0)) { return; } // someone else installed their lock first DeleteCriticalSection(newlock); free(newlock); } } void recursive_mutex_init(recursive_mutex_t *m) { // fixme error checking HANDLE newmutex = CreateMutex(NULL, FALSE, NULL); while (!m->mutex) { // fixme memory barrier here? if (0 == InterlockedCompareExchangePointer(&m->mutex, newmutex, 0)) { // we win return; } } // someone else installed their lock first CloseHandle(newmutex); } WINBOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: environ_init(); tls_init(); lock_init(); sel_init(3500); // old selector heuristic exception_init(); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } OBJC_EXPORT void *_objc_init_image(HMODULE image, const objc_sections *sects) { header_info *hi = malloc(sizeof(header_info)); size_t count, i; hi->mhdr = (const headerType *)image; hi->info = sects->iiStart; hi->allClassesRealized = NO; hi->modules = sects->modStart ? (Module *)((void **)sects->modStart+1) : 0; hi->moduleCount = (Module *)sects->modEnd - hi->modules; hi->protocols = sects->protoStart ? (struct old_protocol **)((void **)sects->protoStart+1) : 0; hi->protocolCount = (struct old_protocol **)sects->protoEnd - hi->protocols; hi->imageinfo = NULL; hi->imageinfoBytes = 0; // hi->imageinfo = sects->iiStart ? (uint8_t *)((void **)sects->iiStart+1) : 0;; // hi->imageinfoBytes = (uint8_t *)sects->iiEnd - hi->imageinfo; hi->selrefs = sects->selrefsStart ? (SEL *)((void **)sects->selrefsStart+1) : 0; hi->selrefCount = (SEL *)sects->selrefsEnd - hi->selrefs; hi->clsrefs = sects->clsrefsStart ? (Class *)((void **)sects->clsrefsStart+1) : 0; hi->clsrefCount = (Class *)sects->clsrefsEnd - hi->clsrefs; count = 0; for (i = 0; i < hi->moduleCount; i++) { if (hi->modules[i]) count++; } hi->mod_count = 0; hi->mod_ptr = 0; if (count > 0) { hi->mod_ptr = malloc(count * sizeof(struct objc_module)); for (i = 0; i < hi->moduleCount; i++) { if (hi->modules[i]) memcpy(&hi->mod_ptr[hi->mod_count++], hi->modules[i], sizeof(struct objc_module)); } } hi->moduleName = malloc(MAX_PATH * sizeof(TCHAR)); GetModuleFileName((HMODULE)(hi->mhdr), hi->moduleName, MAX_PATH * sizeof(TCHAR)); appendHeader(hi); if (PrintImages) { _objc_inform("IMAGES: loading image for %s%s%s%s\n", hi->fname, headerIsBundle(hi) ? " (bundle)" : "", hi->info->isReplacement() ? " (replacement)":"", hi->info->hasCategoryClassProperties() ? " (has class properties)":""); } // Count classes. Size various table based on the total. int total = 0; int unoptimizedTotal = 0; { if (_getObjc2ClassList(hi, &count)) { total += (int)count; if (!hi->getInSharedCache()) unoptimizedTotal += count; } } _read_images(&hi, 1, total, unoptimizedTotal); return hi; } OBJC_EXPORT void _objc_load_image(HMODULE image, header_info *hinfo) { prepare_load_methods(hinfo); call_load_methods(); } OBJC_EXPORT void _objc_unload_image(HMODULE image, header_info *hinfo) { _objc_fatal("image unload not supported"); } // TARGET_OS_WIN32 #elif TARGET_OS_MAC #include "objc-file-old.h" #include "objc-file.h" /*********************************************************************** * libobjc must never run static destructors. * Cover libc's __cxa_atexit with our own definition that runs nothing. * rdar://21734598 ER: Compiler option to suppress C++ static destructors **********************************************************************/ extern "C" int __cxa_atexit(); extern "C" int __cxa_atexit() { return 0; } /*********************************************************************** * bad_magic. * Return YES if the header has invalid Mach-o magic. **********************************************************************/ bool bad_magic(const headerType *mhdr) { return (mhdr->magic != MH_MAGIC && mhdr->magic != MH_MAGIC_64 && mhdr->magic != MH_CIGAM && mhdr->magic != MH_CIGAM_64); } static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses) { header_info *hi; if (bad_magic(mhdr)) return NULL; bool inSharedCache = false; // Look for hinfo from the dyld shared cache. hi = preoptimizedHinfoForHeader(mhdr); if (hi) { // Found an hinfo in the dyld shared cache. // Weed out duplicates. if (hi->isLoaded()) { return NULL; } inSharedCache = true; // Initialize fields not set by the shared cache // hi->next is set by appendHeader hi->setLoaded(true); if (PrintPreopt) { _objc_inform("PREOPTIMIZATION: honoring preoptimized header info at %p for %s", hi, hi->fname()); } #if !__OBJC2__ _objc_fatal("shouldn't be here"); #endif #if DEBUG // Verify image_info size_t info_size = 0; const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size); assert(image_info == hi->info()); #endif } else { // Didn't find an hinfo in the dyld shared cache. // Weed out duplicates for (hi = FirstHeader; hi; hi = hi->getNext()) { if (mhdr == hi->mhdr()) return NULL; } // Locate the __OBJC segment size_t info_size = 0; unsigned long seg_size; const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size); const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size); if (!objc_segment && !image_info) return NULL; // Allocate a header_info entry. // Note we also allocate space for a single header_info_rw in the // rw_data[] inside header_info. hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1); // Set up the new header_info entry. hi->setmhdr(mhdr); #if !__OBJC2__ // mhdr must already be set hi->mod_count = 0; hi->mod_ptr = _getObjcModules(hi, &hi->mod_count); #endif // Install a placeholder image_info if absent to simplify code elsewhere static const objc_image_info emptyInfo = {0, 0}; hi->setinfo(image_info ?: &emptyInfo); hi->setLoaded(true); hi->setAllClassesRealized(NO); } #if __OBJC2__ { size_t count = 0; if (_getObjc2ClassList(hi, &count)) { totalClasses += (int)count; if (!inSharedCache) unoptimizedTotalClasses += count; } } #endif appendHeader(hi); return hi; } /*********************************************************************** * linksToLibrary * Returns true if the image links directly to a dylib whose install name * is exactly the given name. **********************************************************************/ bool linksToLibrary(const header_info *hi, const char *name) { const struct dylib_command *cmd; unsigned long i; cmd = (const struct dylib_command *) (hi->mhdr() + 1); for (i = 0; i < hi->mhdr()->ncmds; i++) { if (cmd->cmd == LC_LOAD_DYLIB || cmd->cmd == LC_LOAD_UPWARD_DYLIB || cmd->cmd == LC_LOAD_WEAK_DYLIB || cmd->cmd == LC_REEXPORT_DYLIB) { const char *dylib = cmd->dylib.name.offset + (const char *)cmd; if (0 == strcmp(dylib, name)) return true; } cmd = (const struct dylib_command *)((char *)cmd + cmd->cmdsize); } return false; } #if SUPPORT_GC_COMPAT /*********************************************************************** * shouldRejectGCApp * Return YES if the executable requires GC. **********************************************************************/ static bool shouldRejectGCApp(const header_info *hi) { assert(hi->mhdr()->filetype == MH_EXECUTE); if (!hi->info()->supportsGC()) { // App does not use GC. Don't reject it. return NO; } // Exception: Trivial AppleScriptObjC apps can run without GC. // 1. executable defines no classes // 2. executable references NSBundle only // 3. executable links to AppleScriptObjC.framework // Note that objc_appRequiresGC() also knows about this. size_t classcount = 0; size_t refcount = 0; #if __OBJC2__ _getObjc2ClassList(hi, &classcount); _getObjc2ClassRefs(hi, &refcount); #else if (hi->mod_count == 0 || (hi->mod_count == 1 && !hi->mod_ptr[0].symtab)) classcount = 0; else classcount = 1; _getObjcClassRefs(hi, &refcount); #endif if (classcount == 0 && refcount == 1 && linksToLibrary(hi, "/System/Library/Frameworks" "/AppleScriptObjC.framework/Versions/A" "/AppleScriptObjC")) { // It's AppleScriptObjC. Don't reject it. return NO; } else { // GC and not trivial AppleScriptObjC. Reject it. return YES; } } /*********************************************************************** * rejectGCImage * Halt if an image requires GC. * Testing of the main executable should use rejectGCApp() instead. **********************************************************************/ static bool shouldRejectGCImage(const headerType *mhdr) { assert(mhdr->filetype != MH_EXECUTE); objc_image_info *image_info; size_t size; #if !__OBJC2__ unsigned long seg_size; // 32-bit: __OBJC seg but no image_info means no GC support if (!getsegmentdata(mhdr, "__OBJC", &seg_size)) { // Not objc, therefore not GC. Don't reject it. return NO; } image_info = _getObjcImageInfo(mhdr, &size); if (!image_info) { // No image_info, therefore not GC. Don't reject it. return NO; } #else // 64-bit: no image_info means no objc at all image_info = _getObjcImageInfo(mhdr, &size); if (!image_info) { // Not objc, therefore not GC. Don't reject it. return NO; } #endif return image_info->requiresGC(); } // SUPPORT_GC_COMPAT #endif /*********************************************************************** * map_images_nolock * Process the given images which are being mapped in by dyld. * All class registration and fixups are performed (or deferred pending * discovery of missing superclasses etc), and +load methods are called. * * info[] is in bottom-up order i.e. libobjc will be earlier in the * array than any library that links to libobjc. * * Locking: loadMethodLock(old) or runtimeLock(new) acquired by map_images. **********************************************************************/ #if __OBJC2__ #include "objc-file.h" #else #include "objc-file-old.h" #endif void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { static bool firstTime = YES; header_info *hList[mhCount]; uint32_t hCount; size_t selrefCount = 0; // Perform first-time initialization if necessary. // This function is called before ordinary library initializers. // fixme defer initialization until an objc-using image is found? if (firstTime) { preopt_init(); } if (PrintImages) { _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount); } // Find all images with Objective-C metadata. hCount = 0; // Count classes. Size various table based on the total. int totalClasses = 0; int unoptimizedTotalClasses = 0; { uint32_t i = mhCount; while (i--) { const headerType *mhdr = (const headerType *)mhdrs[i]; auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses); if (!hi) { // no objc data in this entry continue; } if (mhdr->filetype == MH_EXECUTE) { // Size some data structures based on main executable's size #if __OBJC2__ size_t count; _getObjc2SelectorRefs(hi, &count); selrefCount += count; _getObjc2MessageRefs(hi, &count); selrefCount += count; #else _getObjcSelectorRefs(hi, &selrefCount); #endif #if SUPPORT_GC_COMPAT // Halt if this is a GC app. if (shouldRejectGCApp(hi)) { _objc_fatal_with_reason (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, OS_REASON_FLAG_CONSISTENT_FAILURE, "Objective-C garbage collection " "is no longer supported."); } #endif } hList[hCount++] = hi; if (PrintImages) { _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", hi->fname(), mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", hi->info()->isReplacement() ? " (replacement)" : "", hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "", hi->info()->optimizedByDyld()?" (preoptimized)":""); } } } // Perform one-time runtime initialization that must be deferred until // the executable itself is found. This needs to be done before // further initialization. // (The executable may not be present in this infoList if the // executable does not contain Objective-C code but Objective-C // is dynamically loaded later. if (firstTime) { sel_init(selrefCount); arr_init(); #if SUPPORT_GC_COMPAT // Reject any GC images linked to the main executable. // We already rejected the app itself above. // Images loaded after launch will be rejected by dyld. for (uint32_t i = 0; i < hCount; i++) { auto hi = hList[i]; auto mh = hi->mhdr(); if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) { _objc_fatal_with_reason (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, OS_REASON_FLAG_CONSISTENT_FAILURE, "%s requires Objective-C garbage collection " "which is no longer supported.", hi->fname()); } } #endif #if TARGET_OS_OSX // Disable +initialize fork safety if the app is too old (< 10.13). // Disable +initialize fork safety if the app has a // __DATA,__objc_fork_ok section. if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) { DisableInitializeForkSafety = true; if (PrintInitializing) { _objc_inform("INITIALIZE: disabling +initialize fork " "safety enforcement because the app is " "too old (SDK version " SDK_FORMAT ")", FORMAT_SDK(dyld_get_program_sdk_version())); } } for (uint32_t i = 0; i < hCount; i++) { auto hi = hList[i]; auto mh = hi->mhdr(); if (mh->filetype != MH_EXECUTE) continue; unsigned long size; if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) { DisableInitializeForkSafety = true; if (PrintInitializing) { _objc_inform("INITIALIZE: disabling +initialize fork " "safety enforcement because the app has " "a __DATA,__objc_fork_ok section"); } } break; // assume only one MH_EXECUTE image } #endif } if (hCount > 0) { _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } firstTime = NO; } /*********************************************************************** * unmap_image_nolock * Process the given image which is about to be unmapped by dyld. * mh is mach_header instead of headerType because that's what * dyld_priv.h says even for 64-bit. * * Locking: loadMethodLock(both) and runtimeLock(new) acquired by unmap_image. **********************************************************************/ void unmap_image_nolock(const struct mach_header *mh) { if (PrintImages) { _objc_inform("IMAGES: processing 1 newly-unmapped image...\n"); } header_info *hi; // Find the runtime's header_info struct for the image for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { if (hi->mhdr() == (const headerType *)mh) { break; } } if (!hi) return; if (PrintImages) { _objc_inform("IMAGES: unloading image for %s%s%s\n", hi->fname(), hi->mhdr()->filetype == MH_BUNDLE ? " (bundle)" : "", hi->info()->isReplacement() ? " (replacement)" : ""); } _unload_image(hi); // Remove header_info from header list removeHeader(hi); free(hi); } /*********************************************************************** * static_init * Run C++ static constructor functions. * libc calls _objc_init() before dyld would call our static constructors, * so we have to do it ourselves. **********************************************************************/ static void static_init() { size_t count; auto inits = getLibobjcInitializers(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { inits[i](); } } /*********************************************************************** * _objc_atfork_prepare * _objc_atfork_parent * _objc_atfork_child * Allow ObjC to be used between fork() and exec(). * libc requires this because it has fork-safe functions that use os_objects. * * _objc_atfork_prepare() acquires all locks. * _objc_atfork_parent() releases the locks again. * _objc_atfork_child() forcibly resets the locks. **********************************************************************/ // Declare lock ordering. #if LOCKDEBUG __attribute__((constructor)) static void defineLockOrder() { // Every lock precedes crashlog_lock // on the assumption that fatal errors could be anywhere. lockdebug_lock_precedes_lock(&loadMethodLock, &crashlog_lock); lockdebug_lock_precedes_lock(&classInitLock, &crashlog_lock); #if __OBJC2__ lockdebug_lock_precedes_lock(&runtimeLock, &crashlog_lock); lockdebug_lock_precedes_lock(&DemangleCacheLock, &crashlog_lock); #else lockdebug_lock_precedes_lock(&classLock, &crashlog_lock); lockdebug_lock_precedes_lock(&methodListLock, &crashlog_lock); lockdebug_lock_precedes_lock(&NXUniqueStringLock, &crashlog_lock); lockdebug_lock_precedes_lock(&impLock, &crashlog_lock); #endif lockdebug_lock_precedes_lock(&selLock, &crashlog_lock); lockdebug_lock_precedes_lock(&cacheUpdateLock, &crashlog_lock); lockdebug_lock_precedes_lock(&objcMsgLogLock, &crashlog_lock); lockdebug_lock_precedes_lock(&AltHandlerDebugLock, &crashlog_lock); lockdebug_lock_precedes_lock(&AssociationsManagerLock, &crashlog_lock); SideTableLocksPrecedeLock(&crashlog_lock); PropertyLocks.precedeLock(&crashlog_lock); StructLocks.precedeLock(&crashlog_lock); CppObjectLocks.precedeLock(&crashlog_lock); // loadMethodLock precedes everything // because it is held while +load methods run lockdebug_lock_precedes_lock(&loadMethodLock, &classInitLock); #if __OBJC2__ lockdebug_lock_precedes_lock(&loadMethodLock, &runtimeLock); lockdebug_lock_precedes_lock(&loadMethodLock, &DemangleCacheLock); #else lockdebug_lock_precedes_lock(&loadMethodLock, &methodListLock); lockdebug_lock_precedes_lock(&loadMethodLock, &classLock); lockdebug_lock_precedes_lock(&loadMethodLock, &NXUniqueStringLock); lockdebug_lock_precedes_lock(&loadMethodLock, &impLock); #endif lockdebug_lock_precedes_lock(&loadMethodLock, &selLock); lockdebug_lock_precedes_lock(&loadMethodLock, &cacheUpdateLock); lockdebug_lock_precedes_lock(&loadMethodLock, &objcMsgLogLock); lockdebug_lock_precedes_lock(&loadMethodLock, &AltHandlerDebugLock); lockdebug_lock_precedes_lock(&loadMethodLock, &AssociationsManagerLock); SideTableLocksSucceedLock(&loadMethodLock); PropertyLocks.succeedLock(&loadMethodLock); StructLocks.succeedLock(&loadMethodLock); CppObjectLocks.succeedLock(&loadMethodLock); // PropertyLocks and CppObjectLocks and AssociationManagerLock // precede everything because they are held while objc_retain() // or C++ copy are called. // (StructLocks do not precede everything because it calls memmove only.) auto PropertyAndCppObjectAndAssocLocksPrecedeLock = [&](const void *lock) { PropertyLocks.precedeLock(lock); CppObjectLocks.precedeLock(lock); lockdebug_lock_precedes_lock(&AssociationsManagerLock, lock); }; #if __OBJC2__ PropertyAndCppObjectAndAssocLocksPrecedeLock(&runtimeLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&DemangleCacheLock); #else PropertyAndCppObjectAndAssocLocksPrecedeLock(&methodListLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&classLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&NXUniqueStringLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&impLock); #endif PropertyAndCppObjectAndAssocLocksPrecedeLock(&classInitLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&selLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&cacheUpdateLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&objcMsgLogLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&AltHandlerDebugLock); SideTableLocksSucceedLocks(PropertyLocks); SideTableLocksSucceedLocks(CppObjectLocks); SideTableLocksSucceedLock(&AssociationsManagerLock); PropertyLocks.precedeLock(&AssociationsManagerLock); CppObjectLocks.precedeLock(&AssociationsManagerLock); #if __OBJC2__ lockdebug_lock_precedes_lock(&classInitLock, &runtimeLock); #endif #if __OBJC2__ // Runtime operations may occur inside SideTable locks // (such as storeWeak calling getMethodImplementation) SideTableLocksPrecedeLock(&runtimeLock); SideTableLocksPrecedeLock(&classInitLock); // Some operations may occur inside runtimeLock. lockdebug_lock_precedes_lock(&runtimeLock, &selLock); lockdebug_lock_precedes_lock(&runtimeLock, &cacheUpdateLock); lockdebug_lock_precedes_lock(&runtimeLock, &DemangleCacheLock); #else // Runtime operations may occur inside SideTable locks // (such as storeWeak calling getMethodImplementation) SideTableLocksPrecedeLock(&methodListLock); SideTableLocksPrecedeLock(&classInitLock); // Method lookup and fixup. lockdebug_lock_precedes_lock(&methodListLock, &classLock); lockdebug_lock_precedes_lock(&methodListLock, &selLock); lockdebug_lock_precedes_lock(&methodListLock, &cacheUpdateLock); lockdebug_lock_precedes_lock(&methodListLock, &impLock); lockdebug_lock_precedes_lock(&classLock, &selLock); lockdebug_lock_precedes_lock(&classLock, &cacheUpdateLock); #endif // Striped locks use address order internally. SideTableDefineLockOrder(); PropertyLocks.defineLockOrder(); StructLocks.defineLockOrder(); CppObjectLocks.defineLockOrder(); } // LOCKDEBUG #endif static bool ForkIsMultithreaded; void _objc_atfork_prepare() { // Save threaded-ness for the child's use. ForkIsMultithreaded = pthread_is_threaded_np(); lockdebug_assert_no_locks_locked(); lockdebug_setInForkPrepare(true); loadMethodLock.lock(); PropertyLocks.lockAll(); CppObjectLocks.lockAll(); AssociationsManagerLock.lock(); SideTableLockAll(); classInitLock.enter(); #if __OBJC2__ runtimeLock.lock(); DemangleCacheLock.lock(); #else methodListLock.lock(); classLock.lock(); NXUniqueStringLock.lock(); impLock.lock(); #endif selLock.lock(); cacheUpdateLock.lock(); objcMsgLogLock.lock(); AltHandlerDebugLock.lock(); StructLocks.lockAll(); crashlog_lock.lock(); lockdebug_assert_all_locks_locked(); lockdebug_setInForkPrepare(false); } void _objc_atfork_parent() { lockdebug_assert_all_locks_locked(); CppObjectLocks.unlockAll(); StructLocks.unlockAll(); PropertyLocks.unlockAll(); AssociationsManagerLock.unlock(); AltHandlerDebugLock.unlock(); objcMsgLogLock.unlock(); crashlog_lock.unlock(); loadMethodLock.unlock(); cacheUpdateLock.unlock(); selLock.unlock(); SideTableUnlockAll(); #if __OBJC2__ DemangleCacheLock.unlock(); runtimeLock.unlock(); #else impLock.unlock(); NXUniqueStringLock.unlock(); methodListLock.unlock(); classLock.unlock(); #endif classInitLock.leave(); lockdebug_assert_no_locks_locked(); } void _objc_atfork_child() { // Turn on +initialize fork safety enforcement if applicable. if (ForkIsMultithreaded && !DisableInitializeForkSafety) { MultithreadedForkChild = true; } lockdebug_assert_all_locks_locked(); CppObjectLocks.forceResetAll(); StructLocks.forceResetAll(); PropertyLocks.forceResetAll(); AssociationsManagerLock.forceReset(); AltHandlerDebugLock.forceReset(); objcMsgLogLock.forceReset(); crashlog_lock.forceReset(); loadMethodLock.forceReset(); cacheUpdateLock.forceReset(); selLock.forceReset(); SideTableForceResetAll(); #if __OBJC2__ DemangleCacheLock.forceReset(); runtimeLock.forceReset(); #else impLock.forceReset(); NXUniqueStringLock.forceReset(); methodListLock.forceReset(); classLock.forceReset(); #endif classInitLock.forceReset(); lockdebug_assert_no_locks_locked(); } /*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time **********************************************************************/ void _objc_init(void) { static bool initialized = false; if (initialized) return; initialized = true; // fixme defer initialization until an objc-using image is found? environ_init(); tls_init(); static_init(); lock_init(); exception_init(); _dyld_objc_notify_register(&map_images, load_images, unmap_image); } /*********************************************************************** * _headerForAddress. * addr can be a class or a category **********************************************************************/ static const header_info *_headerForAddress(void *addr) { #if __OBJC2__ const char *segnames[] = { "__DATA", "__DATA_CONST", "__DATA_DIRTY" }; #else const char *segnames[] = { "__OBJC" }; #endif header_info *hi; for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { for (size_t i = 0; i < sizeof(segnames)/sizeof(segnames[0]); i++) { unsigned long seg_size; uint8_t *seg = getsegmentdata(hi->mhdr(), segnames[i], &seg_size); if (!seg) continue; // Is the class in this header? if ((uint8_t *)addr >= seg && (uint8_t *)addr < seg + seg_size) { return hi; } } } // Not found return 0; } /*********************************************************************** * _headerForClass * Return the image header containing this class, or NULL. * Returns NULL on runtime-constructed classes, and the NSCF classes. **********************************************************************/ const header_info *_headerForClass(Class cls) { return _headerForAddress(cls); } /********************************************************************** * secure_open * Securely open a file from a world-writable directory (like /tmp) * If the file does not exist, it will be atomically created with mode 0600 * If the file exists, it must be, and remain after opening: * 1. a regular file (in particular, not a symlink) * 2. owned by euid * 3. permissions 0600 * 4. link count == 1 * Returns a file descriptor or -1. Errno may or may not be set on error. **********************************************************************/ int secure_open(const char *filename, int flags, uid_t euid) { struct stat fs, ls; int fd = -1; bool truncate = NO; bool create = NO; if (flags & O_TRUNC) { // Don't truncate the file until after it is open and verified. truncate = YES; flags &= ~O_TRUNC; } if (flags & O_CREAT) { // Don't create except when we're ready for it create = YES; flags &= ~O_CREAT; flags &= ~O_EXCL; } if (lstat(filename, &ls) < 0) { if (errno == ENOENT && create) { // No such file - create it fd = open(filename, flags | O_CREAT | O_EXCL, 0600); if (fd >= 0) { // File was created successfully. // New file does not need to be truncated. return fd; } else { // File creation failed. return -1; } } else { // lstat failed, or user doesn't want to create the file return -1; } } else { // lstat succeeded - verify attributes and open if (S_ISREG(ls.st_mode) && // regular file? ls.st_nlink == 1 && // link count == 1? ls.st_uid == euid && // owned by euid? (ls.st_mode & ALLPERMS) == (S_IRUSR | S_IWUSR)) // mode 0600? { // Attributes look ok - open it and check attributes again fd = open(filename, flags, 0000); if (fd >= 0) { // File is open - double-check attributes if (0 == fstat(fd, &fs) && fs.st_nlink == ls.st_nlink && // link count == 1? fs.st_uid == ls.st_uid && // owned by euid? fs.st_mode == ls.st_mode && // regular file, 0600? fs.st_ino == ls.st_ino && // same inode as before? fs.st_dev == ls.st_dev) // same device as before? { // File is open and OK if (truncate) ftruncate(fd, 0); return fd; } else { // Opened file looks funny - close it close(fd); return -1; } } else { // File didn't open return -1; } } else { // Unopened file looks funny - don't open it return -1; } } } #if TARGET_OS_IPHONE const char *__crashreporter_info__ = NULL; const char *CRSetCrashLogMessage(const char *msg) { __crashreporter_info__ = msg; return msg; } const char *CRGetCrashLogMessage(void) { return __crashreporter_info__; } #endif // TARGET_OS_MAC #else #error unknown OS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-private.h ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc-private.h * Copyright 1988-1996, NeXT Software, Inc. */ #ifndef _OBJC_PRIVATE_H_ #define _OBJC_PRIVATE_H_ #include "objc-config.h" /* Isolate ourselves from the definitions of id and Class in the compiler * and public headers. */ #ifdef _OBJC_OBJC_H_ #error include objc-private.h before other headers #endif #define OBJC_TYPES_DEFINED 1 #undef OBJC_OLD_DISPATCH_PROTOTYPES #define OBJC_OLD_DISPATCH_PROTOTYPES 0 #include // for nullptr_t #include #include struct objc_class; struct objc_object; typedef struct objc_class *Class; typedef struct objc_object *id; namespace { struct SideTable; }; #include "isa.h" union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; #endif }; struct objc_object { private: isa_t isa; public: // ISA() assumes this is NOT a tagged pointer object Class ISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); // initIsa() should be used to init the isa of new objects only. // If this object already has an isa, use changeIsa() for correctness. // initInstanceIsa(): objects with no custom RR/AWZ // initClassIsa(): class objects // initProtocolIsa(): protocol objects // initIsa(): other objects void initIsa(Class cls /*nonpointer=false*/); void initClassIsa(Class cls /*nonpointer=maybe*/); void initProtocolIsa(Class cls /*nonpointer=maybe*/); void initInstanceIsa(Class cls, bool hasCxxDtor); // changeIsa() should be used to change the isa of existing objects. // If this is a new object, use initIsa() for performance. Class changeIsa(Class newCls); bool hasNonpointerIsa(); bool isTaggedPointer(); bool isBasicTaggedPointer(); bool isExtTaggedPointer(); bool isClass(); // object may have associated objects? bool hasAssociatedObjects(); void setHasAssociatedObjects(); // object may be weakly referenced? bool isWeaklyReferenced(); void setWeaklyReferenced_nolock(); // object may have -.cxx_destruct implementation? bool hasCxxDtor(); // Optimized calls to retain/release methods id retain(); void release(); id autorelease(); // Implementations of retain/release methods id rootRetain(); bool rootRelease(); id rootAutorelease(); bool rootTryRetain(); bool rootReleaseShouldDealloc(); uintptr_t rootRetainCount(); // Implementation of dealloc methods bool rootIsDeallocating(); void clearDeallocating(); void rootDealloc(); private: void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor); // Slow paths for inline control id rootAutorelease2(); bool overrelease_error(); #if SUPPORT_NONPOINTER_ISA // Unified retain count manipulation for nonpointer isa id rootRetain(bool tryRetain, bool handleOverflow); bool rootRelease(bool performDealloc, bool handleUnderflow); id rootRetain_overflow(bool tryRetain); bool rootRelease_underflow(bool performDealloc); void clearDeallocating_slow(); // Side table retain count overflow for nonpointer isa void sidetable_lock(); void sidetable_unlock(); void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced); bool sidetable_addExtraRC_nolock(size_t delta_rc); size_t sidetable_subExtraRC_nolock(size_t delta_rc); size_t sidetable_getExtraRC_nolock(); #endif // Side-table-only retain count bool sidetable_isDeallocating(); void sidetable_clearDeallocating(); bool sidetable_isWeaklyReferenced(); void sidetable_setWeaklyReferenced_nolock(); id sidetable_retain(); id sidetable_retain_slow(SideTable& table); uintptr_t sidetable_release(bool performDealloc = true); uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true); bool sidetable_tryRetain(); uintptr_t sidetable_retainCount(); #if DEBUG bool sidetable_present(); #endif }; #if __OBJC2__ typedef struct method_t *Method; typedef struct ivar_t *Ivar; typedef struct category_t *Category; typedef struct property_t *objc_property_t; #else typedef struct old_method *Method; typedef struct old_ivar *Ivar; typedef struct old_category *Category; typedef struct old_property *objc_property_t; #endif // Public headers #include "objc.h" #include "runtime.h" #include "objc-os.h" #include "objc-abi.h" #include "objc-api.h" #include "objc-config.h" #include "objc-internal.h" #include "maptable.h" #include "hashtable2.h" /* Do not include message.h here. */ /* #include "message.h" */ #define __APPLE_API_PRIVATE #include "objc-gdb.h" #undef __APPLE_API_PRIVATE // Private headers #include "objc-ptrauth.h" #if __OBJC2__ #include "objc-runtime-new.h" #else #include "objc-runtime-old.h" #endif #include "objc-references.h" #include "objc-initialize.h" #include "objc-loadmethod.h" #if SUPPORT_PREOPT && __cplusplus #include using objc_selopt_t = const objc_opt::objc_selopt_t; #else struct objc_selopt_t; #endif #define STRINGIFY(x) #x #define STRINGIFY2(x) STRINGIFY(x) __BEGIN_DECLS struct header_info; // Split out the rw data from header info. For now put it in a huge array // that more than exceeds the space needed. In future we'll just allocate // this in the shared cache builder. typedef struct header_info_rw { bool getLoaded() const { return isLoaded; } void setLoaded(bool v) { isLoaded = v ? 1: 0; } bool getAllClassesRealized() const { return allClassesRealized; } void setAllClassesRealized(bool v) { allClassesRealized = v ? 1: 0; } header_info *getNext() const { return (header_info *)(next << 2); } void setNext(header_info *v) { next = ((uintptr_t)v) >> 2; } private: #ifdef __LP64__ uintptr_t isLoaded : 1; uintptr_t allClassesRealized : 1; uintptr_t next : 62; #else uintptr_t isLoaded : 1; uintptr_t allClassesRealized : 1; uintptr_t next : 30; #endif } header_info_rw; struct header_info_rw* getPreoptimizedHeaderRW(const struct header_info *const hdr); typedef struct header_info { private: // Note, this is no longer a pointer, but instead an offset to a pointer // from this location. intptr_t mhdr_offset; // Note, this is no longer a pointer, but instead an offset to a pointer // from this location. intptr_t info_offset; // Do not add fields without editing ObjCModernAbstraction.hpp public: header_info_rw *getHeaderInfoRW() { header_info_rw *preopt = isPreoptimized() ? getPreoptimizedHeaderRW(this) : nil; if (preopt) return preopt; else return &rw_data[0]; } const headerType *mhdr() const { return (const headerType *)(((intptr_t)&mhdr_offset) + mhdr_offset); } void setmhdr(const headerType *mhdr) { mhdr_offset = (intptr_t)mhdr - (intptr_t)&mhdr_offset; } const objc_image_info *info() const { return (const objc_image_info *)(((intptr_t)&info_offset) + info_offset); } void setinfo(const objc_image_info *info) { info_offset = (intptr_t)info - (intptr_t)&info_offset; } bool isLoaded() { return getHeaderInfoRW()->getLoaded(); } void setLoaded(bool v) { getHeaderInfoRW()->setLoaded(v); } bool areAllClassesRealized() { return getHeaderInfoRW()->getAllClassesRealized(); } void setAllClassesRealized(bool v) { getHeaderInfoRW()->setAllClassesRealized(v); } header_info *getNext() { return getHeaderInfoRW()->getNext(); } void setNext(header_info *v) { getHeaderInfoRW()->setNext(v); } bool isBundle() { return mhdr()->filetype == MH_BUNDLE; } const char *fname() const { return dyld_image_path_containing_address(mhdr()); } bool isPreoptimized() const; #if !__OBJC2__ struct old_protocol **proto_refs; struct objc_module *mod_ptr; size_t mod_count; # if TARGET_OS_WIN32 struct objc_module **modules; size_t moduleCount; struct old_protocol **protocols; size_t protocolCount; void *imageinfo; size_t imageinfoBytes; SEL *selrefs; size_t selrefCount; struct objc_class **clsrefs; size_t clsrefCount; TCHAR *moduleName; # endif #endif private: // Images in the shared cache will have an empty array here while those // allocated at run time will allocate a single entry. header_info_rw rw_data[]; } header_info; extern header_info *FirstHeader; extern header_info *LastHeader; extern int HeaderCount; extern void appendHeader(header_info *hi); extern void removeHeader(header_info *hi); extern objc_image_info *_getObjcImageInfo(const headerType *head, size_t *size); extern bool _hasObjcContents(const header_info *hi); // Mach-O segment and section names are 16 bytes and may be un-terminated. static inline bool segnameEquals(const char *lhs, const char *rhs) { return 0 == strncmp(lhs, rhs, 16); } static inline bool segnameStartsWith(const char *segname, const char *prefix) { return 0 == strncmp(segname, prefix, strlen(prefix)); } static inline bool sectnameEquals(const char *lhs, const char *rhs) { return segnameEquals(lhs, rhs); } static inline bool sectnameStartsWith(const char *sectname, const char *prefix){ return segnameStartsWith(sectname, prefix); } /* selectors */ extern void sel_init(size_t selrefCount); extern SEL sel_registerNameNoLock(const char *str, bool copy); extern SEL SEL_load; extern SEL SEL_initialize; extern SEL SEL_resolveClassMethod; extern SEL SEL_resolveInstanceMethod; extern SEL SEL_cxx_construct; extern SEL SEL_cxx_destruct; extern SEL SEL_retain; extern SEL SEL_release; extern SEL SEL_autorelease; extern SEL SEL_retainCount; extern SEL SEL_alloc; extern SEL SEL_allocWithZone; extern SEL SEL_dealloc; extern SEL SEL_copy; extern SEL SEL_new; extern SEL SEL_forwardInvocation; extern SEL SEL_tryRetain; extern SEL SEL_isDeallocating; extern SEL SEL_retainWeakReference; extern SEL SEL_allowsWeakReference; /* preoptimization */ extern void preopt_init(void); extern void disableSharedCacheOptimizations(void); extern bool isPreoptimized(void); extern bool noMissingWeakSuperclasses(void); extern header_info *preoptimizedHinfoForHeader(const headerType *mhdr); extern objc_selopt_t *preoptimizedSelectors(void); extern Protocol *getPreoptimizedProtocol(const char *name); extern unsigned getPreoptimizedClassUnreasonableCount(); extern Class getPreoptimizedClass(const char *name); extern Class* copyPreoptimizedClasses(const char *name, int *outCount); extern bool sharedRegionContains(const void *ptr); extern Class _calloc_class(size_t size); /* method lookup */ extern IMP lookUpImpOrNil(Class, SEL, id obj, bool initialize, bool cache, bool resolver); extern IMP lookUpImpOrForward(Class, SEL, id obj, bool initialize, bool cache, bool resolver); extern IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel); extern bool class_respondsToSelector_inst(Class cls, SEL sel, id inst); extern bool objcMsgLogEnabled; extern bool logMessageSend(bool isClassMethod, const char *objectsClass, const char *implementingClass, SEL selector); /* message dispatcher */ extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class); #if !OBJC_OLD_DISPATCH_PROTOTYPES extern void _objc_msgForward_impcache(void); #else extern id _objc_msgForward_impcache(id, SEL, ...); #endif /* errors */ extern void __objc_error(id, const char *, ...) __attribute__((format (printf, 2, 3), noreturn)); extern void _objc_inform(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern void _objc_inform_on_crash(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern void _objc_inform_now_and_on_crash(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern void _objc_inform_deprecated(const char *oldname, const char *newname) __attribute__((noinline)); extern void inform_duplicate(const char *name, Class oldCls, Class cls); /* magic */ extern Class _objc_getFreedObjectClass (void); /* map table additions */ extern void *NXMapKeyCopyingInsert(NXMapTable *table, const void *key, const void *value); extern void *NXMapKeyFreeingRemove(NXMapTable *table, const void *key); /* hash table additions */ extern unsigned _NXHashCapacity(NXHashTable *table); extern void _NXHashRehashToCapacity(NXHashTable *table, unsigned newCapacity); /* property attribute parsing */ extern const char *copyPropertyAttributeString(const objc_property_attribute_t *attrs, unsigned int count); extern objc_property_attribute_t *copyPropertyAttributeList(const char *attrs, unsigned int *outCount); extern char *copyPropertyAttributeValue(const char *attrs, const char *name); /* locking */ extern void lock_init(void); class monitor_locker_t : nocopy_t { monitor_t& lock; public: monitor_locker_t(monitor_t& newLock) : lock(newLock) { lock.enter(); } ~monitor_locker_t() { lock.leave(); } }; class recursive_mutex_locker_t : nocopy_t { recursive_mutex_t& lock; public: recursive_mutex_locker_t(recursive_mutex_t& newLock) : lock(newLock) { lock.lock(); } ~recursive_mutex_locker_t() { lock.unlock(); } }; /* Exceptions */ struct alt_handler_list; extern void exception_init(void); extern void _destroyAltHandlerList(struct alt_handler_list *list); /* Class change notifications (gdb only for now) */ #define OBJC_CLASS_ADDED (1<<0) #define OBJC_CLASS_REMOVED (1<<1) #define OBJC_CLASS_IVARS_CHANGED (1<<2) #define OBJC_CLASS_METHODS_CHANGED (1<<3) extern void gdb_objc_class_changed(Class cls, unsigned long changes, const char *classname) __attribute__((noinline)); // Settings from environment variables #define OPTION(var, env, help) extern bool var; #include "objc-env.h" #undef OPTION extern void environ_init(void); extern void logReplacedMethod(const char *className, SEL s, bool isMeta, const char *catName, IMP oldImp, IMP newImp); // objc per-thread storage typedef struct { struct _objc_initializing_classes *initializingClasses; // for +initialize struct SyncCache *syncCache; // for @synchronize struct alt_handler_list *handlerList; // for exception alt handlers char *printableNames[4]; // temporary demangled names for logging // If you add new fields here, don't forget to update // _objc_pthread_destroyspecific() } _objc_pthread_data; extern _objc_pthread_data *_objc_fetch_pthread_data(bool create); extern void tls_init(void); // encoding.h extern unsigned int encoding_getNumberOfArguments(const char *typedesc); extern unsigned int encoding_getSizeOfArguments(const char *typedesc); extern unsigned int encoding_getArgumentInfo(const char *typedesc, unsigned int arg, const char **type, int *offset); extern void encoding_getReturnType(const char *t, char *dst, size_t dst_len); extern char * encoding_copyReturnType(const char *t); extern void encoding_getArgumentType(const char *t, unsigned int index, char *dst, size_t dst_len); extern char *encoding_copyArgumentType(const char *t, unsigned int index); // sync.h extern void _destroySyncCache(struct SyncCache *cache); // arr extern void arr_init(void); extern id objc_autoreleaseReturnValue(id obj); // block trampolines extern IMP _imp_implementationWithBlockNoCopy(id block); // layout.h typedef struct { uint8_t *bits; size_t bitCount; size_t bitsAllocated; bool weak; } layout_bitmap; extern layout_bitmap layout_bitmap_create(const unsigned char *layout_string, size_t layoutStringInstanceSize, size_t instanceSize, bool weak); extern layout_bitmap layout_bitmap_create_empty(size_t instanceSize, bool weak); extern void layout_bitmap_free(layout_bitmap bits); extern const unsigned char *layout_string_create(layout_bitmap bits); extern void layout_bitmap_set_ivar(layout_bitmap bits, const char *type, size_t offset); extern void layout_bitmap_grow(layout_bitmap *bits, size_t newCount); extern void layout_bitmap_slide(layout_bitmap *bits, size_t oldPos, size_t newPos); extern void layout_bitmap_slide_anywhere(layout_bitmap *bits, size_t oldPos, size_t newPos); extern bool layout_bitmap_splat(layout_bitmap dst, layout_bitmap src, size_t oldSrcInstanceSize); extern bool layout_bitmap_or(layout_bitmap dst, layout_bitmap src, const char *msg); extern bool layout_bitmap_clear(layout_bitmap dst, layout_bitmap src, const char *msg); extern void layout_bitmap_print(layout_bitmap bits); // fixme runtime extern bool MultithreadedForkChild; extern id objc_noop_imp(id self, SEL _cmd); extern Class look_up_class(const char *aClassName, bool includeUnconnected, bool includeClassHandler); extern "C" void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]); extern void map_images_nolock(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]); extern void load_images(const char *path, const struct mach_header *mh); extern void unmap_image(const char *path, const struct mach_header *mh); extern void unmap_image_nolock(const struct mach_header *mh); extern void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClass); extern void _unload_image(header_info *hi); extern const header_info *_headerForClass(Class cls); extern Class _class_remap(Class cls); extern Class _class_getNonMetaClass(Class cls, id obj); extern Ivar _class_getVariable(Class cls, const char *name); extern unsigned _class_createInstancesFromZone(Class cls, size_t extraBytes, void *zone, id *results, unsigned num_requested); extern id _objc_constructOrFree(id bytes, Class cls); extern const char *_category_getName(Category cat); extern const char *_category_getClassName(Category cat); extern Class _category_getClass(Category cat); extern IMP _category_getLoadMethod(Category cat); extern id object_cxxConstructFromClass(id obj, Class cls); extern void object_cxxDestruct(id obj); extern void _class_resolveMethod(Class cls, SEL sel, id inst); extern void fixupCopiedIvars(id newObject, id oldObject); extern Class _class_getClassForIvar(Class cls, Ivar ivar); #define OBJC_WARN_DEPRECATED \ do { \ static int warned = 0; \ if (!warned) { \ warned = 1; \ _objc_inform_deprecated(__FUNCTION__, NULL); \ } \ } while (0) \ __END_DECLS #ifndef STATIC_ASSERT # define STATIC_ASSERT(x) _STATIC_ASSERT2(x, __LINE__) # define _STATIC_ASSERT2(x, line) _STATIC_ASSERT3(x, line) # define _STATIC_ASSERT3(x, line) \ typedef struct { \ int _static_assert[(x) ? 0 : -1]; \ } _static_assert_ ## line __attribute__((unavailable)) #endif #define countof(arr) (sizeof(arr) / sizeof((arr)[0])) static __inline uint32_t _objc_strhash(const char *s) { uint32_t hash = 0; for (;;) { int a = *s++; if (0 == a) break; hash += (hash << 8) + a; } return hash; } #if __cplusplus template static inline T log2u(T x) { return (x<2) ? 0 : log2u(x>>1)+1; } template static inline T exp2u(T x) { return (1 << x); } template static T exp2m1u(T x) { return (1 << x) - 1; } #endif // Misalignment-safe integer types __attribute__((aligned(1))) typedef uintptr_t unaligned_uintptr_t; __attribute__((aligned(1))) typedef intptr_t unaligned_intptr_t; __attribute__((aligned(1))) typedef uint64_t unaligned_uint64_t; __attribute__((aligned(1))) typedef int64_t unaligned_int64_t; __attribute__((aligned(1))) typedef uint32_t unaligned_uint32_t; __attribute__((aligned(1))) typedef int32_t unaligned_int32_t; __attribute__((aligned(1))) typedef uint16_t unaligned_uint16_t; __attribute__((aligned(1))) typedef int16_t unaligned_int16_t; // Global operator new and delete. We must not use any app overrides. // This ALSO REQUIRES each of these be in libobjc's unexported symbol list. #if __cplusplus #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winline-new-delete" #include inline void* operator new(std::size_t size) throw (std::bad_alloc) { return malloc(size); } inline void* operator new[](std::size_t size) throw (std::bad_alloc) { return malloc(size); } inline void* operator new(std::size_t size, const std::nothrow_t&) throw() { return malloc(size); } inline void* operator new[](std::size_t size, const std::nothrow_t&) throw() { return malloc(size); } inline void operator delete(void* p) throw() { free(p); } inline void operator delete[](void* p) throw() { free(p); } inline void operator delete(void* p, const std::nothrow_t&) throw() { free(p); } inline void operator delete[](void* p, const std::nothrow_t&) throw() { free(p); } #pragma clang diagnostic pop #endif class TimeLogger { uint64_t mStart; bool mRecord; public: TimeLogger(bool record = true) : mStart(nanoseconds()) , mRecord(record) { } void log(const char *msg) { if (mRecord) { uint64_t end = nanoseconds(); _objc_inform("%.2f ms: %s", (end - mStart) / 1000000.0, msg); mStart = nanoseconds(); } } }; enum { CacheLineSize = 64 }; // StripedMap is a map of void* -> T, sized appropriately // for cache-friendly lock striping. // For example, this may be used as StripedMap // or as StripedMap where SomeStruct stores a spin lock. template class StripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif struct PaddedT { T value alignas(CacheLineSize); }; PaddedT array[StripeCount]; static unsigned int indexForPointer(const void *p) { uintptr_t addr = reinterpret_cast(p); return ((addr >> 4) ^ (addr >> 9)) % StripeCount; } public: T& operator[] (const void *p) { return array[indexForPointer(p)].value; } const T& operator[] (const void *p) const { return const_cast>(this)[p]; } // Shortcuts for StripedMaps of locks. void lockAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.lock(); } } void unlockAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.unlock(); } } void forceResetAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.forceReset(); } } void defineLockOrder() { for (unsigned int i = 1; i < StripeCount; i++) { lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value); } } void precedeLock(const void *newlock) { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock); } void succeedLock(const void *oldlock) { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(oldlock, &array[0].value); } const void *getLock(int i) { if (i < StripeCount) return &array[i].value; else return nil; } #if DEBUG StripedMap() { // Verify alignment expectations. uintptr_t base = (uintptr_t)&array[0].value; uintptr_t delta = (uintptr_t)&array[1].value - base; assert(delta % CacheLineSize == 0); assert(base % CacheLineSize == 0); } #else constexpr StripedMap() {} #endif }; // DisguisedPtr acts like pointer type T*, except the // stored value is disguised to hide it from tools like `leaks`. // nil is disguised as itself so zero-filled memory works as expected, // which means 0x80..00 is also disguised as itself but we don't care. // Note that weak_entry_t knows about this encoding. template class DisguisedPtr { uintptr_t value; static uintptr_t disguise(T* ptr) { return -(uintptr_t)ptr; } static T* undisguise(uintptr_t val) { return (T*)-val; } public: DisguisedPtr() { } DisguisedPtr(T* ptr) : value(disguise(ptr)) { } DisguisedPtr(const DisguisedPtr& ptr) : value(ptr.value) { } DisguisedPtr& operator = (T* rhs) { value = disguise(rhs); return *this; } DisguisedPtr& operator = (const DisguisedPtr& rhs) { value = rhs.value; return *this; } operator T* () const { return undisguise(value); } T* operator -> () const { return undisguise(value); } T& operator * () const { return *undisguise(value); } T& operator [] (size_t i) const { return undisguise(value)[i]; } // pointer arithmetic operators omitted // because we don't currently use them anywhere }; // fixme type id is weird and not identical to objc_object* static inline bool operator == (DisguisedPtr lhs, id rhs) { return lhs == (objc_object *)rhs; } static inline bool operator != (DisguisedPtr lhs, id rhs) { return lhs != (objc_object *)rhs; } // Storage for a thread-safe chained hook function. // get() returns the value for calling. // set() installs a new function and returns the old one for chaining. // More precisely, set() writes the old value to a variable supplied by // the caller. get() and set() use appropriate barriers so that the // old value is safely written to the variable before the new value is // called to use it. // // T1: store to old variable; store-release to hook variable // T2: load-acquire from hook variable; call it; called hook loads old variable template class ChainedHookFunction { std::atomic hook{nil}; public: ChainedHookFunction(Fn f) : hook{f} { }; Fn get() { return hook.load(std::memory_order_acquire); } void set(Fn newValue, Fn *oldVariable) { Fn oldValue = hook.load(std::memory_order_relaxed); do { *oldVariable = oldValue; } while (!hook.compare_exchange_weak(oldValue, newValue, std::memory_order_release, std::memory_order_relaxed)); } }; // Pointer hash function. // This is not a terrific hash, but it is fast // and not outrageously flawed for our purposes. // Based on principles from http://locklessinc.com/articles/fast_hash/ // and evaluation ideas from http://floodyberry.com/noncryptohashzoo/ #if __LP64__ static inline uint32_t ptr_hash(uint64_t key) { key ^= key >> 4; key *= 0x8a970be7488fda55; key ^= __builtin_bswap64(key); return (uint32_t)key; } #else static inline uint32_t ptr_hash(uint32_t key) { key ^= key >> 4; key *= 0x5052acdb; key ^= __builtin_bswap32(key); return key; } #endif /* Higher-quality hash function. This is measurably slower in some workloads. #if __LP64__ uint32_t ptr_hash(uint64_t key) { key -= __builtin_bswap64(key); key *= 0x8a970be7488fda55; key ^= __builtin_bswap64(key); key *= 0x8a970be7488fda55; key ^= __builtin_bswap64(key); return (uint32_t)key; } #else static uint32_t ptr_hash(uint32_t key) { key -= __builtin_bswap32(key); key *= 0x5052acdb; key ^= __builtin_bswap32(key); key *= 0x5052acdb; key ^= __builtin_bswap32(key); return key; } #endif */ // Lock declarations #include "objc-locks.h" // Inlined parts of objc_object's implementation #include "objc-object.h" #endif /* _OBJC_PRIVATE_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-probes.d ================================================ provider objc_runtime { probe objc_exception_throw(void *id); probe objc_exception_rethrow(); }; ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-ptrauth.h ================================================ /* * Copyright (c) 2017 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_PTRAUTH_H_ #define _OBJC_PTRAUTH_H_ #include // On some architectures, method lists and method caches store signed IMPs. // StorageSignedFunctionPointer is declared by libclosure. #include // fixme simply include ptrauth.h once all build trains have it #if __has_include () #include #else #define ptrauth_strip(__value, __key) __value #define ptrauth_blend_discriminator(__pointer, __integer) ((uintptr_t)0) #define ptrauth_sign_constant(__value, __key, __data) __value #define ptrauth_sign_unauthenticated(__value, __key, __data) __value #define ptrauth_auth_and_resign(__value, __old_key, __old_data, __new_key, __new_data) __value #define ptrauth_auth_function(__value, __old_key, __old_data) __value #define ptrauth_auth_data(__value, __old_key, __old_data) __value #define ptrauth_string_discriminator(__string) ((int)0) #define ptrauth_sign_generic_data(__value, __data) ((ptrauth_generic_signature_t)0) #define __ptrauth_function_pointer #define __ptrauth_return_address #define __ptrauth_block_invocation_pointer #define __ptrauth_block_copy_helper #define __ptrauth_block_destroy_helper #define __ptrauth_block_byref_copy_helper #define __ptrauth_block_byref_destroy_helper #define __ptrauth_objc_method_list_imp #define __ptrauth_cxx_vtable_pointer #define __ptrauth_cxx_vtt_vtable_pointer #define __ptrauth_swift_heap_object_destructor #define __ptrauth_cxx_virtual_function_pointer(__declkey) #define __ptrauth_swift_function_pointer(__typekey) #define __ptrauth_swift_class_method_pointer(__declkey) #define __ptrauth_swift_protocol_witness_function_pointer(__declkey) #define __ptrauth_swift_value_witness_function_pointer(__key) #endif #if __has_feature(ptrauth_calls) // Method lists use process-independent signature for compatibility. // Method caches use process-dependent signature for extra protection. // (fixme not yet __ptrauth(...) because of `stp` inline asm in objc-cache.mm) using MethodListIMP = IMP __ptrauth_objc_method_list_imp; using MethodCacheIMP = StorageSignedFunctionPointer; #else using MethodListIMP = IMP; using MethodCacheIMP = IMP; #endif // _OBJC_PTRAUTH_H_ #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-references.h ================================================ /* * Copyright (c) 2008 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc-references.h */ #ifndef _OBJC_REFERENCES_H_ #define _OBJC_REFERENCES_H_ #include "objc-api.h" #include "objc-config.h" __BEGIN_DECLS extern void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy); extern id _object_get_associative_reference(id object, void *key); extern void _object_remove_assocations(id object); __END_DECLS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-references.mm ================================================ /* * Copyright (c) 2004-2007 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* Implementation of the weak / associative references for non-GC mode. */ #include "objc-private.h" #include #include #if _LIBCPP_VERSION # include #else # include using namespace tr1; #endif // wrap all the murky C++ details in a namespace to get them out of the way. namespace objc_references_support { struct DisguisedPointerEqual { bool operator()(uintptr_t p1, uintptr_t p2) const { return p1 == p2; } }; struct DisguisedPointerHash { uintptr_t operator()(uintptr_t k) const { // borrowed from CFSet.c #if __LP64__ uintptr_t a = 0x4368726973746F70ULL; uintptr_t b = 0x686572204B616E65ULL; #else uintptr_t a = 0x4B616E65UL; uintptr_t b = 0x4B616E65UL; #endif uintptr_t c = 1; a += k; #if __LP64__ a -= b; a -= c; a ^= (c >> 43); b -= c; b -= a; b ^= (a << 9); c -= a; c -= b; c ^= (b >> 8); a -= b; a -= c; a ^= (c >> 38); b -= c; b -= a; b ^= (a << 23); c -= a; c -= b; c ^= (b >> 5); a -= b; a -= c; a ^= (c >> 35); b -= c; b -= a; b ^= (a << 49); c -= a; c -= b; c ^= (b >> 11); a -= b; a -= c; a ^= (c >> 12); b -= c; b -= a; b ^= (a << 18); c -= a; c -= b; c ^= (b >> 22); #else a -= b; a -= c; a ^= (c >> 13); b -= c; b -= a; b ^= (a << 8); c -= a; c -= b; c ^= (b >> 13); a -= b; a -= c; a ^= (c >> 12); b -= c; b -= a; b ^= (a << 16); c -= a; c -= b; c ^= (b >> 5); a -= b; a -= c; a ^= (c >> 3); b -= c; b -= a; b ^= (a << 10); c -= a; c -= b; c ^= (b >> 15); #endif return c; } }; struct ObjectPointerLess { bool operator()(const void *p1, const void *p2) const { return p1 < p2; } }; struct ObjcPointerHash { uintptr_t operator()(void *p) const { return DisguisedPointerHash()(uintptr_t(p)); } }; // STL allocator that uses the runtime's internal allocator. template struct ObjcAllocator { typedef T value_type; typedef value_type* pointer; typedef const value_type *const_pointer; typedef value_type& reference; typedef const value_type& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; template struct rebind { typedef ObjcAllocator other; }; template ObjcAllocator(const ObjcAllocator&) {} ObjcAllocator() {} ObjcAllocator(const ObjcAllocator&) {} ~ObjcAllocator() {} pointer address(reference x) const { return &x; } const_pointer address(const_reference x) const { return x; } pointer allocate(size_type n, const_pointer = 0) { return static_cast(::malloc(n * sizeof(T))); } void deallocate(pointer p, size_type) { ::free(p); } size_type max_size() const { return static_cast(-1) / sizeof(T); } void construct(pointer p, const value_type& x) { new(p) value_type(x); } void destroy(pointer p) { p->~value_type(); } void operator=(const ObjcAllocator&); }; template<> struct ObjcAllocator { typedef void value_type; typedef void* pointer; typedef const void *const_pointer; template struct rebind { typedef ObjcAllocator other; }; }; typedef uintptr_t disguised_ptr_t; inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); } inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); } class ObjcAssociation { uintptr_t _policy; id _value; public: ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} ObjcAssociation() : _policy(0), _value(nil) {} uintptr_t policy() const { return _policy; } id value() const { return _value; } bool hasValue() { return _value != nil; } }; #if TARGET_OS_WIN32 typedef hash_map ObjectAssociationMap; typedef hash_map AssociationsHashMap; #else typedef ObjcAllocator > ObjectAssociationMapAllocator; class ObjectAssociationMap : public std::map { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } }; typedef ObjcAllocator > AssociationsHashMapAllocator; class AssociationsHashMap : public unordered_map { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } }; #endif } using namespace objc_references_support; // class AssociationsManager manages a lock / hash table singleton pair. // Allocating an instance acquires the lock, and calling its assocations() // method lazily allocates the hash table. spinlock_t AssociationsManagerLock; class AssociationsManager { // associative references: object pointer -> PtrPtrHashMap. static AssociationsHashMap *_map; public: AssociationsManager() { AssociationsManagerLock.lock(); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &associations() { if (_map == NULL) _map = new AssociationsHashMap(); return *_map; } }; AssociationsHashMap *AssociationsManager::_map = NULL; // expanded policy bits. enum { OBJC_ASSOCIATION_SETTER_ASSIGN = 0, OBJC_ASSOCIATION_SETTER_RETAIN = 1, OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below. OBJC_ASSOCIATION_GETTER_READ = (0 << 8), OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8), OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8) }; id _object_get_associative_reference(id object, void *key) { id value = nil; uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); disguised_ptr_t disguised_object = DISGUISE(object); AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { ObjcAssociation &entry = j->second; value = entry.value(); policy = entry.policy(); if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) { objc_retain(value); } } } } if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) { objc_autorelease(value); } return value; } static id acquireValue(id value, uintptr_t policy) { switch (policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: return objc_retain(value); case OBJC_ASSOCIATION_SETTER_COPY: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); } return value; } static void releaseValue(id value, uintptr_t policy) { if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) { return objc_release(value); } } struct ReleaseValue { void operator() (ObjcAssociation &association) { releaseValue(association.value(), association.policy()); } }; void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // retain the new value (if any) outside the lock. ObjcAssociation old_association(0, nil); id new_value = value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); disguised_ptr_t disguised_object = DISGUISE(object); if (new_value) { // break any existing association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; j->second = ObjcAssociation(policy, new_value); } else { (*refs)[key] = ObjcAssociation(policy, new_value); } } else { // create the new association (first time). ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else { // setting the association to nil breaks the association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } } } } // release the old value (outside of the lock). if (old_association.hasValue()) ReleaseValue()(old_association); } void _object_remove_assocations(id object) { vector< ObjcAssociation,ObjcAllocator > elements; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); if (associations.size() == 0) return; disguised_ptr_t disguised_object = DISGUISE(object); AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // copy all of the associations that need to be removed. ObjectAssociationMap *refs = i->second; for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) { elements.push_back(j->second); } // remove the secondary table. delete refs; associations.erase(i); } } // the calls to releaseValue() happen outside of the lock. for_each(elements.begin(), elements.end(), ReleaseValue()); } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-runtime-new.h ================================================ /* * Copyright (c) 2005-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_RUNTIME_NEW_H #define _OBJC_RUNTIME_NEW_H #if __LP64__ typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits #else typedef uint16_t mask_t; #endif typedef uintptr_t cache_key_t; struct swift_class_t; struct bucket_t { private: // IMP-first is better for arm64e ptrauth and no worse for arm64. // SEL-first is better for armv7* and i386 and x86_64. #if __arm64__ MethodCacheIMP _imp; cache_key_t _key; #else cache_key_t _key; MethodCacheIMP _imp; #endif public: inline cache_key_t key() const { return _key; } inline IMP imp() const { return (IMP)_imp; } inline void setKey(cache_key_t newKey) { _key = newKey; } inline void setImp(IMP newImp) { _imp = newImp; } void set(cache_key_t newKey, IMP newImp); }; struct cache_t { struct bucket_t *_buckets; mask_t _mask; mask_t _occupied; public: struct bucket_t *buckets(); mask_t mask(); mask_t occupied(); void incrementOccupied(); void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask); void initializeToEmpty(); mask_t capacity(); bool isConstantEmptyCache(); bool canBeFreed(); static size_t bytesForCapacity(uint32_t cap); static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap); void expand(); void reallocate(mask_t oldCapacity, mask_t newCapacity); struct bucket_t * find(cache_key_t key, id receiver); static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn)); }; // classref_t is unremapped class_t* typedef struct classref * classref_t; /*********************************************************************** * entsize_list_tt * Generic implementation of an array of non-fragile structs. * * Element is the struct type (e.g. method_t) * List is the specialization of entsize_list_tt (e.g. method_list_t) * FlagMask is used to stash extra bits in the entsize field * (e.g. method list fixup markers) **********************************************************************/ template struct entsize_list_tt { uint32_t entsizeAndFlags; uint32_t count; Element first; uint32_t entsize() const { return entsizeAndFlags & ~FlagMask; } uint32_t flags() const { return entsizeAndFlags & FlagMask; } Element& getOrEnd(uint32_t i) const { assert(i <= count); return *(Element *)((uint8_t *)&first + i*entsize()); } Element& get(uint32_t i) const { assert(i < count); return getOrEnd(i); } size_t byteSize() const { return byteSize(entsize(), count); } static size_t byteSize(uint32_t entsize, uint32_t count) { return sizeof(entsize_list_tt) + (count-1)*entsize; } List *duplicate() const { auto *dup = (List *)calloc(this->byteSize(), 1); dup->entsizeAndFlags = this->entsizeAndFlags; dup->count = this->count; std::copy(begin(), end(), dup->begin()); return dup; } struct iterator; const iterator begin() const { return iterator(*static_cast(this), 0); } iterator begin() { return iterator(*static_cast(this), 0); } const iterator end() const { return iterator(*static_cast(this), count); } iterator end() { return iterator(*static_cast(this), count); } struct iterator { uint32_t entsize; uint32_t index; // keeping track of this saves a divide in operator- Element* element; typedef std::random_access_iterator_tag iterator_category; typedef Element value_type; typedef ptrdiff_t difference_type; typedef Element* pointer; typedef Element& reference; iterator() { } iterator(const List& list, uint32_t start = 0) : entsize(list.entsize()) , index(start) , element(&list.getOrEnd(start)) { } const iterator& operator += (ptrdiff_t delta) { element = (Element*)((uint8_t *)element + delta*entsize); index += (int32_t)delta; return *this; } const iterator& operator -= (ptrdiff_t delta) { element = (Element*)((uint8_t *)element - delta*entsize); index -= (int32_t)delta; return *this; } const iterator operator + (ptrdiff_t delta) const { return iterator(*this) += delta; } const iterator operator - (ptrdiff_t delta) const { return iterator(*this) -= delta; } iterator& operator ++ () { *this += 1; return *this; } iterator& operator -- () { *this -= 1; return *this; } iterator operator ++ (int) { iterator result(*this); *this += 1; return result; } iterator operator -- (int) { iterator result(*this); *this -= 1; return result; } ptrdiff_t operator - (const iterator& rhs) const { return (ptrdiff_t)this->index - (ptrdiff_t)rhs.index; } Element& operator * () const { return *element; } Element* operator -> () const { return element; } operator Element& () const { return *element; } bool operator == (const iterator& rhs) const { return this->element == rhs.element; } bool operator != (const iterator& rhs) const { return this->element != rhs.element; } bool operator < (const iterator& rhs) const { return this->element < rhs.element; } bool operator > (const iterator& rhs) const { return this->element > rhs.element; } }; }; struct method_t { SEL name; const char *types; MethodListIMP imp; struct SortBySELAddress : public std::binary_function { bool operator() (const method_t& lhs, const method_t& rhs) { return lhs.name < rhs.name; } }; }; struct ivar_t { #if __x86_64__ // *offset was originally 64-bit on some x86_64 platforms. // We read and write only 32 bits of it. // Some metadata provides all 64 bits. This is harmless for unsigned // little-endian values. // Some code uses all 64 bits. class_addIvar() over-allocates the // offset for their benefit. #endif int32_t *offset; const char *name; const char *type; // alignment is sometimes -1; use alignment() instead uint32_t alignment_raw; uint32_t size; uint32_t alignment() const { if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT; return 1 << alignment_raw; } }; struct property_t { const char *name; const char *attributes; }; // Two bits of entsize are used for fixup markers. struct method_list_t : entsize_list_tt { bool isFixedUp() const; void setFixedUp(); uint32_t indexOfMethod(const method_t *meth) const { uint32_t i = (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize()); assert(i < count); return i; } }; struct ivar_list_t : entsize_list_tt { bool containsIvar(Ivar ivar) const { return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end()); } }; struct property_list_t : entsize_list_tt { }; typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped // Values for protocol_t->flags #define PROTOCOL_FIXED_UP_2 (1<<31) // must never be set by compiler #define PROTOCOL_FIXED_UP_1 (1<<30) // must never be set by compiler // Bits 0..15 are reserved for Swift's use. #define PROTOCOL_FIXED_UP_MASK (PROTOCOL_FIXED_UP_1 | PROTOCOL_FIXED_UP_2) struct protocol_t : objc_object { const char *mangledName; struct protocol_list_t *protocols; method_list_t *instanceMethods; method_list_t *classMethods; method_list_t *optionalInstanceMethods; method_list_t *optionalClassMethods; property_list_t *instanceProperties; uint32_t size; // sizeof(protocol_t) uint32_t flags; // Fields below this point are not always present on disk. const char **_extendedMethodTypes; const char *_demangledName; property_list_t *_classProperties; const char *demangledName(); const char *nameForLogging() { return demangledName(); } bool isFixedUp() const; void setFixedUp(); # define HAS_FIELD(f) (size >= offsetof(protocol_t, f) + sizeof(f)) bool hasExtendedMethodTypesField() const { return HAS_FIELD(_extendedMethodTypes); } bool hasDemangledNameField() const { return HAS_FIELD(_demangledName); } bool hasClassPropertiesField() const { return HAS_FIELD(_classProperties); } # undef HAS_FIELD const char **extendedMethodTypes() const { return hasExtendedMethodTypesField() ? _extendedMethodTypes : nil; } property_list_t *classProperties() const { return hasClassPropertiesField() ? _classProperties : nil; } }; struct protocol_list_t { // count is 64-bit by accident. uintptr_t count; protocol_ref_t list[0]; // variable-size size_t byteSize() const { return sizeof(*this) + count*sizeof(list[0]); } protocol_list_t *duplicate() const { return (protocol_list_t *)memdup(this, this->byteSize()); } typedef protocol_ref_t* iterator; typedef const protocol_ref_t* const_iterator; const_iterator begin() const { return list; } iterator begin() { return list; } const_iterator end() const { return list + count; } iterator end() { return list + count; } }; struct locstamped_category_t { category_t *cat; struct header_info *hi; }; struct locstamped_category_list_t { uint32_t count; #if __LP64__ uint32_t reserved; #endif locstamped_category_t list[0]; }; // class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags) // The extra bits are optimized for the retain/release and alloc/dealloc paths. // Values for class_ro_t->flags // These are emitted by the compiler and are part of the ABI. // Note: See CGObjCNonFragileABIMac::BuildClassRoTInitializer in clang // class is a metaclass #define RO_META (1<<0) // class is a root class #define RO_ROOT (1<<1) // class has .cxx_construct/destruct implementations #define RO_HAS_CXX_STRUCTORS (1<<2) // class has +load implementation // #define RO_HAS_LOAD_METHOD (1<<3) // class has visibility=hidden set #define RO_HIDDEN (1<<4) // class has attribute(objc_exception): OBJC_EHTYPE_$_ThisClass is non-weak #define RO_EXCEPTION (1<<5) // this bit is available for reassignment // #define RO_REUSE_ME (1<<6) // class compiled with ARC #define RO_IS_ARC (1<<7) // class has .cxx_destruct but no .cxx_construct (with RO_HAS_CXX_STRUCTORS) #define RO_HAS_CXX_DTOR_ONLY (1<<8) // class is not ARC but has ARC-style weak ivar layout #define RO_HAS_WEAK_WITHOUT_ARC (1<<9) // class is in an unloadable bundle - must never be set by compiler #define RO_FROM_BUNDLE (1<<29) // class is unrealized future class - must never be set by compiler #define RO_FUTURE (1<<30) // class is realized - must never be set by compiler #define RO_REALIZED (1<<31) // Values for class_rw_t->flags // These are not emitted by the compiler and are never used in class_ro_t. // Their presence should be considered in future ABI versions. // class_t->data is class_rw_t, not class_ro_t #define RW_REALIZED (1<<31) // class is unresolved future class #define RW_FUTURE (1<<30) // class is initialized #define RW_INITIALIZED (1<<29) // class is initializing #define RW_INITIALIZING (1<<28) // class_rw_t->ro is heap copy of class_ro_t #define RW_COPIED_RO (1<<27) // class allocated but not yet registered #define RW_CONSTRUCTING (1<<26) // class allocated and registered #define RW_CONSTRUCTED (1<<25) // available for use; was RW_FINALIZE_ON_MAIN_THREAD // #define RW_24 (1<<24) // class +load has been called #define RW_LOADED (1<<23) #if !SUPPORT_NONPOINTER_ISA // class instances may have associative references #define RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS (1<<22) #endif // class has instance-specific GC layout #define RW_HAS_INSTANCE_SPECIFIC_LAYOUT (1 << 21) // available for use // #define RW_20 (1<<20) // class has started realizing but not yet completed it #define RW_REALIZING (1<<19) // NOTE: MORE RW_ FLAGS DEFINED BELOW // Values for class_rw_t->flags or class_t->bits // These flags are optimized for retain/release and alloc/dealloc // 64-bit stores more of them in class_t->bits to reduce pointer indirection. #if !__LP64__ // class or superclass has .cxx_construct implementation #define RW_HAS_CXX_CTOR (1<<18) // class or superclass has .cxx_destruct implementation #define RW_HAS_CXX_DTOR (1<<17) // class or superclass has default alloc/allocWithZone: implementation // Note this is is stored in the metaclass. #define RW_HAS_DEFAULT_AWZ (1<<16) // class's instances requires raw isa #if SUPPORT_NONPOINTER_ISA #define RW_REQUIRES_RAW_ISA (1<<15) #endif // class or superclass has default retain/release/autorelease/retainCount/ // _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference #define RW_HAS_DEFAULT_RR (1<<14) // class is a Swift class from the pre-stable Swift ABI #define FAST_IS_SWIFT_LEGACY (1UL<<0) // class is a Swift class from the stable Swift ABI #define FAST_IS_SWIFT_STABLE (1UL<<1) // data pointer #define FAST_DATA_MASK 0xfffffffcUL #elif 1 // Leaks-compatible version that steals low bits only. // class or superclass has .cxx_construct implementation #define RW_HAS_CXX_CTOR (1<<18) // class or superclass has .cxx_destruct implementation #define RW_HAS_CXX_DTOR (1<<17) // class or superclass has default alloc/allocWithZone: implementation // Note this is is stored in the metaclass. #define RW_HAS_DEFAULT_AWZ (1<<16) // class's instances requires raw isa #define RW_REQUIRES_RAW_ISA (1<<15) // class is a Swift class from the pre-stable Swift ABI #define FAST_IS_SWIFT_LEGACY (1UL<<0) // class is a Swift class from the stable Swift ABI #define FAST_IS_SWIFT_STABLE (1UL<<1) // class or superclass has default retain/release/autorelease/retainCount/ // _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference #define FAST_HAS_DEFAULT_RR (1UL<<2) // data pointer #define FAST_DATA_MASK 0x00007ffffffffff8UL #else // Leaks-incompatible version that steals lots of bits. // class is a Swift class from the pre-stable Swift ABI #define FAST_IS_SWIFT_LEGACY (1UL<<0) // class is a Swift class from the stable Swift ABI #define FAST_IS_SWIFT_STABLE (1UL<<1) // summary bit for fast alloc path: !hasCxxCtor and // !instancesRequireRawIsa and instanceSize fits into shiftedSize #define FAST_ALLOC (1UL<<2) // data pointer #define FAST_DATA_MASK 0x00007ffffffffff8UL // class or superclass has .cxx_construct implementation #define FAST_HAS_CXX_CTOR (1UL<<47) // class or superclass has default alloc/allocWithZone: implementation // Note this is is stored in the metaclass. #define FAST_HAS_DEFAULT_AWZ (1UL<<48) // class or superclass has default retain/release/autorelease/retainCount/ // _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference #define FAST_HAS_DEFAULT_RR (1UL<<49) // class's instances requires raw isa // This bit is aligned with isa_t->hasCxxDtor to save an instruction. #define FAST_REQUIRES_RAW_ISA (1UL<<50) // class or superclass has .cxx_destruct implementation #define FAST_HAS_CXX_DTOR (1UL<<51) // instance size in units of 16 bytes // or 0 if the instance size is too big in this field // This field must be LAST #define FAST_SHIFTED_SIZE_SHIFT 52 // FAST_ALLOC means // FAST_HAS_CXX_CTOR is set // FAST_REQUIRES_RAW_ISA is not set // FAST_SHIFTED_SIZE is not zero // FAST_ALLOC does NOT check FAST_HAS_DEFAULT_AWZ because that // bit is stored on the metaclass. #define FAST_ALLOC_MASK (FAST_HAS_CXX_CTOR | FAST_REQUIRES_RAW_ISA) #define FAST_ALLOC_VALUE (0) #endif // The Swift ABI requires that these bits be defined like this on all platforms. static_assert(FAST_IS_SWIFT_LEGACY == 1, "resistance is futile"); static_assert(FAST_IS_SWIFT_STABLE == 2, "resistance is futile"); struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; method_list_t *baseMethods() const { return baseMethodList; } }; /*********************************************************************** * list_array_tt * Generic implementation for metadata that can be augmented by categories. * * Element is the underlying metadata type (e.g. method_t) * List is the metadata's list type (e.g. method_list_t) * * A list_array_tt has one of three values: * - empty * - a pointer to a single list * - an array of pointers to lists * * countLists/beginLists/endLists iterate the metadata lists * count/begin/end iterate the underlying metadata elements **********************************************************************/ template class list_array_tt { struct array_t { uint32_t count; List* lists[0]; static size_t byteSize(uint32_t count) { return sizeof(array_t) + count*sizeof(lists[0]); } size_t byteSize() { return byteSize(count); } }; protected: class iterator { List **lists; List **listsEnd; typename List::iterator m, mEnd; public: iterator(List **begin, List **end) : lists(begin), listsEnd(end) { if (begin != end) { m = (*begin)->begin(); mEnd = (*begin)->end(); } } const Element& operator * () const { return *m; } Element& operator * () { return *m; } bool operator != (const iterator& rhs) const { if (lists != rhs.lists) return true; if (lists == listsEnd) return false; // m is undefined if (m != rhs.m) return true; return false; } const iterator& operator ++ () { assert(m != mEnd); m++; if (m == mEnd) { assert(lists != listsEnd); lists++; if (lists != listsEnd) { m = (*lists)->begin(); mEnd = (*lists)->end(); } } return *this; } }; private: union { List* list; uintptr_t arrayAndFlag; }; bool hasArray() const { return arrayAndFlag & 1; } array_t *array() { return (array_t *)(arrayAndFlag & ~1); } void setArray(array_t *array) { arrayAndFlag = (uintptr_t)array | 1; } public: uint32_t count() { uint32_t result = 0; for (auto lists = beginLists(), end = endLists(); lists != end; ++lists) { result += (*lists)->count; } return result; } iterator begin() { return iterator(beginLists(), endLists()); } iterator end() { List **e = endLists(); return iterator(e, e); } uint32_t countLists() { if (hasArray()) { return array()->count; } else if (list) { return 1; } else { return 0; } } List** beginLists() { if (hasArray()) { return array()->lists; } else { return &list; } } List** endLists() { if (hasArray()) { return array()->lists + array()->count; } else if (list) { return &list + 1; } else { return &list; } } void attachLists(List* const * addedLists, uint32_t addedCount) { if (addedCount == 0) return; if (hasArray()) { // many lists -> many lists uint32_t oldCount = array()->count; uint32_t newCount = oldCount + addedCount; setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); array()->count = newCount; memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } else if (!list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; } else { // 1 list -> many lists List* oldList = list; uint32_t oldCount = oldList ? 1 : 0; uint32_t newCount = oldCount + addedCount; setArray((array_t *)malloc(array_t::byteSize(newCount))); array()->count = newCount; if (oldList) array()->lists[addedCount] = oldList; memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } } void tryFree() { if (hasArray()) { for (uint32_t i = 0; i < array()->count; i++) { try_free(array()->lists[i]); } try_free(array()); } else if (list) { try_free(list); } } template Result duplicate() { Result result; if (hasArray()) { array_t *a = array(); result.setArray((array_t *)memdup(a, a->byteSize())); for (uint32_t i = 0; i < a->count; i++) { result.array()->lists[i] = a->lists[i]->duplicate(); } } else if (list) { result.list = list->duplicate(); } else { result.list = nil; } return result; } }; class method_array_t : public list_array_tt { typedef list_array_tt Super; public: method_list_t **beginCategoryMethodLists() { return beginLists(); } method_list_t **endCategoryMethodLists(Class cls); method_array_t duplicate() { return Super::duplicate(); } }; class property_array_t : public list_array_tt { typedef list_array_tt Super; public: property_array_t duplicate() { return Super::duplicate(); } }; class protocol_array_t : public list_array_tt { typedef list_array_tt Super; public: protocol_array_t duplicate() { return Super::duplicate(); } }; struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; #if SUPPORT_INDEXED_ISA uint32_t index; #endif void setFlags(uint32_t set) { OSAtomicOr32Barrier(set, &flags); } void clearFlags(uint32_t clear) { OSAtomicXor32Barrier(clear, &flags); } // set and clear must not overlap void changeFlags(uint32_t set, uint32_t clear) { assert((set & clear) == 0); uint32_t oldf, newf; do { oldf = flags; newf = (oldf | set) & ~clear; } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags)); } }; struct class_data_bits_t { // Values are the FAST_ flags above. uintptr_t bits; private: bool getBit(uintptr_t bit) { return bits & bit; } #if FAST_ALLOC static uintptr_t updateFastAlloc(uintptr_t oldBits, uintptr_t change) { if (change & FAST_ALLOC_MASK) { if (((oldBits & FAST_ALLOC_MASK) == FAST_ALLOC_VALUE) && ((oldBits >> FAST_SHIFTED_SIZE_SHIFT) != 0)) { oldBits |= FAST_ALLOC; } else { oldBits &= ~FAST_ALLOC; } } return oldBits; } #else static uintptr_t updateFastAlloc(uintptr_t oldBits, uintptr_t change) { return oldBits; } #endif void setBits(uintptr_t set) { uintptr_t oldBits; uintptr_t newBits; do { oldBits = LoadExclusive(&bits); newBits = updateFastAlloc(oldBits | set, set); } while (!StoreReleaseExclusive(&bits, oldBits, newBits)); } void clearBits(uintptr_t clear) { uintptr_t oldBits; uintptr_t newBits; do { oldBits = LoadExclusive(&bits); newBits = updateFastAlloc(oldBits & ~clear, clear); } while (!StoreReleaseExclusive(&bits, oldBits, newBits)); } public: class_rw_t* data() { return (class_rw_t *)(bits & FAST_DATA_MASK); } void setData(class_rw_t *newData) { assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); // Set during realization or construction only. No locking needed. // Use a store-release fence because there may be concurrent // readers of data and data's contents. uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData; atomic_thread_fence(memory_order_release); bits = newBits; } #if FAST_HAS_DEFAULT_RR bool hasDefaultRR() { return getBit(FAST_HAS_DEFAULT_RR); } void setHasDefaultRR() { setBits(FAST_HAS_DEFAULT_RR); } void setHasCustomRR() { clearBits(FAST_HAS_DEFAULT_RR); } #else bool hasDefaultRR() { return data()->flags & RW_HAS_DEFAULT_RR; } void setHasDefaultRR() { data()->setFlags(RW_HAS_DEFAULT_RR); } void setHasCustomRR() { data()->clearFlags(RW_HAS_DEFAULT_RR); } #endif #if FAST_HAS_DEFAULT_AWZ bool hasDefaultAWZ() { return getBit(FAST_HAS_DEFAULT_AWZ); } void setHasDefaultAWZ() { setBits(FAST_HAS_DEFAULT_AWZ); } void setHasCustomAWZ() { clearBits(FAST_HAS_DEFAULT_AWZ); } #else bool hasDefaultAWZ() { return data()->flags & RW_HAS_DEFAULT_AWZ; } void setHasDefaultAWZ() { data()->setFlags(RW_HAS_DEFAULT_AWZ); } void setHasCustomAWZ() { data()->clearFlags(RW_HAS_DEFAULT_AWZ); } #endif #if FAST_HAS_CXX_CTOR bool hasCxxCtor() { return getBit(FAST_HAS_CXX_CTOR); } void setHasCxxCtor() { setBits(FAST_HAS_CXX_CTOR); } #else bool hasCxxCtor() { return data()->flags & RW_HAS_CXX_CTOR; } void setHasCxxCtor() { data()->setFlags(RW_HAS_CXX_CTOR); } #endif #if FAST_HAS_CXX_DTOR bool hasCxxDtor() { return getBit(FAST_HAS_CXX_DTOR); } void setHasCxxDtor() { setBits(FAST_HAS_CXX_DTOR); } #else bool hasCxxDtor() { return data()->flags & RW_HAS_CXX_DTOR; } void setHasCxxDtor() { data()->setFlags(RW_HAS_CXX_DTOR); } #endif #if FAST_REQUIRES_RAW_ISA bool instancesRequireRawIsa() { return getBit(FAST_REQUIRES_RAW_ISA); } void setInstancesRequireRawIsa() { setBits(FAST_REQUIRES_RAW_ISA); } #elif SUPPORT_NONPOINTER_ISA bool instancesRequireRawIsa() { return data()->flags & RW_REQUIRES_RAW_ISA; } void setInstancesRequireRawIsa() { data()->setFlags(RW_REQUIRES_RAW_ISA); } #else bool instancesRequireRawIsa() { return true; } void setInstancesRequireRawIsa() { // nothing } #endif #if FAST_ALLOC size_t fastInstanceSize() { assert(bits & FAST_ALLOC); return (bits >> FAST_SHIFTED_SIZE_SHIFT) * 16; } void setFastInstanceSize(size_t newSize) { // Set during realization or construction only. No locking needed. assert(data()->flags & RW_REALIZING); // Round up to 16-byte boundary, then divide to get 16-byte units newSize = ((newSize + 15) & ~15) / 16; uintptr_t newBits = newSize << FAST_SHIFTED_SIZE_SHIFT; if ((newBits >> FAST_SHIFTED_SIZE_SHIFT) == newSize) { int shift = WORD_BITS - FAST_SHIFTED_SIZE_SHIFT; uintptr_t oldBits = (bits << shift) >> shift; if ((oldBits & FAST_ALLOC_MASK) == FAST_ALLOC_VALUE) { newBits |= FAST_ALLOC; } bits = oldBits | newBits; } } bool canAllocFast() { return bits & FAST_ALLOC; } #else size_t fastInstanceSize() { abort(); } void setFastInstanceSize(size_t) { // nothing } bool canAllocFast() { return false; } #endif void setClassArrayIndex(unsigned Idx) { #if SUPPORT_INDEXED_ISA // 0 is unused as then we can rely on zero-initialisation from calloc. assert(Idx > 0); data()->index = Idx; #endif } unsigned classArrayIndex() { #if SUPPORT_INDEXED_ISA return data()->index; #else return 0; #endif } bool isAnySwift() { return isSwiftStable() || isSwiftLegacy(); } bool isSwiftStable() { return getBit(FAST_IS_SWIFT_STABLE); } void setIsSwiftStable() { setBits(FAST_IS_SWIFT_STABLE); } bool isSwiftLegacy() { return getBit(FAST_IS_SWIFT_LEGACY); } void setIsSwiftLegacy() { setBits(FAST_IS_SWIFT_LEGACY); } }; struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags class_rw_t *data() { return bits.data(); } void setData(class_rw_t *newData) { bits.setData(newData); } void setInfo(uint32_t set) { assert(isFuture() || isRealized()); data()->setFlags(set); } void clearInfo(uint32_t clear) { assert(isFuture() || isRealized()); data()->clearFlags(clear); } // set and clear must not overlap void changeInfo(uint32_t set, uint32_t clear) { assert(isFuture() || isRealized()); assert((set & clear) == 0); data()->changeFlags(set, clear); } bool hasCustomRR() { return ! bits.hasDefaultRR(); } void setHasDefaultRR() { assert(isInitializing()); bits.setHasDefaultRR(); } void setHasCustomRR(bool inherited = false); void printCustomRR(bool inherited); bool hasCustomAWZ() { return ! bits.hasDefaultAWZ(); } void setHasDefaultAWZ() { assert(isInitializing()); bits.setHasDefaultAWZ(); } void setHasCustomAWZ(bool inherited = false); void printCustomAWZ(bool inherited); bool instancesRequireRawIsa() { return bits.instancesRequireRawIsa(); } void setInstancesRequireRawIsa(bool inherited = false); void printInstancesRequireRawIsa(bool inherited); bool canAllocNonpointer() { assert(!isFuture()); return !instancesRequireRawIsa(); } bool canAllocFast() { assert(!isFuture()); return bits.canAllocFast(); } bool hasCxxCtor() { // addSubclass() propagates this flag from the superclass. assert(isRealized()); return bits.hasCxxCtor(); } void setHasCxxCtor() { bits.setHasCxxCtor(); } bool hasCxxDtor() { // addSubclass() propagates this flag from the superclass. assert(isRealized()); return bits.hasCxxDtor(); } void setHasCxxDtor() { bits.setHasCxxDtor(); } bool isSwiftStable() { return bits.isSwiftStable(); } bool isSwiftLegacy() { return bits.isSwiftLegacy(); } bool isAnySwift() { return bits.isAnySwift(); } // Return YES if the class's ivars are managed by ARC, // or the class is MRC but has ARC-style weak ivars. bool hasAutomaticIvars() { return data()->ro->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC); } // Return YES if the class's ivars are managed by ARC. bool isARC() { return data()->ro->flags & RO_IS_ARC; } #if SUPPORT_NONPOINTER_ISA // Tracked in non-pointer isas; not tracked otherwise #else bool instancesHaveAssociatedObjects() { // this may be an unrealized future class in the CF-bridged case assert(isFuture() || isRealized()); return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS; } void setInstancesHaveAssociatedObjects() { // this may be an unrealized future class in the CF-bridged case assert(isFuture() || isRealized()); setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS); } #endif bool shouldGrowCache() { return true; } void setShouldGrowCache(bool) { // fixme good or bad for memory use? } bool isInitializing() { return getMeta()->data()->flags & RW_INITIALIZING; } void setInitializing() { assert(!isMetaClass()); ISA()->setInfo(RW_INITIALIZING); } bool isInitialized() { return getMeta()->data()->flags & RW_INITIALIZED; } void setInitialized(); bool isLoadable() { assert(isRealized()); return true; // any class registered for +load is definitely loadable } IMP getLoadMethod(); // Locking: To prevent concurrent realization, hold runtimeLock. bool isRealized() { return data()->flags & RW_REALIZED; } // Returns true if this is an unrealized future class. // Locking: To prevent concurrent realization, hold runtimeLock. bool isFuture() { return data()->flags & RW_FUTURE; } bool isMetaClass() { assert(this); assert(isRealized()); return data()->ro->flags & RO_META; } // NOT identical to this->ISA when this is a metaclass Class getMeta() { if (isMetaClass()) return (Class)this; else return this->ISA(); } bool isRootClass() { return superclass == nil; } bool isRootMetaclass() { return ISA() == (Class)this; } const char *mangledName() { // fixme can't assert locks here assert(this); if (isRealized() || isFuture()) { return data()->ro->name; } else { return ((const class_ro_t *)data())->name; } } const char *demangledName(bool realize = false); const char *nameForLogging(); // May be unaligned depending on class's ivars. uint32_t unalignedInstanceStart() { assert(isRealized()); return data()->ro->instanceStart; } // Class's instance start rounded up to a pointer-size boundary. // This is used for ARC layout bitmaps. uint32_t alignedInstanceStart() { return word_align(unalignedInstanceStart()); } // May be unaligned depending on class's ivars. uint32_t unalignedInstanceSize() { assert(isRealized()); return data()->ro->instanceSize; } // Class's ivar size rounded up to a pointer-size boundary. uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; } void setInstanceSize(uint32_t newSize) { assert(isRealized()); if (newSize != data()->ro->instanceSize) { assert(data()->flags & RW_COPIED_RO); *const_cast(&data()->ro->instanceSize) = newSize; } bits.setFastInstanceSize(newSize); } void chooseClassArrayIndex(); void setClassArrayIndex(unsigned Idx) { bits.setClassArrayIndex(Idx); } unsigned classArrayIndex() { return bits.classArrayIndex(); } }; struct swift_class_t : objc_class { uint32_t flags; uint32_t instanceAddressOffset; uint32_t instanceSize; uint16_t instanceAlignMask; uint16_t reserved; uint32_t classSize; uint32_t classAddressOffset; void *description; // ... void *baseAddress() { return (void *)((uint8_t *)this - classAddressOffset); } }; struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); }; struct objc_super2 { id receiver; Class current_class; }; struct message_ref_t { IMP imp; SEL sel; }; extern Method protocol_getMethod(protocol_t *p, SEL sel, bool isRequiredMethod, bool isInstanceMethod, bool recursive); static inline void foreach_realized_class_and_subclass_2(Class top, unsigned& count, std::function code) { // runtimeLock.assertLocked(); assert(top); Class cls = top; while (1) { if (--count == 0) { _objc_fatal("Memory corruption in class list."); } if (!code(cls)) break; if (cls->data()->firstSubclass) { cls = cls->data()->firstSubclass; } else { while (!cls->data()->nextSiblingClass && cls != top) { cls = cls->superclass; if (--count == 0) { _objc_fatal("Memory corruption in class list."); } } if (cls == top) break; cls = cls->data()->nextSiblingClass; } } } extern Class firstRealizedClass(); extern unsigned int unreasonableClassCount(); // Enumerates a class and all of its realized subclasses. static inline void foreach_realized_class_and_subclass(Class top, std::function code) { unsigned int count = unreasonableClassCount(); foreach_realized_class_and_subclass_2(top, count, [&code](Class cls) -> bool { code(cls); return true; }); } // Enumerates all realized classes and metaclasses. static inline void foreach_realized_class_and_metaclass(std::function code) { unsigned int count = unreasonableClassCount(); for (Class top = firstRealizedClass(); top != nil; top = top->data()->nextSiblingClass) { foreach_realized_class_and_subclass_2(top, count, [&code](Class cls) -> bool { code(cls); return true; }); } } { // Class‘s ivar size rounded up to a pointer-size boundary. uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } } #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-runtime-new.mm ================================================ /* * Copyright (c) 2005-2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-runtime-new.m * Support for new-ABI classes and images. **********************************************************************/ #if __OBJC2__ #include "objc-private.h" #include "objc-runtime-new.h" #include "objc-file.h" #include "objc-cache.h" #include #include #include #define newprotocol(p) ((protocol_t *)p) static void disableTaggedPointers(); static void detach_class(Class cls, bool isMeta); static void free_class(Class cls); static Class setSuperclass(Class cls, Class newSuper); static Class realizeClass(Class cls); static method_t *getMethodNoSuper_nolock(Class cls, SEL sel); static method_t *getMethod_nolock(Class cls, SEL sel); static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace); static bool isRRSelector(SEL sel); static bool isAWZSelector(SEL sel); static bool methodListImplementsRR(const method_list_t *mlist); static bool methodListImplementsAWZ(const method_list_t *mlist); static void updateCustomRR_AWZ(Class cls, method_t *meth); static method_t *search_method_list(const method_list_t *mlist, SEL sel); static void flushCaches(Class cls); static void initializeTaggedPointerObfuscator(void); #if SUPPORT_FIXUP static void fixupMessageRef(message_ref_t *msg); #endif static bool MetaclassNSObjectAWZSwizzled; static bool ClassNSObjectRRSwizzled; /*********************************************************************** * Lock management **********************************************************************/ mutex_t runtimeLock; mutex_t selLock; mutex_t cacheUpdateLock; recursive_mutex_t loadMethodLock; void lock_init(void) { } /*********************************************************************** * Class structure decoding **********************************************************************/ const uintptr_t objc_debug_class_rw_data_mask = FAST_DATA_MASK; /*********************************************************************** * Non-pointer isa decoding **********************************************************************/ #if SUPPORT_INDEXED_ISA // Indexed non-pointer isa. // These are used to mask the ISA and see if its got an index or not. const uintptr_t objc_debug_indexed_isa_magic_mask = ISA_INDEX_MAGIC_MASK; const uintptr_t objc_debug_indexed_isa_magic_value = ISA_INDEX_MAGIC_VALUE; // die if masks overlap STATIC_ASSERT((ISA_INDEX_MASK & ISA_INDEX_MAGIC_MASK) == 0); // die if magic is wrong STATIC_ASSERT((~ISA_INDEX_MAGIC_MASK & ISA_INDEX_MAGIC_VALUE) == 0); // Then these are used to extract the index from the ISA. const uintptr_t objc_debug_indexed_isa_index_mask = ISA_INDEX_MASK; const uintptr_t objc_debug_indexed_isa_index_shift = ISA_INDEX_SHIFT; asm("\n .globl _objc_absolute_indexed_isa_magic_mask" \ "\n _objc_absolute_indexed_isa_magic_mask = " STRINGIFY2(ISA_INDEX_MAGIC_MASK)); asm("\n .globl _objc_absolute_indexed_isa_magic_value" \ "\n _objc_absolute_indexed_isa_magic_value = " STRINGIFY2(ISA_INDEX_MAGIC_VALUE)); asm("\n .globl _objc_absolute_indexed_isa_index_mask" \ "\n _objc_absolute_indexed_isa_index_mask = " STRINGIFY2(ISA_INDEX_MASK)); asm("\n .globl _objc_absolute_indexed_isa_index_shift" \ "\n _objc_absolute_indexed_isa_index_shift = " STRINGIFY2(ISA_INDEX_SHIFT)); // And then we can use that index to get the class from this array. Note // the size is provided so that clients can ensure the index they get is in // bounds and not read off the end of the array. // Defined in the objc-msg-*.s files // const Class objc_indexed_classes[] // When we don't have enough bits to store a class*, we can instead store an // index in to this array. Classes are added here when they are realized. // Note, an index of 0 is illegal. uintptr_t objc_indexed_classes_count = 0; // SUPPORT_INDEXED_ISA #else // not SUPPORT_INDEXED_ISA // These variables exist but are all set to 0 so that they are ignored. const uintptr_t objc_debug_indexed_isa_magic_mask = 0; const uintptr_t objc_debug_indexed_isa_magic_value = 0; const uintptr_t objc_debug_indexed_isa_index_mask = 0; const uintptr_t objc_debug_indexed_isa_index_shift = 0; Class objc_indexed_classes[1] = { nil }; uintptr_t objc_indexed_classes_count = 0; // not SUPPORT_INDEXED_ISA #endif #if SUPPORT_PACKED_ISA // Packed non-pointer isa. asm("\n .globl _objc_absolute_packed_isa_class_mask" \ "\n _objc_absolute_packed_isa_class_mask = " STRINGIFY2(ISA_MASK)); const uintptr_t objc_debug_isa_class_mask = ISA_MASK; const uintptr_t objc_debug_isa_magic_mask = ISA_MAGIC_MASK; const uintptr_t objc_debug_isa_magic_value = ISA_MAGIC_VALUE; // die if masks overlap STATIC_ASSERT((ISA_MASK & ISA_MAGIC_MASK) == 0); // die if magic is wrong STATIC_ASSERT((~ISA_MAGIC_MASK & ISA_MAGIC_VALUE) == 0); // die if virtual address space bound goes up STATIC_ASSERT((~ISA_MASK & MACH_VM_MAX_ADDRESS) == 0 || ISA_MASK + sizeof(void*) == MACH_VM_MAX_ADDRESS); // SUPPORT_PACKED_ISA #else // not SUPPORT_PACKED_ISA // These variables exist but enforce pointer alignment only. const uintptr_t objc_debug_isa_class_mask = (~WORD_MASK); const uintptr_t objc_debug_isa_magic_mask = WORD_MASK; const uintptr_t objc_debug_isa_magic_value = 0; // not SUPPORT_PACKED_ISA #endif /*********************************************************************** * allocatedClasses * A table of all classes (and metaclasses) which have been allocated * with objc_allocateClassPair. **********************************************************************/ static NXHashTable *allocatedClasses = nil; typedef locstamped_category_list_t category_list; /* Low two bits of mlist->entsize is used as the fixed-up marker. PREOPTIMIZED VERSION: Method lists from shared cache are 1 (uniqued) or 3 (uniqued and sorted). (Protocol method lists are not sorted because of their extra parallel data) Runtime fixed-up method lists get 3. UN-PREOPTIMIZED VERSION: Method lists from shared cache are 1 (uniqued) or 3 (uniqued and sorted) Shared cache's sorting and uniquing are not trusted, but do affect the location of the selector name string. Runtime fixed-up method lists get 2. High two bits of protocol->flags is used as the fixed-up marker. PREOPTIMIZED VERSION: Protocols from shared cache are 1<<30. Runtime fixed-up protocols get 1<<30. UN-PREOPTIMIZED VERSION: Protocols from shared cache are 1<<30. Shared cache's fixups are not trusted. Runtime fixed-up protocols get 3<<30. */ static uint32_t fixed_up_method_list = 3; static uint32_t fixed_up_protocol = PROTOCOL_FIXED_UP_1; void disableSharedCacheOptimizations(void) { fixed_up_method_list = 2; fixed_up_protocol = PROTOCOL_FIXED_UP_1 | PROTOCOL_FIXED_UP_2; } bool method_list_t::isFixedUp() const { return flags() == fixed_up_method_list; } void method_list_t::setFixedUp() { runtimeLock.assertLocked(); assert(!isFixedUp()); entsizeAndFlags = entsize() | fixed_up_method_list; } bool protocol_t::isFixedUp() const { return (flags & PROTOCOL_FIXED_UP_MASK) == fixed_up_protocol; } void protocol_t::setFixedUp() { runtimeLock.assertLocked(); assert(!isFixedUp()); flags = (flags & ~PROTOCOL_FIXED_UP_MASK) | fixed_up_protocol; } method_list_t **method_array_t::endCategoryMethodLists(Class cls) { method_list_t **mlists = beginLists(); method_list_t **mlistsEnd = endLists(); if (mlists == mlistsEnd || !cls->data()->ro->baseMethods()) { // No methods, or no base methods. // Everything here is a category method. return mlistsEnd; } // Have base methods. Category methods are // everything except the last method list. return mlistsEnd - 1; } static const char *sel_cname(SEL sel) { return (const char *)(void *)sel; } static size_t protocol_list_size(const protocol_list_t *plist) { return sizeof(protocol_list_t) + plist->count * sizeof(protocol_t *); } static void try_free(const void *p) { if (p && malloc_size(p)) free((void *)p); } static void (*classCopyFixupHandler)(Class _Nonnull oldClass, Class _Nonnull newClass); void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) (Class _Nonnull oldClass, Class _Nonnull newClass)) { classCopyFixupHandler = newFixupHandler; } static Class alloc_class_for_subclass(Class supercls, size_t extraBytes) { if (!supercls || !supercls->isAnySwift()) { return _calloc_class(sizeof(objc_class) + extraBytes); } // Superclass is a Swift class. New subclass must duplicate its extra bits. // Allocate the new class, with space for super's prefix and suffix // and self's extraBytes. swift_class_t *swiftSupercls = (swift_class_t *)supercls; size_t superSize = swiftSupercls->classSize; void *superBits = swiftSupercls->baseAddress(); void *bits = malloc(superSize + extraBytes); // Copy all of the superclass's data to the new class. memcpy(bits, superBits, superSize); // Erase the objc data and the Swift description in the new class. swift_class_t *swcls = (swift_class_t *) ((uint8_t *)bits + swiftSupercls->classAddressOffset); bzero(swcls, sizeof(objc_class)); swcls->description = nil; if (classCopyFixupHandler) { classCopyFixupHandler(supercls, (Class)swcls); } // Mark this class as Swift-enhanced. if (supercls->isSwiftStable()) { swcls->bits.setIsSwiftStable(); } if (supercls->isSwiftLegacy()) { swcls->bits.setIsSwiftLegacy(); } return (Class)swcls; } /*********************************************************************** * object_getIndexedIvars. **********************************************************************/ void *object_getIndexedIvars(id obj) { uint8_t *base = (uint8_t *)obj; if (!obj) return nil; if (obj->isTaggedPointer()) return nil; if (!obj->isClass()) return base + obj->ISA()->alignedInstanceSize(); Class cls = (Class)obj; if (!cls->isAnySwift()) return base + sizeof(objc_class); swift_class_t *swcls = (swift_class_t *)cls; return base - swcls->classAddressOffset + word_align(swcls->classSize); } /*********************************************************************** * make_ro_writeable * Reallocates rw->ro if necessary to make it writeable. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static class_ro_t *make_ro_writeable(class_rw_t *rw) { runtimeLock.assertLocked(); if (rw->flags & RW_COPIED_RO) { // already writeable, do nothing } else { class_ro_t *ro = (class_ro_t *) memdup(rw->ro, sizeof(*rw->ro)); rw->ro = ro; rw->flags |= RW_COPIED_RO; } return (class_ro_t *)rw->ro; } /*********************************************************************** * unattachedCategories * Returns the class => categories map of unattached categories. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static NXMapTable *unattachedCategories(void) { runtimeLock.assertLocked(); static NXMapTable *category_map = nil; if (category_map) return category_map; // fixme initial map size category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16); return category_map; } /*********************************************************************** * dataSegmentsContain * Returns true if the given address lies within a data segment in any * loaded image. * * This is optimized for use where the return value is expected to be * true. A call where the return value is false always results in a * slow linear search of all loaded images. A call where the return * value is fast will often be fast due to caching. **********************************************************************/ static bool dataSegmentsContain(const void *ptr) { struct Range { uintptr_t start, end; bool contains(uintptr_t ptr) { return start <= ptr && ptr <= end; } }; // This is a really simple linear searched cache. On a cache hit, // the hit entry is moved to the front of the array. On a cache // miss where a range is successfully found on the slow path, the // found range is inserted at the beginning of the cache. This gives // us fast access to the most recently used elements, and LRU // eviction. enum { cacheCount = 16 }; static Range cache[cacheCount]; uintptr_t addr = (uintptr_t)ptr; // Special case a hit on the first entry of the cache. No // bookkeeping is required at all in this case. if (cache[0].contains(addr)) { return true; } // Search the rest of the cache. for (unsigned i = 1; i < cacheCount; i++) { if (cache[i].contains(addr)) { // Cache hit. Move all preceding entries down one element, // then place this entry at the front. Range r = cache[i]; memmove(&cache[1], &cache[0], i * sizeof(cache[0])); cache[0] = r; return true; } } // Cache miss. Find the image header containing the given address. // If there isn't one, then we're definitely not in any image, // so return false. Range found = { 0, 0 }; auto *h = (headerType *)dyld_image_header_containing_address(ptr); if (h == nullptr) return false; // Iterate over the data segments in the found image. If the address // lies within one, note the data segment range in `found`. // TODO: this is more work than we'd like to do. All we really need // is the full range of the image. Addresses within the TEXT segment // would also be acceptable for our use case. If possible, we should // change this to work with the full address range of the found // image header. Another possibility would be to use the range // from `h` to the end of the page containing `addr`. foreach_data_segment(h, [&](const segmentType *seg, intptr_t slide) { Range r; r.start = seg->vmaddr + slide; r.end = r.start + seg->vmsize; if (r.contains(addr)) found = r; }); if (found.start != 0) { memmove(&cache[1], &cache[0], (cacheCount - 1) * sizeof(cache[0])); cache[0] = found; return true; } return false; } /*********************************************************************** * isKnownClass * Return true if the class is known to the runtime (located within the * shared cache, within the data segment of a loaded image, or has been * allocated with obj_allocateClassPair). **********************************************************************/ static bool isKnownClass(Class cls) { // The order of conditionals here is important for speed. We want to // put the most common cases first, but also the fastest cases // first. Checking the shared region is both fast and common. // Checking allocatedClasses is fast, but may not be common, // depending on what the program is doing. Checking if data segments // contain the address is slow, so do it last. return (sharedRegionContains(cls) || NXHashMember(allocatedClasses, cls) || dataSegmentsContain(cls)); } /*********************************************************************** * addClassTableEntry * Add a class to the table of all classes. If addMeta is true, * automatically adds the metaclass of the class as well. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void addClassTableEntry(Class cls, bool addMeta = true) { runtimeLock.assertLocked(); // This class is allowed to be a known class via the shared cache or via // data segments, but it is not allowed to be in the dynamic table already. assert(!NXHashMember(allocatedClasses, cls)); if (!isKnownClass(cls)) NXHashInsert(allocatedClasses, cls); if (addMeta) addClassTableEntry(cls->ISA(), false); } /*********************************************************************** * checkIsKnownClass * Checks the given class against the list of all known classes. Dies * with a fatal error if the class is not known. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void checkIsKnownClass(Class cls) { if (!isKnownClass(cls)) _objc_fatal("Attempt to use unknown class %p.", cls); } /*********************************************************************** * addUnattachedCategoryForClass * Records an unattached category. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void addUnattachedCategoryForClass(category_t *cat, Class cls, header_info *catHeader) { runtimeLock.assertLocked(); // DO NOT use cat->cls! cls may be cat->cls->isa instead NXMapTable *cats = unattachedCategories(); category_list *list; list = (category_list *)NXMapGet(cats, cls); if (!list) { list = (category_list *) calloc(sizeof(*list) + sizeof(list->list[0]), 1); } else { list = (category_list *) realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1)); } list->list[list->count++] = (locstamped_category_t){cat, catHeader}; NXMapInsert(cats, cls, list); } /*********************************************************************** * removeUnattachedCategoryForClass * Removes an unattached category. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void removeUnattachedCategoryForClass(category_t *cat, Class cls) { runtimeLock.assertLocked(); // DO NOT use cat->cls! cls may be cat->cls->isa instead NXMapTable *cats = unattachedCategories(); category_list *list; list = (category_list *)NXMapGet(cats, cls); if (!list) return; uint32_t i; for (i = 0; i < list->count; i++) { if (list->list[i].cat == cat) { // shift entries to preserve list order memmove(&list->list[i], &list->list[i+1], (list->count-i-1) * sizeof(list->list[i])); list->count--; return; } } } /*********************************************************************** * unattachedCategoriesForClass * Returns the list of unattached categories for a class, and * deletes them from the list. * The result must be freed by the caller. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static category_list * unattachedCategoriesForClass(Class cls, bool realizing) { runtimeLock.assertLocked(); return (category_list *)NXMapRemove(unattachedCategories(), cls); } /*********************************************************************** * removeAllUnattachedCategoriesForClass * Deletes all unattached categories (loaded or not) for a class. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void removeAllUnattachedCategoriesForClass(Class cls) { runtimeLock.assertLocked(); void *list = NXMapRemove(unattachedCategories(), cls); if (list) free(list); } /*********************************************************************** * classNSObject * Returns class NSObject. * Locking: none **********************************************************************/ static Class classNSObject(void) { extern objc_class OBJC_CLASS_$_NSObject; return (Class)&OBJC_CLASS_$_NSObject; } /*********************************************************************** * printReplacements * Implementation of PrintReplacedMethods / OBJC_PRINT_REPLACED_METHODS. * Warn about methods from cats that override other methods in cats or cls. * Assumes no methods from cats have been added to cls yet. **********************************************************************/ static void printReplacements(Class cls, category_list *cats) { uint32_t c; bool isMeta = cls->isMetaClass(); if (!cats) return; // Newest categories are LAST in cats // Later categories override earlier ones. for (c = 0; c < cats->count; c++) { category_t *cat = cats->list[c].cat; method_list_t *mlist = cat->methodsForMeta(isMeta); if (!mlist) continue; for (const auto& meth : *mlist) { SEL s = sel_registerName(sel_cname(meth.name)); // Search for replaced methods in method lookup order. // Complain about the first duplicate only. // Look for method in earlier categories for (uint32_t c2 = 0; c2 < c; c2++) { category_t *cat2 = cats->list[c2].cat; const method_list_t *mlist2 = cat2->methodsForMeta(isMeta); if (!mlist2) continue; for (const auto& meth2 : *mlist2) { SEL s2 = sel_registerName(sel_cname(meth2.name)); if (s == s2) { logReplacedMethod(cls->nameForLogging(), s, cls->isMetaClass(), cat->name, meth2.imp, meth.imp); goto complained; } } } // Look for method in cls for (const auto& meth2 : cls->data()->methods) { SEL s2 = sel_registerName(sel_cname(meth2.name)); if (s == s2) { logReplacedMethod(cls->nameForLogging(), s, cls->isMetaClass(), cat->name, meth2.imp, meth.imp); goto complained; } } complained: ; } } } static bool isBundleClass(Class cls) { return cls->data()->ro->flags & RO_FROM_BUNDLE; } static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { runtimeLock.assertLocked(); assert(!mlist->isFixedUp()); // fixme lock less in attachMethodLists ? { mutex_locker_t lock(selLock); // Unique selectors in list. for (auto& meth : *mlist) { const char *name = sel_cname(meth.name); meth.name = sel_registerNameNoLock(name, bundleCopy); } } // Sort by selector address. if (sort) { method_t::SortBySELAddress sorter; std::stable_sort(mlist->begin(), mlist->end(), sorter); } // Mark method list as uniqued and sorted mlist->setFixedUp(); } static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, bool baseMethods, bool methodsFromBundle) { runtimeLock.assertLocked(); if (addedCount == 0) return; // Don't scan redundantly bool scanForCustomRR = !cls->hasCustomRR(); bool scanForCustomAWZ = !cls->hasCustomAWZ(); // There exist RR/AWZ special cases for some class's base methods. // But this code should never need to scan base methods for RR/AWZ: // default RR/AWZ cannot be set before setInitialized(). // Therefore we need not handle any special cases here. if (baseMethods) { assert(!scanForCustomRR && !scanForCustomAWZ); } // Add method lists to array. // Reallocate un-fixed method lists. // The new methods are PREPENDED to the method list array. for (int i = 0; i < addedCount; i++) { method_list_t *mlist = addedLists[i]; assert(mlist); // Fixup selectors if necessary if (!mlist->isFixedUp()) { fixupMethodList(mlist, methodsFromBundle, true/*sort*/); } // Scan for method implementations tracked by the class's flags if (scanForCustomRR && methodListImplementsRR(mlist)) { cls->setHasCustomRR(); scanForCustomRR = false; } if (scanForCustomAWZ && methodListImplementsAWZ(mlist)) { cls->setHasCustomAWZ(); scanForCustomAWZ = false; } } } // Attach method lists and properties and protocols from categories to a class. // Assumes the categories in cats are all loaded and sorted by load order, // oldest categories first. static void attachCategories(Class cls, category_list *cats, bool flush_caches) { if (!cats) return; if (PrintReplacedMethods) printReplacements(cls, cats); bool isMeta = cls->isMetaClass(); // fixme rearrange to remove these intermediate allocations method_list_t **mlists = (method_list_t **) malloc(cats->count * sizeof(*mlists)); property_list_t **proplists = (property_list_t **) malloc(cats->count * sizeof(*proplists)); protocol_list_t **protolists = (protocol_list_t **) malloc(cats->count * sizeof(*protolists)); // Count backwards through cats to get newest categories first int mcount = 0; int propcount = 0; int protocount = 0; int i = cats->count; bool fromBundle = NO; while (i--) { auto& entry = cats->list[i]; method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { mlists[mcount++] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { proplists[propcount++] = proplist; } protocol_list_t *protolist = entry.cat->protocols; if (protolist) { protolists[protocount++] = protolist; } } auto rw = cls->data(); prepareMethodLists(cls, mlists, mcount, NO, fromBundle); rw->methods.attachLists(mlists, mcount); free(mlists); if (flush_caches && mcount > 0) flushCaches(cls); rw->properties.attachLists(proplists, propcount); free(proplists); rw->protocols.attachLists(protolists, protocount); free(protolists); } /*********************************************************************** * methodizeClass * Fixes up cls's method list, protocol list, and property list. * Attaches any outstanding categories. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void methodizeClass(Class cls) { runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro; // Methodizing for the first time if (PrintConnecting) { _objc_inform("CLASS: methodizing class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : ""); } // Install methods and properties that the class implements itself. method_list_t *list = ro->baseMethods(); if (list) { prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls)); rw->methods.attachLists(&list, 1); } property_list_t *proplist = ro->baseProperties; if (proplist) { rw->properties.attachLists(&proplist, 1); } protocol_list_t *protolist = ro->baseProtocols; if (protolist) { rw->protocols.attachLists(&protolist, 1); } // Root classes get bonus method implementations if they don't have // them already. These apply before category replacements. if (cls->isRootMetaclass()) { // root metaclass addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO); } // Attach categories. category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/); attachCategories(cls, cats, false /*don't flush caches*/); if (PrintConnecting) { if (cats) { for (uint32_t i = 0; i < cats->count; i++) { _objc_inform("CLASS: attached category %c%s(%s)", isMeta ? '+' : '-', cls->nameForLogging(), cats->list[i].cat->name); } } } if (cats) free(cats); #if DEBUG // Debug: sanity-check all SELs; log method list contents for (const auto& meth : rw->methods) { if (PrintConnecting) { _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', cls->nameForLogging(), sel_getName(meth.name)); } assert(sel_registerName(sel_getName(meth.name)) == meth.name); } #endif } /*********************************************************************** * remethodizeClass * Attach outstanding categories to an existing class. * Fixes up cls's method list, protocol list, and property list. * Updates method caches for cls and its subclasses. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void remethodizeClass(Class cls) { category_list *cats; bool isMeta; runtimeLock.assertLocked(); isMeta = cls->isMetaClass(); // Re-methodizing: check for more categories if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) { if (PrintConnecting) { _objc_inform("CLASS: attaching categories to class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : ""); } attachCategories(cls, cats, true /*flush caches*/); free(cats); } } /*********************************************************************** * nonMetaClasses * Returns the secondary metaclass => class map * Used for some cases of +initialize and +resolveClassMethod:. * This map does not contain all class and metaclass pairs. It only * contains metaclasses whose classes would be in the runtime-allocated * named-class table, but are not because some other class with the same name * is in that table. * Classes with no duplicates are not included. * Classes in the preoptimized named-class table are not included. * Classes whose duplicates are in the preoptimized table are not included. * Most code should use getNonMetaClass() instead of reading this table. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static NXMapTable *nonmeta_class_map = nil; static NXMapTable *nonMetaClasses(void) { runtimeLock.assertLocked(); if (nonmeta_class_map) return nonmeta_class_map; // nonmeta_class_map is typically small INIT_ONCE_PTR(nonmeta_class_map, NXCreateMapTable(NXPtrValueMapPrototype, 32), NXFreeMapTable(v)); return nonmeta_class_map; } /*********************************************************************** * addNonMetaClass * Adds metacls => cls to the secondary metaclass map * Locking: runtimeLock must be held by the caller **********************************************************************/ static void addNonMetaClass(Class cls) { runtimeLock.assertLocked(); void *old; old = NXMapInsert(nonMetaClasses(), cls->ISA(), cls); assert(!cls->isMetaClass()); assert(cls->ISA()->isMetaClass()); assert(!old); } static void removeNonMetaClass(Class cls) { runtimeLock.assertLocked(); NXMapRemove(nonMetaClasses(), cls->ISA()); } static bool scanMangledField(const char *&string, const char *end, const char *&field, int& length) { // Leading zero not allowed. if (*string == '0') return false; length = 0; field = string; while (field < end) { char c = *field; if (!isdigit(c)) break; field++; if (__builtin_smul_overflow(length, 10, &length)) return false; if (__builtin_sadd_overflow(length, c - '0', &length)) return false; } string = field + length; return length > 0 && string <= end; } /*********************************************************************** * copySwiftV1DemangledName * Returns the pretty form of the given Swift-v1-mangled class or protocol name. * Returns nil if the string doesn't look like a mangled Swift v1 name. * The result must be freed with free(). **********************************************************************/ static char *copySwiftV1DemangledName(const char *string, bool isProtocol = false) { if (!string) return nil; // Swift mangling prefix. if (strncmp(string, isProtocol ? "_TtP" : "_TtC", 4) != 0) return nil; string += 4; const char *end = string + strlen(string); // Module name. const char *prefix; int prefixLength; if (string[0] == 's') { // "s" is the Swift module. prefix = "Swift"; prefixLength = 5; string += 1; } else { if (! scanMangledField(string, end, prefix, prefixLength)) return nil; } // Class or protocol name. const char *suffix; int suffixLength; if (! scanMangledField(string, end, suffix, suffixLength)) return nil; if (isProtocol) { // Remainder must be "_". if (strcmp(string, "_") != 0) return nil; } else { // Remainder must be empty. if (string != end) return nil; } char *result; asprintf(&result, "%.*s.%.*s", prefixLength,prefix, suffixLength,suffix); return result; } /*********************************************************************** * copySwiftV1MangledName * Returns the Swift 1.0 mangled form of the given class or protocol name. * Returns nil if the string doesn't look like an unmangled Swift name. * The result must be freed with free(). **********************************************************************/ static char *copySwiftV1MangledName(const char *string, bool isProtocol = false) { if (!string) return nil; size_t dotCount = 0; size_t dotIndex; const char *s; for (s = string; *s; s++) { if (*s == '.') { dotCount++; dotIndex = s - string; } } size_t stringLength = s - string; if (dotCount != 1 || dotIndex == 0 || dotIndex >= stringLength-1) { return nil; } const char *prefix = string; size_t prefixLength = dotIndex; const char *suffix = string + dotIndex + 1; size_t suffixLength = stringLength - (dotIndex + 1); char *name; if (prefixLength == 5 && memcmp(prefix, "Swift", 5) == 0) { asprintf(&name, "_Tt%cs%zu%.*s%s", isProtocol ? 'P' : 'C', suffixLength, (int)suffixLength, suffix, isProtocol ? "_" : ""); } else { asprintf(&name, "_Tt%c%zu%.*s%zu%.*s%s", isProtocol ? 'P' : 'C', prefixLength, (int)prefixLength, prefix, suffixLength, (int)suffixLength, suffix, isProtocol ? "_" : ""); } return name; } /*********************************************************************** * getClass * Looks up a class by name. The class MIGHT NOT be realized. * Demangled Swift names are recognized. * Locking: runtimeLock must be read- or write-locked by the caller. **********************************************************************/ // This is a misnomer: gdb_objc_realized_classes is actually a list of // named classes not in the dyld shared cache, whether realized or not. NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h static Class getClass_impl(const char *name) { runtimeLock.assertLocked(); // allocated in _read_images assert(gdb_objc_realized_classes); // Try runtime-allocated table Class result = (Class)NXMapGet(gdb_objc_realized_classes, name); if (result) return result; // Try table from dyld shared cache return getPreoptimizedClass(name); } static Class getClass(const char *name) { runtimeLock.assertLocked(); // Try name as-is Class result = getClass_impl(name); if (result) return result; // Try Swift-mangled equivalent of the given name. if (char *swName = copySwiftV1MangledName(name)) { result = getClass_impl(swName); free(swName); return result; } return nil; } /*********************************************************************** * addNamedClass * Adds name => cls to the named non-meta class map. * Warns about duplicate class names and keeps the old mapping. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void addNamedClass(Class cls, const char *name, Class replacing = nil) { runtimeLock.assertLocked(); Class old; if ((old = getClass(name)) && old != replacing) { inform_duplicate(name, old, cls); // getNonMetaClass uses name lookups. Classes not found by name // lookup must be in the secondary meta->nonmeta table. addNonMetaClass(cls); } else { NXMapInsert(gdb_objc_realized_classes, name, cls); } assert(!(cls->data()->flags & RO_META)); // wrong: constructed classes are already realized when they get here // assert(!cls->isRealized()); } /*********************************************************************** * removeNamedClass * Removes cls from the name => cls map. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void removeNamedClass(Class cls, const char *name) { runtimeLock.assertLocked(); assert(!(cls->data()->flags & RO_META)); if (cls == NXMapGet(gdb_objc_realized_classes, name)) { NXMapRemove(gdb_objc_realized_classes, name); } else { // cls has a name collision with another class - don't remove the other // but do remove cls from the secondary metaclass->class map. removeNonMetaClass(cls); } } /*********************************************************************** * unreasonableClassCount * Provides an upper bound for any iteration of classes, * to prevent spins when runtime metadata is corrupted. **********************************************************************/ unsigned unreasonableClassCount() { runtimeLock.assertLocked(); int base = NXCountMapTable(gdb_objc_realized_classes) + getPreoptimizedClassUnreasonableCount(); // Provide lots of slack here. Some iterations touch metaclasses too. // Some iterations backtrack (like realized class iteration). // We don't need an efficient bound, merely one that prevents spins. return (base + 1) * 16; } /*********************************************************************** * futureNamedClasses * Returns the classname => future class map for unrealized future classes. * Locking: runtimeLock must be held by the caller **********************************************************************/ static NXMapTable *future_named_class_map = nil; static NXMapTable *futureNamedClasses() { runtimeLock.assertLocked(); if (future_named_class_map) return future_named_class_map; // future_named_class_map is big enough for CF's classes and a few others future_named_class_map = NXCreateMapTable(NXStrValueMapPrototype, 32); return future_named_class_map; } static bool haveFutureNamedClasses() { return future_named_class_map && NXCountMapTable(future_named_class_map); } /*********************************************************************** * addFutureNamedClass * Installs cls as the class structure to use for the named class if it appears. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void addFutureNamedClass(const char *name, Class cls) { void *old; runtimeLock.assertLocked(); if (PrintFuture) { _objc_inform("FUTURE: reserving %p for %s", (void*)cls, name); } class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); class_ro_t *ro = (class_ro_t *)calloc(sizeof(class_ro_t), 1); ro->name = strdupIfMutable(name); rw->ro = ro; cls->setData(rw); cls->data()->flags = RO_FUTURE; old = NXMapKeyCopyingInsert(futureNamedClasses(), name, cls); assert(!old); } /*********************************************************************** * popFutureNamedClass * Removes the named class from the unrealized future class list, * because it has been realized. * Returns nil if the name is not used by a future class. * Locking: runtimeLock must be held by the caller **********************************************************************/ static Class popFutureNamedClass(const char *name) { runtimeLock.assertLocked(); Class cls = nil; if (future_named_class_map) { cls = (Class)NXMapKeyFreeingRemove(future_named_class_map, name); if (cls && NXCountMapTable(future_named_class_map) == 0) { NXFreeMapTable(future_named_class_map); future_named_class_map = nil; } } return cls; } /*********************************************************************** * remappedClasses * Returns the oldClass => newClass map for realized future classes. * Returns the oldClass => nil map for ignored weak-linked classes. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static NXMapTable *remappedClasses(bool create) { static NXMapTable *remapped_class_map = nil; runtimeLock.assertLocked(); if (remapped_class_map) return remapped_class_map; if (!create) return nil; // remapped_class_map is big enough to hold CF's classes and a few others INIT_ONCE_PTR(remapped_class_map, NXCreateMapTable(NXPtrValueMapPrototype, 32), NXFreeMapTable(v)); return remapped_class_map; } /*********************************************************************** * noClassesRemapped * Returns YES if no classes have been remapped * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static bool noClassesRemapped(void) { runtimeLock.assertLocked(); bool result = (remappedClasses(NO) == nil); #if DEBUG // Catch construction of an empty table, which defeats optimization. NXMapTable *map = remappedClasses(NO); if (map) assert(NXCountMapTable(map) > 0); #endif return result; } /*********************************************************************** * addRemappedClass * newcls is a realized future class, replacing oldcls. * OR newcls is nil, replacing ignored weak-linked class oldcls. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ static void addRemappedClass(Class oldcls, Class newcls) { runtimeLock.assertLocked(); if (PrintFuture) { _objc_inform("FUTURE: using %p instead of %p for %s", (void*)newcls, (void*)oldcls, oldcls->nameForLogging()); } void *old; old = NXMapInsert(remappedClasses(YES), oldcls, newcls); assert(!old); } /*********************************************************************** * remapClass * Returns the live class pointer for cls, which may be pointing to * a class struct that has been reallocated. * Returns nil if cls is ignored because of weak linking. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static Class remapClass(Class cls) { runtimeLock.assertLocked(); Class c2; if (!cls) return nil; NXMapTable *map = remappedClasses(NO); if (!map || NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) { return cls; } else { return c2; } } static Class remapClass(classref_t cls) { return remapClass((Class)cls); } Class _class_remap(Class cls) { mutex_locker_t lock(runtimeLock); return remapClass(cls); } /*********************************************************************** * remapClassRef * Fix up a class ref, in case the class referenced has been reallocated * or is an ignored weak-linked class. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static void remapClassRef(Class *clsref) { runtimeLock.assertLocked(); Class newcls = remapClass(*clsref); if (*clsref != newcls) *clsref = newcls; } /*********************************************************************** * getNonMetaClass * Return the ordinary class for this class or metaclass. * `inst` is an instance of `cls` or a subclass thereof, or nil. * Non-nil inst is faster. * Used by +initialize. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static Class getNonMetaClass(Class metacls, id inst) { static int total, named, secondary, sharedcache; runtimeLock.assertLocked(); realizeClass(metacls); total++; // return cls itself if it's already a non-meta class if (!metacls->isMetaClass()) return metacls; // metacls really is a metaclass // special case for root metaclass // where inst == inst->ISA() == metacls is possible if (metacls->ISA() == metacls) { Class cls = metacls->superclass; assert(cls->isRealized()); assert(!cls->isMetaClass()); assert(cls->ISA() == metacls); if (cls->ISA() == metacls) return cls; } // use inst if available if (inst) { Class cls = (Class)inst; realizeClass(cls); // cls may be a subclass - find the real class for metacls while (cls && cls->ISA() != metacls) { cls = cls->superclass; realizeClass(cls); } if (cls) { assert(!cls->isMetaClass()); assert(cls->ISA() == metacls); return cls; } #if DEBUG _objc_fatal("cls is not an instance of metacls"); #else // release build: be forgiving and fall through to slow lookups #endif } // try name lookup { Class cls = getClass(metacls->mangledName()); if (cls->ISA() == metacls) { named++; if (PrintInitializing) { _objc_inform("INITIALIZE: %d/%d (%g%%) " "successful by-name metaclass lookups", named, total, named*100.0/total); } realizeClass(cls); return cls; } } // try secondary table { Class cls = (Class)NXMapGet(nonMetaClasses(), metacls); if (cls) { secondary++; if (PrintInitializing) { _objc_inform("INITIALIZE: %d/%d (%g%%) " "successful secondary metaclass lookups", secondary, total, secondary*100.0/total); } assert(cls->ISA() == metacls); realizeClass(cls); return cls; } } // try any duplicates in the dyld shared cache { Class cls = nil; int count; Class *classes = copyPreoptimizedClasses(metacls->mangledName(),&count); if (classes) { for (int i = 0; i < count; i++) { if (classes[i]->ISA() == metacls) { cls = classes[i]; break; } } free(classes); } if (cls) { sharedcache++; if (PrintInitializing) { _objc_inform("INITIALIZE: %d/%d (%g%%) " "successful shared cache metaclass lookups", sharedcache, total, sharedcache*100.0/total); } realizeClass(cls); return cls; } } _objc_fatal("no class for metaclass %p", (void*)metacls); } /*********************************************************************** * _class_getNonMetaClass * Return the ordinary class for this class or metaclass. * Used by +initialize. * Locking: acquires runtimeLock **********************************************************************/ Class _class_getNonMetaClass(Class cls, id obj) { mutex_locker_t lock(runtimeLock); cls = getNonMetaClass(cls, obj); assert(cls->isRealized()); return cls; } /*********************************************************************** * addRootClass * Adds cls as a new realized root class. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static Class _firstRealizedClass = nil; Class firstRealizedClass() { runtimeLock.assertLocked(); return _firstRealizedClass; } static void addRootClass(Class cls) { runtimeLock.assertLocked(); assert(cls->isRealized()); cls->data()->nextSiblingClass = _firstRealizedClass; _firstRealizedClass = cls; } static void removeRootClass(Class cls) { runtimeLock.assertLocked(); Class *classp; for (classp = &_firstRealizedClass; *classp != cls; classp = &(*classp)->data()->nextSiblingClass) { } *classp = (*classp)->data()->nextSiblingClass; } /*********************************************************************** * addSubclass * Adds subcls as a subclass of supercls. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void addSubclass(Class supercls, Class subcls) { runtimeLock.assertLocked(); if (supercls && subcls) { assert(supercls->isRealized()); assert(subcls->isRealized()); subcls->data()->nextSiblingClass = supercls->data()->firstSubclass; supercls->data()->firstSubclass = subcls; if (supercls->hasCxxCtor()) { subcls->setHasCxxCtor(); } if (supercls->hasCxxDtor()) { subcls->setHasCxxDtor(); } if (supercls->hasCustomRR()) { subcls->setHasCustomRR(true); } if (supercls->hasCustomAWZ()) { subcls->setHasCustomAWZ(true); } // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass if (supercls->instancesRequireRawIsa() && supercls->superclass) { subcls->setInstancesRequireRawIsa(true); } } } /*********************************************************************** * removeSubclass * Removes subcls as a subclass of supercls. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void removeSubclass(Class supercls, Class subcls) { runtimeLock.assertLocked(); assert(supercls->isRealized()); assert(subcls->isRealized()); assert(subcls->superclass == supercls); Class *cp; for (cp = &supercls->data()->firstSubclass; *cp && *cp != subcls; cp = &(*cp)->data()->nextSiblingClass) ; assert(*cp == subcls); *cp = subcls->data()->nextSiblingClass; } /*********************************************************************** * protocols * Returns the protocol name => protocol map for protocols. * Locking: runtimeLock must read- or write-locked by the caller **********************************************************************/ static NXMapTable *protocols(void) { static NXMapTable *protocol_map = nil; runtimeLock.assertLocked(); INIT_ONCE_PTR(protocol_map, NXCreateMapTable(NXStrValueMapPrototype, 16), NXFreeMapTable(v) ); return protocol_map; } /*********************************************************************** * getProtocol * Looks up a protocol by name. Demangled Swift names are recognized. * Locking: runtimeLock must be read- or write-locked by the caller. **********************************************************************/ static Protocol *getProtocol(const char *name) { runtimeLock.assertLocked(); // Try name as-is. Protocol *result = (Protocol *)NXMapGet(protocols(), name); if (result) return result; // Try Swift-mangled equivalent of the given name. if (char *swName = copySwiftV1MangledName(name, true/*isProtocol*/)) { result = (Protocol *)NXMapGet(protocols(), swName); free(swName); return result; } return nil; } /*********************************************************************** * remapProtocol * Returns the live protocol pointer for proto, which may be pointing to * a protocol struct that has been reallocated. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static protocol_t *remapProtocol(protocol_ref_t proto) { runtimeLock.assertLocked(); protocol_t *newproto = (protocol_t *) getProtocol(((protocol_t *)proto)->mangledName); return newproto ? newproto : (protocol_t *)proto; } /*********************************************************************** * remapProtocolRef * Fix up a protocol ref, in case the protocol referenced has been reallocated. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static size_t UnfixedProtocolReferences; static void remapProtocolRef(protocol_t **protoref) { runtimeLock.assertLocked(); protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref); if (*protoref != newproto) { *protoref = newproto; UnfixedProtocolReferences++; } } /*********************************************************************** * moveIvars * Slides a class's ivars to accommodate the given superclass size. * Ivars are NOT compacted to compensate for a superclass that shrunk. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void moveIvars(class_ro_t *ro, uint32_t superSize) { runtimeLock.assertLocked(); uint32_t diff; assert(superSize > ro->instanceStart); diff = superSize - ro->instanceStart; if (ro->ivars) { // Find maximum alignment in this class's ivars uint32_t maxAlignment = 1; for (const auto& ivar : *ro->ivars) { if (!ivar.offset) continue; // anonymous bitfield uint32_t alignment = ivar.alignment(); if (alignment > maxAlignment) maxAlignment = alignment; } // Compute a slide value that preserves that alignment uint32_t alignMask = maxAlignment - 1; diff = (diff + alignMask) & ~alignMask; // Slide all of this class's ivars en masse for (const auto& ivar : *ro->ivars) { if (!ivar.offset) continue; // anonymous bitfield uint32_t oldOffset = (uint32_t)*ivar.offset; uint32_t newOffset = oldOffset + diff; *ivar.offset = newOffset; if (PrintIvars) { _objc_inform("IVARS: offset %u -> %u for %s " "(size %u, align %u)", oldOffset, newOffset, ivar.name, ivar.size, ivar.alignment()); } } } *(uint32_t *)&ro->instanceStart += diff; *(uint32_t *)&ro->instanceSize += diff; } static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro_t*& ro) { class_rw_t *rw = cls->data(); assert(supercls); assert(!cls->isMetaClass()); /* debug: print them all before sliding if (ro->ivars) { for (const auto& ivar : *ro->ivars) { if (!ivar.offset) continue; // anonymous bitfield _objc_inform("IVARS: %s.%s (offset %u, size %u, align %u)", ro->name, ivar.name, *ivar.offset, ivar.size, ivar.alignment()); } } */ // Non-fragile ivars - reconcile this class with its superclass const class_ro_t *super_ro = supercls->data()->ro; if (DebugNonFragileIvars) { // Debugging: Force non-fragile ivars to slide. // Intended to find compiler, runtime, and program bugs. // If it fails with this and works without, you have a problem. // Operation: Reset everything to 0 + misalignment. // Then force the normal sliding logic to push everything back. // Exceptions: root classes, metaclasses, *NSCF* classes, // __CF* classes, NSConstantString, NSSimpleCString // (already know it's not root because supercls != nil) const char *clsname = cls->mangledName(); if (!strstr(clsname, "NSCF") && 0 != strncmp(clsname, "__CF", 4) && 0 != strcmp(clsname, "NSConstantString") && 0 != strcmp(clsname, "NSSimpleCString")) { uint32_t oldStart = ro->instanceStart; class_ro_t *ro_w = make_ro_writeable(rw); ro = rw->ro; // Find max ivar alignment in class. // default to word size to simplify ivar update uint32_t alignment = 1<ivars) { for (const auto& ivar : *ro->ivars) { if (ivar.alignment() > alignment) { alignment = ivar.alignment(); } } } uint32_t misalignment = ro->instanceStart % alignment; uint32_t delta = ro->instanceStart - misalignment; ro_w->instanceStart = misalignment; ro_w->instanceSize -= delta; if (PrintIvars) { _objc_inform("IVARS: DEBUG: forcing ivars for class '%s' " "to slide (instanceStart %zu -> %zu)", cls->nameForLogging(), (size_t)oldStart, (size_t)ro->instanceStart); } if (ro->ivars) { for (const auto& ivar : *ro->ivars) { if (!ivar.offset) continue; // anonymous bitfield *ivar.offset -= delta; } } } } if (ro->instanceStart >= super_ro->instanceSize) { // Superclass has not overgrown its space. We're done here. return; } // fixme can optimize for "class has no new ivars", etc if (ro->instanceStart < super_ro->instanceSize) { // Superclass has changed size. This class's ivars must move. // Also slide layout bits in parallel. // This code is incapable of compacting the subclass to // compensate for a superclass that shrunk, so don't do that. if (PrintIvars) { _objc_inform("IVARS: sliding ivars for class %s " "(superclass was %u bytes, now %u)", cls->nameForLogging(), ro->instanceStart, super_ro->instanceSize); } class_ro_t *ro_w = make_ro_writeable(rw); ro = rw->ro; moveIvars(ro_w, super_ro->instanceSize); gdb_objc_class_changed(cls, OBJC_CLASS_IVARS_CHANGED, ro->name); } } /*********************************************************************** * realizeClass * Performs first-time initialization on class cls, * including allocating its read-write data. * Returns the real class structure for the class. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ static Class realizeClass(Class cls) { runtimeLock.assertLocked(); const class_ro_t *ro; class_rw_t *rw; Class supercls; Class metacls; bool isMeta; if (!cls) return nil; if (cls->isRealized()) return cls; assert(cls == remapClass(cls)); // fixme verify class is not in an un-dlopened part of the shared cache? ro = (const class_ro_t *)cls->data(); if (ro->flags & RO_FUTURE) { // This was a future class. rw data is already allocated. rw = cls->data(); ro = cls->data()->ro; cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { // Normal class. Allocate writeable class data. rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); rw->ro = ro; rw->flags = RW_REALIZED|RW_REALIZING; cls->setData(rw); } isMeta = ro->flags & RO_META; rw->version = isMeta ? 7 : 0; // old runtime went up to 6 // Choose an index for this class. // Sets cls->instancesRequireRawIsa if indexes no more indexes are available cls->chooseClassArrayIndex(); if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s'%s %p %p #%u", cls->nameForLogging(), isMeta ? " (meta)" : "", (void*)cls, ro, cls->classArrayIndex()); } // Realize superclass and metaclass, if they aren't already. // This needs to be done after RW_REALIZED is set above, for root classes. // This needs to be done after class index is chosen, for root metaclasses. supercls = realizeClass(remapClass(cls->superclass)); metacls = realizeClass(remapClass(cls->ISA())); #if SUPPORT_NONPOINTER_ISA // Disable non-pointer isa for some classes and/or platforms. // Set instancesRequireRawIsa. bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); bool rawIsaIsInherited = false; static bool hackedDispatch = false; if (DisableNonpointerIsa) { // Non-pointer isa disabled by environment or app SDK version instancesRequireRawIsa = true; } else if (!hackedDispatch && !(ro->flags & RO_META) && 0 == strcmp(ro->name, "OS_object")) { // hack for libdispatch et al - isa also acts as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } else if (supercls && supercls->superclass && supercls->instancesRequireRawIsa()) { // This is also propagated by addSubclass() // but nonpointer isa setup needs it earlier. // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass instancesRequireRawIsa = true; rawIsaIsInherited = true; } if (instancesRequireRawIsa) { cls->setInstancesRequireRawIsa(rawIsaIsInherited); } // SUPPORT_NONPOINTER_ISA #endif // Update superclass and metaclass in case of remapping cls->superclass = supercls; cls->initClassIsa(metacls); // Reconcile instance variable offsets / layout. // This may reallocate class_ro_t, updating our ro variable. if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro); // Set fastInstanceSize if it wasn't set already. cls->setInstanceSize(ro->instanceSize); // Copy some flags from ro to rw if (ro->flags & RO_HAS_CXX_STRUCTORS) { cls->setHasCxxDtor(); if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) { cls->setHasCxxCtor(); } } // Connect this class to its superclass's subclass lists if (supercls) { addSubclass(supercls, cls); } else { addRootClass(cls); } // Attach categories methodizeClass(cls); return cls; } /*********************************************************************** * missingWeakSuperclass * Return YES if some superclass of cls was weak-linked and is missing. **********************************************************************/ static bool missingWeakSuperclass(Class cls) { assert(!cls->isRealized()); if (!cls->superclass) { // superclass nil. This is normal for root classes only. return (!(cls->data()->flags & RO_ROOT)); } else { // superclass not nil. Check if a higher superclass is missing. Class supercls = remapClass(cls->superclass); assert(cls != cls->superclass); assert(cls != supercls); if (!supercls) return YES; if (supercls->isRealized()) return NO; return missingWeakSuperclass(supercls); } } /*********************************************************************** * realizeAllClassesInImage * Non-lazily realizes all unrealized classes in the given image. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void realizeAllClassesInImage(header_info *hi) { runtimeLock.assertLocked(); size_t count, i; classref_t *classlist; if (hi->areAllClassesRealized()) return; classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { realizeClass(remapClass(classlist[i])); } hi->setAllClassesRealized(YES); } /*********************************************************************** * realizeAllClasses * Non-lazily realizes all unrealized classes in all known images. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void realizeAllClasses(void) { runtimeLock.assertLocked(); header_info *hi; for (hi = FirstHeader; hi; hi = hi->getNext()) { realizeAllClassesInImage(hi); } } /*********************************************************************** * _objc_allocateFutureClass * Allocate an unresolved future class for the given class name. * Returns any existing allocation if one was already made. * Assumes the named class doesn't exist yet. * Locking: acquires runtimeLock **********************************************************************/ Class _objc_allocateFutureClass(const char *name) { mutex_locker_t lock(runtimeLock); Class cls; NXMapTable *map = futureNamedClasses(); if ((cls = (Class)NXMapGet(map, name))) { // Already have a future class for this name. return cls; } cls = _calloc_class(sizeof(objc_class)); addFutureNamedClass(name, cls); return cls; } /*********************************************************************** * objc_getFutureClass. Return the id of the named class. * If the class does not exist, return an uninitialized class * structure that will be used for the class when and if it * does get loaded. * Not thread safe. **********************************************************************/ Class objc_getFutureClass(const char *name) { Class cls; // YES unconnected, NO class handler // (unconnected is OK because it will someday be the real class) cls = look_up_class(name, YES, NO); if (cls) { if (PrintFuture) { _objc_inform("FUTURE: found %p already in use for %s", (void*)cls, name); } return cls; } // No class or future class with that name yet. Make one. // fixme not thread-safe with respect to // simultaneous library load or getFutureClass. return _objc_allocateFutureClass(name); } BOOL _class_isFutureClass(Class cls) { return cls && cls->isFuture(); } /*********************************************************************** * _objc_flush_caches * Flushes all caches. * (Historical behavior: flush caches for cls, its metaclass, * and subclasses thereof. Nil flushes all classes.) * Locking: acquires runtimeLock **********************************************************************/ static void flushCaches(Class cls) { runtimeLock.assertLocked(); mutex_locker_t lock(cacheUpdateLock); if (cls) { foreach_realized_class_and_subclass(cls, ^(Class c){ cache_erase_nolock(c); }); } else { foreach_realized_class_and_metaclass(^(Class c){ cache_erase_nolock(c); }); } } void _objc_flush_caches(Class cls) { { mutex_locker_t lock(runtimeLock); flushCaches(cls); if (cls && cls->superclass && cls != cls->getIsa()) { flushCaches(cls->getIsa()); } else { // cls is a root class or root metaclass. Its metaclass is itself // or a subclass so the metaclass caches were already flushed. } } if (!cls) { // collectALot if cls==nil mutex_locker_t lock(cacheUpdateLock); cache_collect(true); } } /*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock **********************************************************************/ void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) { mutex_locker_t lock(runtimeLock); return map_images_nolock(count, paths, mhdrs); } /*********************************************************************** * load_images * Process +load in the given images which are being mapped in by dyld. * * Locking: write-locks runtimeLock and loadMethodLock **********************************************************************/ extern bool hasLoadMethods(const headerType *mhdr); extern void prepare_load_methods(const headerType *mhdr); void load_images(const char *path __unused, const struct mach_header *mh) { // Return without taking locks if there are no +load methods here. if (!hasLoadMethods((const headerType *)mh)) return; recursive_mutex_locker_t lock(loadMethodLock); // Discover load methods { mutex_locker_t lock2(runtimeLock); prepare_load_methods((const headerType *)mh); } // Call +load methods (without runtimeLock - re-entrant) call_load_methods(); } /*********************************************************************** * unmap_image * Process the given image which is about to be unmapped by dyld. * * Locking: write-locks runtimeLock and loadMethodLock **********************************************************************/ void unmap_image(const char *path __unused, const struct mach_header *mh) { recursive_mutex_locker_t lock(loadMethodLock); mutex_locker_t lock2(runtimeLock); unmap_image_nolock(mh); } /*********************************************************************** * mustReadClasses * Preflight check in advance of readClass() from an image. **********************************************************************/ bool mustReadClasses(header_info *hi) { const char *reason; // If the image is not preoptimized then we must read classes. if (!hi->isPreoptimized()) { reason = nil; // Don't log this one because it is noisy. goto readthem; } // If iOS simulator then we must read classes. #if TARGET_OS_SIMULATOR reason = "the image is for iOS simulator"; goto readthem; #endif assert(!hi->isBundle()); // no MH_BUNDLE in shared cache // If the image may have missing weak superclasses then we must read classes if (!noMissingWeakSuperclasses()) { reason = "the image may contain classes with missing weak superclasses"; goto readthem; } // If there are unresolved future classes then we must read classes. if (haveFutureNamedClasses()) { reason = "there are unresolved future classes pending"; goto readthem; } // readClass() does not need to do anything. return NO; readthem: if (PrintPreopt && reason) { _objc_inform("PREOPTIMIZATION: reading classes manually from %s " "because %s", hi->fname(), reason); } return YES; } /*********************************************************************** * readClass * Read a class and metaclass as written by a compiler. * Returns the new class pointer. This could be: * - cls * - nil (cls has a missing weak-linked superclass) * - something else (space for this class was reserved by a future class) * * Note that all work performed by this function is preflighted by * mustReadClasses(). Do not change this function without updating that one. * * Locking: runtimeLock acquired by map_images or objc_readClassPair **********************************************************************/ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->mangledName(); if (missingWeakSuperclass(cls)) { // No superclass (probably weak-linked). // Disavow any knowledge of this subclass. if (PrintConnecting) { _objc_inform("CLASS: IGNORING class '%s' with " "missing weak-linked superclass", cls->nameForLogging()); } addRemappedClass(cls, nil); cls->superclass = nil; return nil; } // Note: Class __ARCLite__'s hack does not go through here. // Class structure fixups that apply to it also need to be // performed in non-lazy realization below. // These fields should be set to zero because of the // binding of _objc_empty_vtable, but OS X 10.8's dyld // does not bind shared cache absolute symbols as expected. // This (and the __ARCLite__ hack below) can be removed // once the simulator drops 10.8 support. #if TARGET_OS_SIMULATOR if (cls->cache._mask) cls->cache._mask = 0; if (cls->cache._occupied) cls->cache._occupied = 0; if (cls->ISA()->cache._mask) cls->ISA()->cache._mask = 0; if (cls->ISA()->cache._occupied) cls->ISA()->cache._occupied = 0; #endif Class replacing = nil; if (Class newCls = popFutureNamedClass(mangledName)) { // This name was previously allocated as a future class. // Copy objc_class to future class's struct. // Preserve future's rw data block. if (newCls->isAnySwift()) { _objc_fatal("Can't complete future class request for '%s' " "because the real class is too big.", cls->nameForLogging()); } class_rw_t *rw = newCls->data(); const class_ro_t *old_ro = rw->ro; memcpy(newCls, cls, sizeof(objc_class)); rw->ro = (class_ro_t *)newCls->data(); newCls->setData(rw); freeIfMutable((char *)old_ro->name); free((void *)old_ro); addRemappedClass(cls, newCls); replacing = cls; cls = newCls; } if (headerIsPreoptimized && !replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // assert(cls == getClass(name)); assert(getClass(mangledName)); } else { addNamedClass(cls, mangledName, replacing); addClassTableEntry(cls); } // for future reference: shared cache never contains MH_BUNDLEs if (headerIsBundle) { cls->data()->flags |= RO_FROM_BUNDLE; cls->ISA()->data()->flags |= RO_FROM_BUNDLE; } return cls; } /*********************************************************************** * readProtocol * Read a protocol as written by a compiler. **********************************************************************/ static void readProtocol(protocol_t *newproto, Class protocol_class, NXMapTable *protocol_map, bool headerIsPreoptimized, bool headerIsBundle) { // This is not enough to make protocols in unloaded bundles safe, // but it does prevent crashes when looking up unrelated protocols. auto insertFn = headerIsBundle ? NXMapKeyCopyingInsert : NXMapInsert; protocol_t *oldproto = (protocol_t *)getProtocol(newproto->mangledName); if (oldproto) { // Some other definition already won. if (PrintProtocols) { _objc_inform("PROTOCOLS: protocol at %p is %s " "(duplicate of %p)", newproto, oldproto->nameForLogging(), oldproto); } } else if (headerIsPreoptimized) { // Shared cache initialized the protocol object itself, // but in order to allow out-of-cache replacement we need // to add it to the protocol table now. protocol_t *cacheproto = (protocol_t *) getPreoptimizedProtocol(newproto->mangledName); protocol_t *installedproto; if (cacheproto && cacheproto != newproto) { // Another definition in the shared cache wins (because // everything in the cache was fixed up to point to it). installedproto = cacheproto; } else { // This definition wins. installedproto = newproto; } assert(installedproto->getIsa() == protocol_class); assert(installedproto->size >= sizeof(protocol_t)); insertFn(protocol_map, installedproto->mangledName, installedproto); if (PrintProtocols) { _objc_inform("PROTOCOLS: protocol at %p is %s", installedproto, installedproto->nameForLogging()); if (newproto != installedproto) { _objc_inform("PROTOCOLS: protocol at %p is %s " "(duplicate of %p)", newproto, installedproto->nameForLogging(), installedproto); } } } else if (newproto->size >= sizeof(protocol_t)) { // New protocol from an un-preoptimized image // with sufficient storage. Fix it up in place. // fixme duplicate protocols from unloadable bundle newproto->initIsa(protocol_class); // fixme pinned insertFn(protocol_map, newproto->mangledName, newproto); if (PrintProtocols) { _objc_inform("PROTOCOLS: protocol at %p is %s", newproto, newproto->nameForLogging()); } } else { // New protocol from an un-preoptimized image // with insufficient storage. Reallocate it. // fixme duplicate protocols from unloadable bundle size_t size = max(sizeof(protocol_t), (size_t)newproto->size); protocol_t *installedproto = (protocol_t *)calloc(size, 1); memcpy(installedproto, newproto, newproto->size); installedproto->size = (typeof(installedproto->size))size; installedproto->initIsa(protocol_class); // fixme pinned insertFn(protocol_map, installedproto->mangledName, installedproto); if (PrintProtocols) { _objc_inform("PROTOCOLS: protocol at %p is %s ", installedproto, installedproto->nameForLogging()); _objc_inform("PROTOCOLS: protocol at %p is %s " "(reallocated to %p)", newproto, installedproto->nameForLogging(), installedproto); } } } /*********************************************************************** * _read_images * Perform initial processing of the headers in the linked * list beginning with headerList. * * Called by: map_images_nolock * * Locking: runtimeLock acquired by map_images **********************************************************************/ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { header_info *hi; uint32_t hIndex; size_t count; size_t i; Class *resolvedFutureClasses = nil; size_t resolvedFutureClassCount = 0; static bool doneOnce; TimeLogger ts(PrintImageTimes); runtimeLock.assertLocked(); #define EACH_HEADER \ hIndex = 0; \ hIndex < hCount && (hi = hList[hIndex]); \ hIndex++ if (!doneOnce) { doneOnce = YES; #if SUPPORT_NONPOINTER_ISA // Disable non-pointer isa under some conditions. # if SUPPORT_INDEXED_ISA // Disable nonpointer isa if any image contains old Swift code for (EACH_HEADER) { if (hi->info()->containsSwift() && hi->info()->swiftVersion() < objc_image_info::SwiftVersion3) { DisableNonpointerIsa = true; if (PrintRawIsa) { _objc_inform("RAW ISA: disabling non-pointer isa because " "the app or a framework contains Swift code " "older than Swift 3.0"); } break; } } # endif # if TARGET_OS_OSX // Disable non-pointer isa if the app is too old // (linked before OS X 10.11) if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) { DisableNonpointerIsa = true; if (PrintRawIsa) { _objc_inform("RAW ISA: disabling non-pointer isa because " "the app is too old (SDK version " SDK_FORMAT ")", FORMAT_SDK(dyld_get_program_sdk_version())); } } // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section // New apps that load old extensions may need this. for (EACH_HEADER) { if (hi->mhdr()->filetype != MH_EXECUTE) continue; unsigned long size; if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) { DisableNonpointerIsa = true; if (PrintRawIsa) { _objc_inform("RAW ISA: disabling non-pointer isa because " "the app has a __DATA,__objc_rawisa section"); } } break; // assume only one MH_EXECUTE image } # endif #endif if (DisableTaggedPointers) { disableTaggedPointers(); } initializeTaggedPointerObfuscator(); if (PrintConnecting) { _objc_inform("CLASS: found %d classes during launch", totalClasses); } // namedClasses // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil); ts.log("IMAGE TIMES: first time tasks"); } // Discover classes. Fix up unresolved future classes. Mark bundle classes. for (EACH_HEADER) { classref_t *classlist = _getObjc2ClassList(hi, &count); if (! mustReadClasses(hi)) { // Image is sufficiently optimized that we need not call readClass() continue; } bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->isPreoptimized(); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); if (newCls != cls && newCls) { // Class was moved but not deleted. Currently this occurs // only when the new class resolved a future class. // Non-lazily realize the class below. resolvedFutureClasses = (Class *) realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; } } } ts.log("IMAGE TIMES: discover classes"); // Fix up remapped classes // Class list and nonlazy class list remain unremapped. // Class refs and super refs are remapped for message dispatching. if (!noClassesRemapped()) { for (EACH_HEADER) { Class *classrefs = _getObjc2ClassRefs(hi, &count); for (i = 0; i < count; i++) { remapClassRef(&classrefs[i]); } // fixme why doesn't test future1 catch the absence of this? classrefs = _getObjc2SuperRefs(hi, &count); for (i = 0; i < count; i++) { remapClassRef(&classrefs[i]); } } } ts.log("IMAGE TIMES: remap classes"); // Fix up @selector references static size_t UnfixedSelectors; { mutex_locker_t lock(selLock); for (EACH_HEADER) { if (hi->isPreoptimized()) continue; bool isBundle = hi->isBundle(); SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count; for (i = 0; i < count; i++) { const char *name = sel_cname(sels[i]); sels[i] = sel_registerNameNoLock(name, isBundle); } } } ts.log("IMAGE TIMES: fix up selector references"); #if SUPPORT_FIXUP // Fix up old objc_msgSend_fixup call sites for (EACH_HEADER) { message_ref_t *refs = _getObjc2MessageRefs(hi, &count); if (count == 0) continue; if (PrintVtables) { _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch " "call sites in %s", count, hi->fname()); } for (i = 0; i < count; i++) { fixupMessageRef(refs+i); } } ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); #endif // Discover protocols. Fix up protocol refs. for (EACH_HEADER) { extern objc_class OBJC_CLASS_$_Protocol; Class cls = (Class)&OBJC_CLASS_$_Protocol; assert(cls); NXMapTable *protocol_map = protocols(); bool isPreoptimized = hi->isPreoptimized(); bool isBundle = hi->isBundle(); protocol_t **protolist = _getObjc2ProtocolList(hi, &count); for (i = 0; i < count; i++) { readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); } } ts.log("IMAGE TIMES: discover protocols"); // Fix up @protocol references // Preoptimized images may have the right // answer already but we don't know for sure. for (EACH_HEADER) { protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; i++) { remapProtocolRef(&protolist[i]); } } ts.log("IMAGE TIMES: fix up @protocol references"); // Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { classref_t *classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (!cls) continue; // hack for class __ARCLite__, which didn't get this above #if TARGET_OS_SIMULATOR if (cls->cache._buckets == (void*)&_objc_empty_cache && (cls->cache._mask || cls->cache._occupied)) { cls->cache._mask = 0; cls->cache._occupied = 0; } if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache && (cls->ISA()->cache._mask || cls->ISA()->cache._occupied)) { cls->ISA()->cache._mask = 0; cls->ISA()->cache._occupied = 0; } #endif addClassTableEntry(cls); realizeClass(cls); } } ts.log("IMAGE TIMES: realize non-lazy classes"); // Realize newly-resolved future classes, in case CF manipulates them if (resolvedFutureClasses) { for (i = 0; i < resolvedFutureClassCount; i++) { realizeClass(resolvedFutureClasses[i]); resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/); } free(resolvedFutureClasses); } ts.log("IMAGE TIMES: realize future classes"); // Discover categories. for (EACH_HEADER) { category_t **catlist = _getObjc2CategoryList(hi, &count); bool hasClassProperties = hi->info()->hasCategoryClassProperties(); for (i = 0; i < count; i++) { category_t *cat = catlist[i]; Class cls = remapClass(cat->cls); if (!cls) { // Category's target class is missing (probably weak-linked). // Disavow any knowledge of this category. catlist[i] = nil; if (PrintConnecting) { _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " "missing weak-linked target class", cat->name, cat); } continue; } // Process this category. // First, register the category with its target class. // Then, rebuild the class's method lists (etc) if // the class is realized. bool classExists = NO; if (cat->instanceMethods || cat->protocols || cat->instanceProperties) { addUnattachedCategoryForClass(cat, cls, hi); if (cls->isRealized()) { remethodizeClass(cls); classExists = YES; } if (PrintConnecting) { _objc_inform("CLASS: found category -%s(%s) %s", cls->nameForLogging(), cat->name, classExists ? "on existing class" : ""); } } if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) { addUnattachedCategoryForClass(cat, cls->ISA(), hi); if (cls->ISA()->isRealized()) { remethodizeClass(cls->ISA()); } if (PrintConnecting) { _objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name); } } } } ts.log("IMAGE TIMES: discover categories"); // Category discovery MUST BE LAST to avoid potential races // when other threads call the new category code before // this thread finishes its fixups. // +load handled by prepare_load_methods() if (DebugNonFragileIvars) { realizeAllClasses(); } // Print preoptimization statistics if (PrintPreopt) { static unsigned int PreoptTotalMethodLists; static unsigned int PreoptOptimizedMethodLists; static unsigned int PreoptTotalClasses; static unsigned int PreoptOptimizedClasses; for (EACH_HEADER) { if (hi->isPreoptimized()) { _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors " "in %s", hi->fname()); } else if (hi->info()->optimizedByDyld()) { _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors " "in %s", hi->fname()); } classref_t *classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (!cls) continue; PreoptTotalClasses++; if (hi->isPreoptimized()) { PreoptOptimizedClasses++; } const method_list_t *mlist; if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) { PreoptTotalMethodLists++; if (mlist->isFixedUp()) { PreoptOptimizedMethodLists++; } } if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) { PreoptTotalMethodLists++; if (mlist->isFixedUp()) { PreoptOptimizedMethodLists++; } } } } _objc_inform("PREOPTIMIZATION: %zu selector references not " "pre-optimized", UnfixedSelectors); _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted", PreoptOptimizedMethodLists, PreoptTotalMethodLists, PreoptTotalMethodLists ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists : 0.0); _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered", PreoptOptimizedClasses, PreoptTotalClasses, PreoptTotalClasses ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses : 0.0); _objc_inform("PREOPTIMIZATION: %zu protocol references not " "pre-optimized", UnfixedProtocolReferences); } #undef EACH_HEADER } /*********************************************************************** * prepare_load_methods * Schedule +load for classes in this image, any un-+load-ed * superclasses in other images, and any categories in this image. **********************************************************************/ // Recursively schedule +load for cls and any un-+load-ed superclasses. // cls must already be connected. static void schedule_class_load(Class cls) { if (!cls) return; assert(cls->isRealized()); // _read_images should realize if (cls->data()->flags & RW_LOADED) return; // Ensure superclass-first ordering schedule_class_load(cls->superclass); add_class_to_loadable_list(cls); cls->setInfo(RW_LOADED); } // Quick scan for +load methods that doesn't take a lock. bool hasLoadMethods(const headerType *mhdr) { size_t count; if (_getObjc2NonlazyClassList(mhdr, &count) && count > 0) return true; if (_getObjc2NonlazyCategoryList(mhdr, &count) && count > 0) return true; return false; } void prepare_load_methods(const headerType *mhdr) { size_t count, i; runtimeLock.assertLocked(); classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count); for (i = 0; i < count; i++) { schedule_class_load(remapClass(classlist[i])); } category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count); for (i = 0; i < count; i++) { category_t *cat = categorylist[i]; Class cls = remapClass(cat->cls); if (!cls) continue; // category for ignored weak-linked class realizeClass(cls); assert(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); } } /*********************************************************************** * _unload_image * Only handles MH_BUNDLE for now. * Locking: write-lock and loadMethodLock acquired by unmap_image **********************************************************************/ void _unload_image(header_info *hi) { size_t count, i; loadMethodLock.assertLocked(); runtimeLock.assertLocked(); // Unload unattached categories and categories waiting for +load. category_t **catlist = _getObjc2CategoryList(hi, &count); for (i = 0; i < count; i++) { category_t *cat = catlist[i]; if (!cat) continue; // category for ignored weak-linked class Class cls = remapClass(cat->cls); assert(cls); // shouldn't have live category for dead class // fixme for MH_DYLIB cat's class may have been unloaded already // unattached list removeUnattachedCategoryForClass(cat, cls); // +load queue remove_category_from_loadable_list(cat); } // Unload classes. // Gather classes from both __DATA,__objc_clslist // and __DATA,__objc_nlclslist. arclite's hack puts a class in the latter // only, and we need to unload that class if we unload an arclite image. NXHashTable *classes = NXCreateHashTable(NXPtrPrototype, 0, nil); classref_t *classlist; classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (cls) NXHashInsert(classes, cls); } classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (cls) NXHashInsert(classes, cls); } // First detach classes from each other. Then free each class. // This avoid bugs where this loop unloads a subclass before its superclass NXHashState hs; Class cls; hs = NXInitHashState(classes); while (NXNextHashState(classes, &hs, (void**)&cls)) { remove_class_from_loadable_list(cls); detach_class(cls->ISA(), YES); detach_class(cls, NO); } hs = NXInitHashState(classes); while (NXNextHashState(classes, &hs, (void**)&cls)) { free_class(cls->ISA()); free_class(cls); } NXFreeHashTable(classes); // XXX FIXME -- Clean up protocols: // Support unloading protocols at dylib/image unload time // fixme DebugUnload } /*********************************************************************** * method_getDescription * Returns a pointer to this method's objc_method_description. * Locking: none **********************************************************************/ struct objc_method_description * method_getDescription(Method m) { if (!m) return nil; return (struct objc_method_description *)m; } IMP method_getImplementation(Method m) { return m ? m->imp : nil; } /*********************************************************************** * method_getName * Returns this method's selector. * The method must not be nil. * The method must already have been fixed-up. * Locking: none **********************************************************************/ SEL method_getName(Method m) { if (!m) return nil; assert(m->name == sel_registerName(sel_getName(m->name))); return m->name; } /*********************************************************************** * method_getTypeEncoding * Returns this method's old-style type encoding string. * The method must not be nil. * Locking: none **********************************************************************/ const char * method_getTypeEncoding(Method m) { if (!m) return nil; return m->types; } /*********************************************************************** * method_setImplementation * Sets this method's implementation to imp. * The previous implementation is returned. **********************************************************************/ static IMP _method_setImplementation(Class cls, method_t *m, IMP imp) { runtimeLock.assertLocked(); if (!m) return nil; if (!imp) return nil; IMP old = m->imp; m->imp = imp; // Cache updates are slow if cls is nil (i.e. unknown) // RR/AWZ updates are slow if cls is nil (i.e. unknown) // fixme build list of classes whose Methods are known externally? flushCaches(cls); updateCustomRR_AWZ(cls, m); return old; } IMP method_setImplementation(Method m, IMP imp) { // Don't know the class - will be slow if RR/AWZ are affected // fixme build list of classes whose Methods are known externally? mutex_locker_t lock(runtimeLock); return _method_setImplementation(Nil, m, imp); } void method_exchangeImplementations(Method m1, Method m2) { if (!m1 || !m2) return; mutex_locker_t lock(runtimeLock); IMP m1_imp = m1->imp; m1->imp = m2->imp; m2->imp = m1_imp; // RR/AWZ updates are slow because class is unknown // Cache updates are slow because class is unknown // fixme build list of classes whose Methods are known externally? flushCaches(nil); updateCustomRR_AWZ(nil, m1); updateCustomRR_AWZ(nil, m2); } /*********************************************************************** * ivar_getOffset * fixme * Locking: none **********************************************************************/ ptrdiff_t ivar_getOffset(Ivar ivar) { if (!ivar) return 0; return *ivar->offset; } /*********************************************************************** * ivar_getName * fixme * Locking: none **********************************************************************/ const char * ivar_getName(Ivar ivar) { if (!ivar) return nil; return ivar->name; } /*********************************************************************** * ivar_getTypeEncoding * fixme * Locking: none **********************************************************************/ const char * ivar_getTypeEncoding(Ivar ivar) { if (!ivar) return nil; return ivar->type; } const char *property_getName(objc_property_t prop) { return prop->name; } const char *property_getAttributes(objc_property_t prop) { return prop->attributes; } objc_property_attribute_t *property_copyAttributeList(objc_property_t prop, unsigned int *outCount) { if (!prop) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); return copyPropertyAttributeList(prop->attributes,outCount); } char * property_copyAttributeValue(objc_property_t prop, const char *name) { if (!prop || !name || *name == '\0') return nil; mutex_locker_t lock(runtimeLock); return copyPropertyAttributeValue(prop->attributes, name); } /*********************************************************************** * getExtendedTypesIndexesForMethod * Returns: * a is the count of methods in all method lists before m's method list * b is the index of m in m's method list * a+b is the index of m's extended types in the extended types array **********************************************************************/ static void getExtendedTypesIndexesForMethod(protocol_t *proto, const method_t *m, bool isRequiredMethod, bool isInstanceMethod, uint32_t& a, uint32_t &b) { a = 0; if (proto->instanceMethods) { if (isRequiredMethod && isInstanceMethod) { b = proto->instanceMethods->indexOfMethod(m); return; } a += proto->instanceMethods->count; } if (proto->classMethods) { if (isRequiredMethod && !isInstanceMethod) { b = proto->classMethods->indexOfMethod(m); return; } a += proto->classMethods->count; } if (proto->optionalInstanceMethods) { if (!isRequiredMethod && isInstanceMethod) { b = proto->optionalInstanceMethods->indexOfMethod(m); return; } a += proto->optionalInstanceMethods->count; } if (proto->optionalClassMethods) { if (!isRequiredMethod && !isInstanceMethod) { b = proto->optionalClassMethods->indexOfMethod(m); return; } a += proto->optionalClassMethods->count; } } /*********************************************************************** * getExtendedTypesIndexForMethod * Returns the index of m's extended types in proto's extended types array. **********************************************************************/ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t *m, bool isRequiredMethod, bool isInstanceMethod) { uint32_t a; uint32_t b; getExtendedTypesIndexesForMethod(proto, m, isRequiredMethod, isInstanceMethod, a, b); return a + b; } /*********************************************************************** * fixupProtocolMethodList * Fixes up a single method list in a protocol. **********************************************************************/ static void fixupProtocolMethodList(protocol_t *proto, method_list_t *mlist, bool required, bool instance) { runtimeLock.assertLocked(); if (!mlist) return; if (mlist->isFixedUp()) return; const char **extTypes = proto->extendedMethodTypes(); fixupMethodList(mlist, true/*always copy for simplicity*/, !extTypes/*sort if no extended method types*/); if (extTypes) { // Sort method list and extended method types together. // fixupMethodList() can't do this. // fixme COW stomp uint32_t count = mlist->count; uint32_t prefix; uint32_t junk; getExtendedTypesIndexesForMethod(proto, &mlist->get(0), required, instance, prefix, junk); for (uint32_t i = 0; i < count; i++) { for (uint32_t j = i+1; j < count; j++) { method_t& mi = mlist->get(i); method_t& mj = mlist->get(j); if (mi.name > mj.name) { std::swap(mi, mj); std::swap(extTypes[prefix+i], extTypes[prefix+j]); } } } } } /*********************************************************************** * fixupProtocol * Fixes up all of a protocol's method lists. **********************************************************************/ static void fixupProtocol(protocol_t *proto) { runtimeLock.assertLocked(); if (proto->protocols) { for (uintptr_t i = 0; i < proto->protocols->count; i++) { protocol_t *sub = remapProtocol(proto->protocols->list[i]); if (!sub->isFixedUp()) fixupProtocol(sub); } } fixupProtocolMethodList(proto, proto->instanceMethods, YES, YES); fixupProtocolMethodList(proto, proto->classMethods, YES, NO); fixupProtocolMethodList(proto, proto->optionalInstanceMethods, NO, YES); fixupProtocolMethodList(proto, proto->optionalClassMethods, NO, NO); // fixme memory barrier so we can check this with no lock proto->setFixedUp(); } /*********************************************************************** * fixupProtocolIfNeeded * Fixes up all of a protocol's method lists if they aren't fixed up already. * Locking: write-locks runtimeLock. **********************************************************************/ static void fixupProtocolIfNeeded(protocol_t *proto) { runtimeLock.assertUnlocked(); assert(proto); if (!proto->isFixedUp()) { mutex_locker_t lock(runtimeLock); fixupProtocol(proto); } } static method_list_t * getProtocolMethodList(protocol_t *proto, bool required, bool instance) { method_list_t **mlistp = nil; if (required) { if (instance) { mlistp = &proto->instanceMethods; } else { mlistp = &proto->classMethods; } } else { if (instance) { mlistp = &proto->optionalInstanceMethods; } else { mlistp = &proto->optionalClassMethods; } } return *mlistp; } /*********************************************************************** * protocol_getMethod_nolock * Locking: runtimeLock must be held by the caller **********************************************************************/ static method_t * protocol_getMethod_nolock(protocol_t *proto, SEL sel, bool isRequiredMethod, bool isInstanceMethod, bool recursive) { runtimeLock.assertLocked(); if (!proto || !sel) return nil; assert(proto->isFixedUp()); method_list_t *mlist = getProtocolMethodList(proto, isRequiredMethod, isInstanceMethod); if (mlist) { method_t *m = search_method_list(mlist, sel); if (m) return m; } if (recursive && proto->protocols) { method_t *m; for (uint32_t i = 0; i < proto->protocols->count; i++) { protocol_t *realProto = remapProtocol(proto->protocols->list[i]); m = protocol_getMethod_nolock(realProto, sel, isRequiredMethod, isInstanceMethod, true); if (m) return m; } } return nil; } /*********************************************************************** * protocol_getMethod * fixme * Locking: acquires runtimeLock **********************************************************************/ Method protocol_getMethod(protocol_t *proto, SEL sel, bool isRequiredMethod, bool isInstanceMethod, bool recursive) { if (!proto) return nil; fixupProtocolIfNeeded(proto); mutex_locker_t lock(runtimeLock); return protocol_getMethod_nolock(proto, sel, isRequiredMethod, isInstanceMethod, recursive); } /*********************************************************************** * protocol_getMethodTypeEncoding_nolock * Return the @encode string for the requested protocol method. * Returns nil if the compiler did not emit any extended @encode data. * Locking: runtimeLock must be held by the caller **********************************************************************/ const char * protocol_getMethodTypeEncoding_nolock(protocol_t *proto, SEL sel, bool isRequiredMethod, bool isInstanceMethod) { runtimeLock.assertLocked(); if (!proto) return nil; if (!proto->extendedMethodTypes()) return nil; assert(proto->isFixedUp()); method_t *m = protocol_getMethod_nolock(proto, sel, isRequiredMethod, isInstanceMethod, false); if (m) { uint32_t i = getExtendedTypesIndexForMethod(proto, m, isRequiredMethod, isInstanceMethod); return proto->extendedMethodTypes()[i]; } // No method with that name. Search incorporated protocols. if (proto->protocols) { for (uintptr_t i = 0; i < proto->protocols->count; i++) { const char *enc = protocol_getMethodTypeEncoding_nolock(remapProtocol(proto->protocols->list[i]), sel, isRequiredMethod, isInstanceMethod); if (enc) return enc; } } return nil; } /*********************************************************************** * _protocol_getMethodTypeEncoding * Return the @encode string for the requested protocol method. * Returns nil if the compiler did not emit any extended @encode data. * Locking: acquires runtimeLock **********************************************************************/ const char * _protocol_getMethodTypeEncoding(Protocol *proto_gen, SEL sel, BOOL isRequiredMethod, BOOL isInstanceMethod) { protocol_t *proto = newprotocol(proto_gen); if (!proto) return nil; fixupProtocolIfNeeded(proto); mutex_locker_t lock(runtimeLock); return protocol_getMethodTypeEncoding_nolock(proto, sel, isRequiredMethod, isInstanceMethod); } /*********************************************************************** * protocol_t::demangledName * Returns the (Swift-demangled) name of the given protocol. * Locking: none **********************************************************************/ const char * protocol_t::demangledName() { assert(hasDemangledNameField()); if (! _demangledName) { char *de = copySwiftV1DemangledName(mangledName, true/*isProtocol*/); if (! OSAtomicCompareAndSwapPtrBarrier(nil, (void*)(de ?: mangledName), (void**)&_demangledName)) { if (de) free(de); } } return _demangledName; } /*********************************************************************** * protocol_getName * Returns the (Swift-demangled) name of the given protocol. * Locking: runtimeLock must not be held by the caller **********************************************************************/ const char * protocol_getName(Protocol *proto) { if (!proto) return "nil"; else return newprotocol(proto)->demangledName(); } /*********************************************************************** * protocol_getInstanceMethodDescription * Returns the description of a named instance method. * Locking: runtimeLock must not be held by the caller **********************************************************************/ struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod) { Method m = protocol_getMethod(newprotocol(p), aSel, isRequiredMethod, isInstanceMethod, true); if (m) return *method_getDescription(m); else return (struct objc_method_description){nil, nil}; } /*********************************************************************** * protocol_conformsToProtocol_nolock * Returns YES if self conforms to other. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static bool protocol_conformsToProtocol_nolock(protocol_t *self, protocol_t *other) { runtimeLock.assertLocked(); if (!self || !other) { return NO; } // protocols need not be fixed up if (0 == strcmp(self->mangledName, other->mangledName)) { return YES; } if (self->protocols) { uintptr_t i; for (i = 0; i < self->protocols->count; i++) { protocol_t *proto = remapProtocol(self->protocols->list[i]); if (0 == strcmp(other->mangledName, proto->mangledName)) { return YES; } if (protocol_conformsToProtocol_nolock(proto, other)) { return YES; } } } return NO; } /*********************************************************************** * protocol_conformsToProtocol * Returns YES if self conforms to other. * Locking: acquires runtimeLock **********************************************************************/ BOOL protocol_conformsToProtocol(Protocol *self, Protocol *other) { mutex_locker_t lock(runtimeLock); return protocol_conformsToProtocol_nolock(newprotocol(self), newprotocol(other)); } /*********************************************************************** * protocol_isEqual * Return YES if two protocols are equal (i.e. conform to each other) * Locking: acquires runtimeLock **********************************************************************/ BOOL protocol_isEqual(Protocol *self, Protocol *other) { if (self == other) return YES; if (!self || !other) return NO; if (!protocol_conformsToProtocol(self, other)) return NO; if (!protocol_conformsToProtocol(other, self)) return NO; return YES; } /*********************************************************************** * protocol_copyMethodDescriptionList * Returns descriptions of a protocol's methods. * Locking: acquires runtimeLock **********************************************************************/ struct objc_method_description * protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod,BOOL isInstanceMethod, unsigned int *outCount) { protocol_t *proto = newprotocol(p); struct objc_method_description *result = nil; unsigned int count = 0; if (!proto) { if (outCount) *outCount = 0; return nil; } fixupProtocolIfNeeded(proto); mutex_locker_t lock(runtimeLock); method_list_t *mlist = getProtocolMethodList(proto, isRequiredMethod, isInstanceMethod); if (mlist) { result = (struct objc_method_description *) calloc(mlist->count + 1, sizeof(struct objc_method_description)); for (const auto& meth : *mlist) { result[count].name = meth.name; result[count].types = (char *)meth.types; count++; } } if (outCount) *outCount = count; return result; } /*********************************************************************** * protocol_getProperty * fixme * Locking: runtimeLock must be held by the caller **********************************************************************/ static property_t * protocol_getProperty_nolock(protocol_t *proto, const char *name, bool isRequiredProperty, bool isInstanceProperty) { runtimeLock.assertLocked(); if (!isRequiredProperty) { // Only required properties are currently supported. return nil; } property_list_t *plist = isInstanceProperty ? proto->instanceProperties : proto->classProperties(); if (plist) { for (auto& prop : *plist) { if (0 == strcmp(name, prop.name)) { return ∝ } } } if (proto->protocols) { uintptr_t i; for (i = 0; i < proto->protocols->count; i++) { protocol_t *p = remapProtocol(proto->protocols->list[i]); property_t *prop = protocol_getProperty_nolock(p, name, isRequiredProperty, isInstanceProperty); if (prop) return prop; } } return nil; } objc_property_t protocol_getProperty(Protocol *p, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty) { if (!p || !name) return nil; mutex_locker_t lock(runtimeLock); return (objc_property_t) protocol_getProperty_nolock(newprotocol(p), name, isRequiredProperty, isInstanceProperty); } /*********************************************************************** * protocol_copyPropertyList * protocol_copyPropertyList2 * fixme * Locking: acquires runtimeLock **********************************************************************/ static property_t ** copyPropertyList(property_list_t *plist, unsigned int *outCount) { property_t **result = nil; unsigned int count = 0; if (plist) { count = plist->count; } if (count > 0) { result = (property_t **)malloc((count+1) * sizeof(property_t *)); count = 0; for (auto& prop : *plist) { result[count++] = ∝ } result[count] = nil; } if (outCount) *outCount = count; return result; } objc_property_t * protocol_copyPropertyList2(Protocol *proto, unsigned int *outCount, BOOL isRequiredProperty, BOOL isInstanceProperty) { if (!proto || !isRequiredProperty) { // Optional properties are not currently supported. if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); property_list_t *plist = isInstanceProperty ? newprotocol(proto)->instanceProperties : newprotocol(proto)->classProperties(); return (objc_property_t *)copyPropertyList(plist, outCount); } objc_property_t * protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) { return protocol_copyPropertyList2(proto, outCount, YES/*required*/, YES/*instance*/); } /*********************************************************************** * protocol_copyProtocolList * Copies this protocol's incorporated protocols. * Does not copy those protocol's incorporated protocols in turn. * Locking: acquires runtimeLock **********************************************************************/ Protocol * __unsafe_unretained * protocol_copyProtocolList(Protocol *p, unsigned int *outCount) { unsigned int count = 0; Protocol **result = nil; protocol_t *proto = newprotocol(p); if (!proto) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); if (proto->protocols) { count = (unsigned int)proto->protocols->count; } if (count > 0) { result = (Protocol **)malloc((count+1) * sizeof(Protocol *)); unsigned int i; for (i = 0; i < count; i++) { result[i] = (Protocol *)remapProtocol(proto->protocols->list[i]); } result[i] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * objc_allocateProtocol * Creates a new protocol. The protocol may not be used until * objc_registerProtocol() is called. * Returns nil if a protocol with the same name already exists. * Locking: acquires runtimeLock **********************************************************************/ Protocol * objc_allocateProtocol(const char *name) { mutex_locker_t lock(runtimeLock); if (getProtocol(name)) { return nil; } protocol_t *result = (protocol_t *)calloc(sizeof(protocol_t), 1); extern objc_class OBJC_CLASS_$___IncompleteProtocol; Class cls = (Class)&OBJC_CLASS_$___IncompleteProtocol; result->initProtocolIsa(cls); result->size = sizeof(protocol_t); // fixme mangle the name if it looks swift-y? result->mangledName = strdupIfMutable(name); // fixme reserve name without installing return (Protocol *)result; } /*********************************************************************** * objc_registerProtocol * Registers a newly-constructed protocol. The protocol is now * ready for use and immutable. * Locking: acquires runtimeLock **********************************************************************/ void objc_registerProtocol(Protocol *proto_gen) { protocol_t *proto = newprotocol(proto_gen); mutex_locker_t lock(runtimeLock); extern objc_class OBJC_CLASS_$___IncompleteProtocol; Class oldcls = (Class)&OBJC_CLASS_$___IncompleteProtocol; extern objc_class OBJC_CLASS_$_Protocol; Class cls = (Class)&OBJC_CLASS_$_Protocol; if (proto->ISA() == cls) { _objc_inform("objc_registerProtocol: protocol '%s' was already " "registered!", proto->nameForLogging()); return; } if (proto->ISA() != oldcls) { _objc_inform("objc_registerProtocol: protocol '%s' was not allocated " "with objc_allocateProtocol!", proto->nameForLogging()); return; } // NOT initProtocolIsa(). The protocol object may already // have been retained and we must preserve that count. proto->changeIsa(cls); NXMapKeyCopyingInsert(protocols(), proto->mangledName, proto); } /*********************************************************************** * protocol_addProtocol * Adds an incorporated protocol to another protocol. * No method enforcement is performed. * `proto` must be under construction. `addition` must not. * Locking: acquires runtimeLock **********************************************************************/ void protocol_addProtocol(Protocol *proto_gen, Protocol *addition_gen) { protocol_t *proto = newprotocol(proto_gen); protocol_t *addition = newprotocol(addition_gen); extern objc_class OBJC_CLASS_$___IncompleteProtocol; Class cls = (Class)&OBJC_CLASS_$___IncompleteProtocol; if (!proto_gen) return; if (!addition_gen) return; mutex_locker_t lock(runtimeLock); if (proto->ISA() != cls) { _objc_inform("protocol_addProtocol: modified protocol '%s' is not " "under construction!", proto->nameForLogging()); return; } if (addition->ISA() == cls) { _objc_inform("protocol_addProtocol: added protocol '%s' is still " "under construction!", addition->nameForLogging()); return; } protocol_list_t *protolist = proto->protocols; if (!protolist) { protolist = (protocol_list_t *) calloc(1, sizeof(protocol_list_t) + sizeof(protolist->list[0])); } else { protolist = (protocol_list_t *) realloc(protolist, protocol_list_size(protolist) + sizeof(protolist->list[0])); } protolist->list[protolist->count++] = (protocol_ref_t)addition; proto->protocols = protolist; } /*********************************************************************** * protocol_addMethodDescription * Adds a method to a protocol. The protocol must be under construction. * Locking: acquires runtimeLock **********************************************************************/ static void protocol_addMethod_nolock(method_list_t*& list, SEL name, const char *types) { if (!list) { list = (method_list_t *)calloc(sizeof(method_list_t), 1); list->entsizeAndFlags = sizeof(list->first); list->setFixedUp(); } else { size_t size = list->byteSize() + list->entsize(); list = (method_list_t *)realloc(list, size); } method_t& meth = list->get(list->count++); meth.name = name; meth.types = types ? strdupIfMutable(types) : ""; meth.imp = nil; } void protocol_addMethodDescription(Protocol *proto_gen, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod) { protocol_t *proto = newprotocol(proto_gen); extern objc_class OBJC_CLASS_$___IncompleteProtocol; Class cls = (Class)&OBJC_CLASS_$___IncompleteProtocol; if (!proto_gen) return; mutex_locker_t lock(runtimeLock); if (proto->ISA() != cls) { _objc_inform("protocol_addMethodDescription: protocol '%s' is not " "under construction!", proto->nameForLogging()); return; } if (isRequiredMethod && isInstanceMethod) { protocol_addMethod_nolock(proto->instanceMethods, name, types); } else if (isRequiredMethod && !isInstanceMethod) { protocol_addMethod_nolock(proto->classMethods, name, types); } else if (!isRequiredMethod && isInstanceMethod) { protocol_addMethod_nolock(proto->optionalInstanceMethods, name,types); } else /* !isRequiredMethod && !isInstanceMethod) */ { protocol_addMethod_nolock(proto->optionalClassMethods, name, types); } } /*********************************************************************** * protocol_addProperty * Adds a property to a protocol. The protocol must be under construction. * Locking: acquires runtimeLock **********************************************************************/ static void protocol_addProperty_nolock(property_list_t *&plist, const char *name, const objc_property_attribute_t *attrs, unsigned int count) { if (!plist) { plist = (property_list_t *)calloc(sizeof(property_list_t), 1); plist->entsizeAndFlags = sizeof(property_t); } else { plist = (property_list_t *) realloc(plist, sizeof(property_list_t) + plist->count * plist->entsize()); } property_t& prop = plist->get(plist->count++); prop.name = strdupIfMutable(name); prop.attributes = copyPropertyAttributeString(attrs, count); } void protocol_addProperty(Protocol *proto_gen, const char *name, const objc_property_attribute_t *attrs, unsigned int count, BOOL isRequiredProperty, BOOL isInstanceProperty) { protocol_t *proto = newprotocol(proto_gen); extern objc_class OBJC_CLASS_$___IncompleteProtocol; Class cls = (Class)&OBJC_CLASS_$___IncompleteProtocol; if (!proto) return; if (!name) return; mutex_locker_t lock(runtimeLock); if (proto->ISA() != cls) { _objc_inform("protocol_addProperty: protocol '%s' is not " "under construction!", proto->nameForLogging()); return; } if (isRequiredProperty && isInstanceProperty) { protocol_addProperty_nolock(proto->instanceProperties, name, attrs, count); } else if (isRequiredProperty && !isInstanceProperty) { protocol_addProperty_nolock(proto->_classProperties, name, attrs, count); } //else if (!isRequiredProperty && isInstanceProperty) { // protocol_addProperty_nolock(proto->optionalInstanceProperties, name, attrs, count); //} //else /* !isRequiredProperty && !isInstanceProperty) */ { // protocol_addProperty_nolock(proto->optionalClassProperties, name, attrs, count); //} } /*********************************************************************** * objc_getClassList * Returns pointers to all classes. * This requires all classes be realized, which is regretfully non-lazy. * Locking: acquires runtimeLock **********************************************************************/ int objc_getClassList(Class *buffer, int bufferLen) { mutex_locker_t lock(runtimeLock); realizeAllClasses(); __block int count = 0; foreach_realized_class_and_metaclass(^(Class cls) { if (!cls->isMetaClass()) count++; }); if (buffer) { __block int c = 0; foreach_realized_class_and_metaclass(^(Class cls) { if (c < bufferLen && !cls->isMetaClass()) { buffer[c++] = cls; } }); } return count; } /*********************************************************************** * objc_copyClassList * Returns pointers to all classes. * This requires all classes be realized, which is regretfully non-lazy. * * outCount may be nil. *outCount is the number of classes returned. * If the returned array is not nil, it is nil-terminated and must be * freed with free(). * Locking: write-locks runtimeLock **********************************************************************/ Class * objc_copyClassList(unsigned int *outCount) { mutex_locker_t lock(runtimeLock); realizeAllClasses(); Class *result = nil; __block unsigned int count = 0; foreach_realized_class_and_metaclass(^(Class cls) { if (!cls->isMetaClass()) count++; }); if (count > 0) { result = (Class *)malloc((1+count) * sizeof(Class)); __block unsigned int c = 0; foreach_realized_class_and_metaclass(^(Class cls) { if (!cls->isMetaClass()) { result[c++] = cls; } }); result[c] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * objc_copyProtocolList * Returns pointers to all protocols. * Locking: read-locks runtimeLock **********************************************************************/ Protocol * __unsafe_unretained * objc_copyProtocolList(unsigned int *outCount) { mutex_locker_t lock(runtimeLock); NXMapTable *protocol_map = protocols(); unsigned int count = NXCountMapTable(protocol_map); if (count == 0) { if (outCount) *outCount = 0; return nil; } Protocol **result = (Protocol **)malloc((count+1) * sizeof(Protocol*)); unsigned int i = 0; Protocol *proto; const char *name; NXMapState state = NXInitMapState(protocol_map); while (NXNextMapState(protocol_map, &state, (const void **)&name, (const void **)&proto)) { result[i++] = proto; } result[i++] = nil; assert(i == count+1); if (outCount) *outCount = count; return result; } /*********************************************************************** * objc_getProtocol * Get a protocol by name, or return nil * Locking: read-locks runtimeLock **********************************************************************/ Protocol *objc_getProtocol(const char *name) { mutex_locker_t lock(runtimeLock); return getProtocol(name); } /*********************************************************************** * class_copyMethodList * fixme * Locking: read-locks runtimeLock **********************************************************************/ Method * class_copyMethodList(Class cls, unsigned int *outCount) { unsigned int count = 0; Method *result = nil; if (!cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); assert(cls->isRealized()); count = cls->data()->methods.count(); if (count > 0) { result = (Method *)malloc((count + 1) * sizeof(Method)); count = 0; for (auto& meth : cls->data()->methods) { result[count++] = &meth; } result[count] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * class_copyIvarList * fixme * Locking: read-locks runtimeLock **********************************************************************/ Ivar * class_copyIvarList(Class cls, unsigned int *outCount) { const ivar_list_t *ivars; Ivar *result = nil; unsigned int count = 0; if (!cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); assert(cls->isRealized()); if ((ivars = cls->data()->ro->ivars) && ivars->count) { result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar)); for (auto& ivar : *ivars) { if (!ivar.offset) continue; // anonymous bitfield result[count++] = &ivar; } result[count] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * class_copyPropertyList. Returns a heap block containing the * properties declared in the class, or nil if the class * declares no properties. Caller must free the block. * Does not copy any superclass's properties. * Locking: read-locks runtimeLock **********************************************************************/ objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount) { if (!cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); assert(cls->isRealized()); auto rw = cls->data(); property_t **result = nil; unsigned int count = rw->properties.count(); if (count > 0) { result = (property_t **)malloc((count + 1) * sizeof(property_t *)); count = 0; for (auto& prop : rw->properties) { result[count++] = ∝ } result[count] = nil; } if (outCount) *outCount = count; return (objc_property_t *)result; } /*********************************************************************** * objc_class::getLoadMethod * fixme * Called only from add_class_to_loadable_list. * Locking: runtimeLock must be read- or write-locked by the caller. **********************************************************************/ IMP objc_class::getLoadMethod() { runtimeLock.assertLocked(); const method_list_t *mlist; assert(isRealized()); assert(ISA()->isRealized()); assert(!isMetaClass()); assert(ISA()->isMetaClass()); mlist = ISA()->data()->ro->baseMethods(); if (mlist) { for (const auto& meth : *mlist) { const char *name = sel_cname(meth.name); if (0 == strcmp(name, "load")) { return meth.imp; } } } return nil; } /*********************************************************************** * _category_getName * Returns a category's name. * Locking: none **********************************************************************/ const char * _category_getName(Category cat) { return cat->name; } /*********************************************************************** * _category_getClassName * Returns a category's class's name * Called only from add_category_to_loadable_list and * remove_category_from_loadable_list for logging purposes. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ const char * _category_getClassName(Category cat) { runtimeLock.assertLocked(); return remapClass(cat->cls)->nameForLogging(); } /*********************************************************************** * _category_getClass * Returns a category's class * Called only by call_category_loads. * Locking: read-locks runtimeLock **********************************************************************/ Class _category_getClass(Category cat) { mutex_locker_t lock(runtimeLock); Class result = remapClass(cat->cls); assert(result->isRealized()); // ok for call_category_loads' usage return result; } /*********************************************************************** * _category_getLoadMethod * fixme * Called only from add_category_to_loadable_list * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ IMP _category_getLoadMethod(Category cat) { runtimeLock.assertLocked(); const method_list_t *mlist; mlist = cat->classMethods; if (mlist) { for (const auto& meth : *mlist) { const char *name = sel_cname(meth.name); if (0 == strcmp(name, "load")) { return meth.imp; } } } return nil; } /*********************************************************************** * category_t::propertiesForMeta * Return a category's instance or class properties. * hi is the image containing the category. **********************************************************************/ property_list_t * category_t::propertiesForMeta(bool isMeta, struct header_info *hi) { if (!isMeta) return instanceProperties; else if (hi->info()->hasCategoryClassProperties()) return _classProperties; else return nil; } /*********************************************************************** * class_copyProtocolList * fixme * Locking: read-locks runtimeLock **********************************************************************/ Protocol * __unsafe_unretained * class_copyProtocolList(Class cls, unsigned int *outCount) { unsigned int count = 0; Protocol **result = nil; if (!cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); assert(cls->isRealized()); count = cls->data()->protocols.count(); if (count > 0) { result = (Protocol **)malloc((count+1) * sizeof(Protocol *)); count = 0; for (const auto& proto : cls->data()->protocols) { result[count++] = (Protocol *)remapProtocol(proto); } result[count] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * objc_copyImageNames * Copies names of loaded images with ObjC contents. * * Locking: acquires runtimeLock **********************************************************************/ const char **objc_copyImageNames(unsigned int *outCount) { mutex_locker_t lock(runtimeLock); #if TARGET_OS_WIN32 const TCHAR **names = (const TCHAR **) malloc((HeaderCount+1) * sizeof(TCHAR *)); #else const char **names = (const char **) malloc((HeaderCount+1) * sizeof(char *)); #endif unsigned int count = 0; for (header_info *hi = FirstHeader; hi != nil; hi = hi->getNext()) { #if TARGET_OS_WIN32 if (hi->moduleName) { names[count++] = hi->moduleName; } #else const char *fname = hi->fname(); if (fname) { names[count++] = fname; } #endif } names[count] = nil; if (count == 0) { // Return nil instead of empty list if there are no images free((void *)names); names = nil; } if (outCount) *outCount = count; return names; } /*********************************************************************** * copyClassNamesForImage_nolock * Copies class names from the given image. * Missing weak-import classes are omitted. * Swift class names are demangled. * * Locking: runtimeLock must be held by the caller **********************************************************************/ const char ** copyClassNamesForImage_nolock(header_info *hi, unsigned int *outCount) { runtimeLock.assertLocked(); assert(hi); size_t count; classref_t *classlist = _getObjc2ClassList(hi, &count); const char **names = (const char **) malloc((count+1) * sizeof(const char *)); size_t shift = 0; for (size_t i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (cls) { names[i-shift] = cls->demangledName(true/*realize*/); } else { shift++; // ignored weak-linked class } } count -= shift; names[count] = nil; if (outCount) *outCount = (unsigned int)count; return names; } /*********************************************************************** * objc_copyClassNamesForImage * Copies class names from the named image. * The image name must be identical to dladdr's dli_fname value. * Missing weak-import classes are omitted. * Swift class names are demangled. * * Locking: acquires runtimeLock **********************************************************************/ const char ** objc_copyClassNamesForImage(const char *image, unsigned int *outCount) { if (!image) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); // Find the image. header_info *hi; for (hi = FirstHeader; hi != nil; hi = hi->getNext()) { #if TARGET_OS_WIN32 if (0 == wcscmp((TCHAR *)image, hi->moduleName)) break; #else if (0 == strcmp(image, hi->fname())) break; #endif } if (!hi) { if (outCount) *outCount = 0; return nil; } return copyClassNamesForImage_nolock(hi, outCount); } /*********************************************************************** * objc_copyClassNamesForImageHeader * Copies class names from the given image. * Missing weak-import classes are omitted. * Swift class names are demangled. * * Locking: acquires runtimeLock **********************************************************************/ const char ** objc_copyClassNamesForImageHeader(const struct mach_header *mh, unsigned int *outCount) { if (!mh) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); // Find the image. header_info *hi; for (hi = FirstHeader; hi != nil; hi = hi->getNext()) { if (hi->mhdr() == (const headerType *)mh) break; } if (!hi) { if (outCount) *outCount = 0; return nil; } return copyClassNamesForImage_nolock(hi, outCount); } /*********************************************************************** * saveTemporaryString * Save a string in a thread-local FIFO buffer. * This is suitable for temporary strings generated for logging purposes. **********************************************************************/ static void saveTemporaryString(char *str) { // Fixed-size FIFO. We free the first string, shift // the rest, and add the new string to the end. _objc_pthread_data *data = _objc_fetch_pthread_data(true); if (data->printableNames[0]) { free(data->printableNames[0]); } int last = countof(data->printableNames) - 1; for (int i = 0; i < last; i++) { data->printableNames[i] = data->printableNames[i+1]; } data->printableNames[last] = str; } /*********************************************************************** * objc_class::nameForLogging * Returns the class's name, suitable for display. * The returned memory is TEMPORARY. Print it or copy it immediately. * Locking: none **********************************************************************/ const char * objc_class::nameForLogging() { // Handle the easy case directly. if (isRealized() || isFuture()) { if (data()->demangledName) return data()->demangledName; } char *result; const char *name = mangledName(); char *de = copySwiftV1DemangledName(name); if (de) result = de; else result = strdup(name); saveTemporaryString(result); return result; } /*********************************************************************** * objc_class::demangledName * If realize=false, the class must already be realized or future. * Locking: If realize=true, runtimeLock must be held by the caller. **********************************************************************/ mutex_t DemangleCacheLock; static NXHashTable *DemangleCache; const char * objc_class::demangledName(bool realize) { // Return previously demangled name if available. if (isRealized() || isFuture()) { if (data()->demangledName) return data()->demangledName; } // Try demangling the mangled name. const char *mangled = mangledName(); char *de = copySwiftV1DemangledName(mangled); if (isRealized() || isFuture()) { // Class is already realized or future. // Save demangling result in rw data. // We may not own runtimeLock so use an atomic operation instead. if (! OSAtomicCompareAndSwapPtrBarrier(nil, (void*)(de ?: mangled), (void**)&data()->demangledName)) { if (de) free(de); } return data()->demangledName; } // Class is not yet realized. if (!de) { // Name is not mangled. Return it without caching. return mangled; } // Class is not yet realized and name is mangled. Realize the class. // Only objc_copyClassNamesForImage() should get here. // fixme lldb's calls to class_getName() can also get here when // interrogating the dyld shared cache. (rdar://27258517) // fixme runtimeLock.assertLocked(); // fixme assert(realize); if (realize) { runtimeLock.assertLocked(); realizeClass((Class)this); data()->demangledName = de; return de; } else { // Save the string to avoid leaks. char *cached; { mutex_locker_t lock(DemangleCacheLock); if (!DemangleCache) { DemangleCache = NXCreateHashTable(NXStrPrototype, 0, nil); } cached = (char *)NXHashInsertIfAbsent(DemangleCache, de); } if (cached != de) free(de); return cached; } } /*********************************************************************** * class_getName * fixme * Locking: acquires runtimeLock **********************************************************************/ const char *class_getName(Class cls) { if (!cls) return "nil"; // fixme lldb calls class_getName() on unrealized classes (rdar://27258517) // assert(cls->isRealized() || cls->isFuture()); return cls->demangledName(); } /*********************************************************************** * class_getVersion * fixme * Locking: none **********************************************************************/ int class_getVersion(Class cls) { if (!cls) return 0; assert(cls->isRealized()); return cls->data()->version; } /*********************************************************************** * class_setVersion * fixme * Locking: none **********************************************************************/ void class_setVersion(Class cls, int version) { if (!cls) return; assert(cls->isRealized()); cls->data()->version = version; } static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list) { assert(list); const method_t * const first = &list->first; const method_t *base = first; const method_t *probe; uintptr_t keyValue = (uintptr_t)key; uint32_t count; for (count = list->count; count != 0; count >>= 1) { probe = base + (count >> 1); uintptr_t probeValue = (uintptr_t)probe->name; if (keyValue == probeValue) { // `probe` is a match. // Rewind looking for the *first* occurrence of this value. // This is required for correct category overrides. while (probe > first && keyValue == (uintptr_t)probe[-1].name) { probe--; } return (method_t *)probe; } if (keyValue > probeValue) { base = probe + 1; count--; } } return nil; } /*********************************************************************** * getMethodNoSuper_nolock * fixme * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static method_t *search_method_list(const method_list_t *mlist, SEL sel) { int methodListIsFixedUp = mlist->isFixedUp(); int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) { return findMethodInSortedMethodList(sel, mlist); } else { // Linear search of unsorted method list for (auto& meth : *mlist) { if (meth.name == sel) return &meth; } } #if DEBUG // sanity-check negative results if (mlist->isFixedUp()) { for (auto& meth : *mlist) { if (meth.name == sel) { _objc_fatal("linear search worked when binary search did not"); } } } #endif return nil; } static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) { runtimeLock.assertLocked(); assert(cls->isRealized()); // fixme nil cls? // fixme nil sel? for (auto mlists = cls->data()->methods.beginLists(), end = cls->data()->methods.endLists(); mlists != end; ++mlists) { method_t *m = search_method_list(*mlists, sel); if (m) return m; } return nil; } /*********************************************************************** * getMethod_nolock * fixme * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static method_t * getMethod_nolock(Class cls, SEL sel) { method_t *m = nil; runtimeLock.assertLocked(); // fixme nil cls? // fixme nil sel? assert(cls->isRealized()); while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) { cls = cls->superclass; } return m; } /*********************************************************************** * _class_getMethod * fixme * Locking: read-locks runtimeLock **********************************************************************/ static Method _class_getMethod(Class cls, SEL sel) { mutex_locker_t lock(runtimeLock); return getMethod_nolock(cls, sel); } /*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/ Method class_getInstanceMethod(Class cls, SEL sel) { if (!cls || !sel) return nil; // This deliberately avoids +initialize because it historically did so. // This implementation is a bit weird because it's the only place that // wants a Method instead of an IMP. #warning fixme build and search caches // Search method lists, try method resolver, etc. lookUpImpOrNil(cls, sel, nil, NO/*initialize*/, NO/*cache*/, YES/*resolver*/); #warning fixme build and search caches return _class_getMethod(cls, sel); } /*********************************************************************** * log_and_fill_cache * Log this method call. If the logger permits it, fill the method cache. * cls is the method whose cache should be filled. * implementer is the class that owns the implementation in question. **********************************************************************/ static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) { #if SUPPORT_MESSAGE_LOGGING if (objcMsgLogEnabled) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), sel); if (!cacheIt) return; } #endif cache_fill (cls, sel, imp, receiver); } /*********************************************************************** * _class_lookupMethodAndLoadCache. * Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp(). * This lookup avoids optimistic cache scan because the dispatcher * already tried that. **********************************************************************/ IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) { return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/); } /*********************************************************************** * lookUpImpOrForward. * The standard IMP lookup. * initialize==NO tries to avoid +initialize (but sometimes fails) * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere) * Most callers should use initialize==YES and cache==YES. * inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. * If you don't want forwarding at all, use lookUpImpOrNil() instead. **********************************************************************/ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = nil; bool triedResolver = NO; runtimeLock.assertUnlocked(); // Optimistic cache lookup if (cache) { imp = cache_getImp(cls, sel); if (imp) return imp; } // runtimeLock is held during isRealized and isInitialized checking // to prevent races against concurrent realization. // runtimeLock is held during method search to make // method-lookup + cache-fill atomic with respect to method addition. // Otherwise, a category could be added but ignored indefinitely because // the cache was re-filled with the old value after the cache flush on // behalf of the category. runtimeLock.lock(); checkIsKnownClass(cls); if (!cls->isRealized()) { realizeClass(cls); } if (initialize && !cls->isInitialized()) { runtimeLock.unlock(); _class_initialize (_class_getNonMetaClass(cls, inst)); runtimeLock.lock(); // If sel == initialize, _class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172 } retry: runtimeLock.assertLocked(); // Try this class's cache. imp = cache_getImp(cls, sel); if (imp) goto done; // Try this class's method lists. { Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; } } // Try superclass caches and method lists. { unsigned attempts = unreasonableClassCount(); for (Class curClass = cls->superclass; curClass != nil; curClass = curClass->superclass) { // Halt if there is a cycle in the superclass chain. if (--attempts == 0) { _objc_fatal("Memory corruption in class list."); } // Superclass cache. imp = cache_getImp(curClass, sel); if (imp) { if (imp != (IMP)_objc_msgForward_impcache) { // Found the method in a superclass. Cache it in this class. log_and_fill_cache(cls, imp, sel, inst, curClass); goto done; } else { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; } } } // No implementation found. Try method resolver once. if (resolver && !triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; goto retry; } // No implementation found, and method resolver didn't help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); done: runtimeLock.unlock(); return imp; } /*********************************************************************** * lookUpImpOrNil. * Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache **********************************************************************/ IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver); if (imp == _objc_msgForward_impcache) return nil; else return imp; } /*********************************************************************** * lookupMethodInClassAndLoadCache. * Like _class_lookupMethodAndLoadCache, but does not search superclasses. * Caches and returns objc_msgForward if the method is not found in the class. **********************************************************************/ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) { Method meth; IMP imp; // fixme this is incomplete - no resolver, +initialize - // but it's only used for .cxx_construct/destruct so we don't care assert(sel == SEL_cxx_construct || sel == SEL_cxx_destruct); // Search cache first. imp = cache_getImp(cls, sel); if (imp) return imp; // Cache miss. Search method list. mutex_locker_t lock(runtimeLock); meth = getMethodNoSuper_nolock(cls, sel); if (meth) { // Hit in method list. Cache it. cache_fill(cls, sel, meth->imp, nil); return meth->imp; } else { // Miss in method list. Cache objc_msgForward. cache_fill(cls, sel, _objc_msgForward_impcache, nil); return _objc_msgForward_impcache; } } /*********************************************************************** * class_getProperty * fixme * Locking: read-locks runtimeLock **********************************************************************/ objc_property_t class_getProperty(Class cls, const char *name) { if (!cls || !name) return nil; mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); assert(cls->isRealized()); for ( ; cls; cls = cls->superclass) { for (auto& prop : cls->data()->properties) { if (0 == strcmp(name, prop.name)) { return (objc_property_t)∝ } } } return nil; } /*********************************************************************** * Locking: fixme **********************************************************************/ Class gdb_class_getClass(Class cls) { const char *className = cls->mangledName(); if(!className || !strlen(className)) return Nil; Class rCls = look_up_class(className, NO, NO); return rCls; } Class gdb_object_getClass(id obj) { if (!obj) return nil; return gdb_class_getClass(obj->getIsa()); } /*********************************************************************** * Locking: write-locks runtimeLock **********************************************************************/ void objc_class::setInitialized() { Class metacls; Class cls; assert(!isMetaClass()); cls = (Class)this; metacls = cls->ISA(); mutex_locker_t lock(runtimeLock); // Scan metaclass for custom AWZ. // Scan metaclass for custom RR. // Scan class for custom RR. // Also print custom RR/AWZ because we probably haven't done it yet. // Special cases: // NSObject AWZ class methods are default. // NSObject RR instance methods are default. // updateCustomRR_AWZ() also knows these special cases. // attachMethodLists() also knows these special cases. bool inherited; bool metaCustomAWZ = NO; if (MetaclassNSObjectAWZSwizzled) { // Somebody already swizzled NSObject's methods metaCustomAWZ = YES; inherited = NO; } else if (metacls == classNSObject()->ISA()) { // NSObject's metaclass AWZ is default, but we still need to check cats auto& methods = metacls->data()->methods; for (auto mlists = methods.beginCategoryMethodLists(), end = methods.endCategoryMethodLists(metacls); mlists != end; ++mlists) { if (methodListImplementsAWZ(*mlists)) { metaCustomAWZ = YES; inherited = NO; break; } } } else if (metacls->superclass->hasCustomAWZ()) { // Superclass is custom AWZ, therefore we are too. metaCustomAWZ = YES; inherited = YES; } else { // Not metaclass NSObject. auto& methods = metacls->data()->methods; for (auto mlists = methods.beginLists(), end = methods.endLists(); mlists != end; ++mlists) { if (methodListImplementsAWZ(*mlists)) { metaCustomAWZ = YES; inherited = NO; break; } } } if (!metaCustomAWZ) metacls->setHasDefaultAWZ(); if (PrintCustomAWZ && metaCustomAWZ) metacls->printCustomAWZ(inherited); // metacls->printCustomRR(); bool clsCustomRR = NO; if (ClassNSObjectRRSwizzled) { // Somebody already swizzled NSObject's methods clsCustomRR = YES; inherited = NO; } if (cls == classNSObject()) { // NSObject's RR is default, but we still need to check categories auto& methods = cls->data()->methods; for (auto mlists = methods.beginCategoryMethodLists(), end = methods.endCategoryMethodLists(cls); mlists != end; ++mlists) { if (methodListImplementsRR(*mlists)) { clsCustomRR = YES; inherited = NO; break; } } } else if (!cls->superclass) { // Custom root class clsCustomRR = YES; inherited = NO; } else if (cls->superclass->hasCustomRR()) { // Superclass is custom RR, therefore we are too. clsCustomRR = YES; inherited = YES; } else { // Not class NSObject. auto& methods = cls->data()->methods; for (auto mlists = methods.beginLists(), end = methods.endLists(); mlists != end; ++mlists) { if (methodListImplementsRR(*mlists)) { clsCustomRR = YES; inherited = NO; break; } } } if (!clsCustomRR) cls->setHasDefaultRR(); // cls->printCustomAWZ(); if (PrintCustomRR && clsCustomRR) cls->printCustomRR(inherited); // Update the +initialize flags. // Do this last. metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING); } /*********************************************************************** * Return YES if sel is used by retain/release implementors **********************************************************************/ static bool isRRSelector(SEL sel) { return (sel == SEL_retain || sel == SEL_release || sel == SEL_autorelease || sel == SEL_retainCount || sel == SEL_tryRetain || sel == SEL_retainWeakReference || sel == SEL_isDeallocating || sel == SEL_allowsWeakReference); } /*********************************************************************** * Return YES if mlist implements one of the isRRSelector() methods **********************************************************************/ static bool methodListImplementsRR(const method_list_t *mlist) { return (search_method_list(mlist, SEL_retain) || search_method_list(mlist, SEL_release) || search_method_list(mlist, SEL_autorelease) || search_method_list(mlist, SEL_retainCount) || search_method_list(mlist, SEL_tryRetain) || search_method_list(mlist, SEL_isDeallocating) || search_method_list(mlist, SEL_retainWeakReference) || search_method_list(mlist, SEL_allowsWeakReference)); } /*********************************************************************** * Return YES if sel is used by alloc or allocWithZone implementors **********************************************************************/ static bool isAWZSelector(SEL sel) { return (sel == SEL_allocWithZone || sel == SEL_alloc); } /*********************************************************************** * Return YES if mlist implements one of the isAWZSelector() methods **********************************************************************/ static bool methodListImplementsAWZ(const method_list_t *mlist) { return (search_method_list(mlist, SEL_allocWithZone) || search_method_list(mlist, SEL_alloc)); } void objc_class::printCustomRR(bool inherited) { assert(PrintCustomRR); assert(hasCustomRR()); _objc_inform("CUSTOM RR: %s%s%s", nameForLogging(), isMetaClass() ? " (meta)" : "", inherited ? " (inherited)" : ""); } void objc_class::printCustomAWZ(bool inherited) { assert(PrintCustomAWZ); assert(hasCustomAWZ()); _objc_inform("CUSTOM AWZ: %s%s%s", nameForLogging(), isMetaClass() ? " (meta)" : "", inherited ? " (inherited)" : ""); } void objc_class::printInstancesRequireRawIsa(bool inherited) { assert(PrintRawIsa); assert(instancesRequireRawIsa()); _objc_inform("RAW ISA: %s%s%s", nameForLogging(), isMetaClass() ? " (meta)" : "", inherited ? " (inherited)" : ""); } /*********************************************************************** * Mark this class and all of its subclasses as implementors or * inheritors of custom RR (retain/release/autorelease/retainCount) **********************************************************************/ void objc_class::setHasCustomRR(bool inherited) { Class cls = (Class)this; runtimeLock.assertLocked(); if (hasCustomRR()) return; foreach_realized_class_and_subclass(cls, ^(Class c){ if (c != cls && !c->isInitialized()) { // Subclass not yet initialized. Wait for setInitialized() to do it // fixme short circuit recursion? return; } if (c->hasCustomRR()) { // fixme short circuit recursion? return; } c->bits.setHasCustomRR(); if (PrintCustomRR) c->printCustomRR(inherited || c != cls); }); } /*********************************************************************** * Mark this class and all of its subclasses as implementors or * inheritors of custom alloc/allocWithZone: **********************************************************************/ void objc_class::setHasCustomAWZ(bool inherited) { Class cls = (Class)this; runtimeLock.assertLocked(); if (hasCustomAWZ()) return; foreach_realized_class_and_subclass(cls, ^(Class c){ if (c != cls && !c->isInitialized()) { // Subclass not yet initialized. Wait for setInitialized() to do it // fixme short circuit recursion? return; } if (c->hasCustomAWZ()) { // fixme short circuit recursion? return; } c->bits.setHasCustomAWZ(); if (PrintCustomAWZ) c->printCustomAWZ(inherited || c != cls); }); } /*********************************************************************** * Mark this class and all of its subclasses as requiring raw isa pointers **********************************************************************/ void objc_class::setInstancesRequireRawIsa(bool inherited) { Class cls = (Class)this; runtimeLock.assertLocked(); if (instancesRequireRawIsa()) return; foreach_realized_class_and_subclass(cls, ^(Class c){ if (c->instancesRequireRawIsa()) { // fixme short circuit recursion? return; } c->bits.setInstancesRequireRawIsa(); if (PrintRawIsa) c->printInstancesRequireRawIsa(inherited || c != cls); }); } /*********************************************************************** * Choose a class index. * Set instancesRequireRawIsa if no more class indexes are available. **********************************************************************/ void objc_class::chooseClassArrayIndex() { #if SUPPORT_INDEXED_ISA Class cls = (Class)this; runtimeLock.assertLocked(); if (objc_indexed_classes_count >= ISA_INDEX_COUNT) { // No more indexes available. assert(cls->classArrayIndex() == 0); cls->setInstancesRequireRawIsa(false/*not inherited*/); return; } unsigned index = objc_indexed_classes_count++; if (index == 0) index = objc_indexed_classes_count++; // index 0 is unused classForIndex(index) = cls; cls->setClassArrayIndex(index); #endif } /*********************************************************************** * Update custom RR and AWZ when a method changes its IMP **********************************************************************/ static void updateCustomRR_AWZ(Class cls, method_t *meth) { // In almost all cases, IMP swizzling does not affect custom RR/AWZ bits. // Custom RR/AWZ search will already find the method whether or not // it is swizzled, so it does not transition from non-custom to custom. // // The only cases where IMP swizzling can affect the RR/AWZ bits is // if the swizzled method is one of the methods that is assumed to be // non-custom. These special cases are listed in setInitialized(). // We look for such cases here. if (isRRSelector(meth->name)) { if ((classNSObject()->isInitialized() && classNSObject()->hasCustomRR()) || ClassNSObjectRRSwizzled) { // already custom, nothing would change return; } bool swizzlingNSObject = NO; if (cls == classNSObject()) { swizzlingNSObject = YES; } else { // Don't know the class. // The only special case is class NSObject. for (const auto& meth2 : classNSObject()->data()->methods) { if (meth == &meth2) { swizzlingNSObject = YES; break; } } } if (swizzlingNSObject) { if (classNSObject()->isInitialized()) { classNSObject()->setHasCustomRR(); } else { // NSObject not yet +initialized, so custom RR has not yet // been checked, and setInitialized() will not notice the // swizzle. ClassNSObjectRRSwizzled = YES; } } } else if (isAWZSelector(meth->name)) { Class metaclassNSObject = classNSObject()->ISA(); if ((metaclassNSObject->isInitialized() && metaclassNSObject->hasCustomAWZ()) || MetaclassNSObjectAWZSwizzled) { // already custom, nothing would change return; } bool swizzlingNSObject = NO; if (cls == metaclassNSObject) { swizzlingNSObject = YES; } else { // Don't know the class. // The only special case is metaclass NSObject. for (const auto& meth2 : metaclassNSObject->data()->methods) { if (meth == &meth2) { swizzlingNSObject = YES; break; } } } if (swizzlingNSObject) { if (metaclassNSObject->isInitialized()) { metaclassNSObject->setHasCustomAWZ(); } else { // NSObject not yet +initialized, so custom RR has not yet // been checked, and setInitialized() will not notice the // swizzle. MetaclassNSObjectAWZSwizzled = YES; } } } } /*********************************************************************** * class_getIvarLayout * Called by the garbage collector. * The class must be nil or already realized. * Locking: none **********************************************************************/ const uint8_t * class_getIvarLayout(Class cls) { if (cls) return cls->data()->ro->ivarLayout; else return nil; } /*********************************************************************** * class_getWeakIvarLayout * Called by the garbage collector. * The class must be nil or already realized. * Locking: none **********************************************************************/ const uint8_t * class_getWeakIvarLayout(Class cls) { if (cls) return cls->data()->ro->weakIvarLayout; else return nil; } /*********************************************************************** * class_setIvarLayout * Changes the class's ivar layout. * nil layout means no unscanned ivars * The class must be under construction. * fixme: sanity-check layout vs instance size? * fixme: sanity-check layout vs superclass? * Locking: acquires runtimeLock **********************************************************************/ void class_setIvarLayout(Class cls, const uint8_t *layout) { if (!cls) return; mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); // Can only change layout of in-construction classes. // note: if modifications to post-construction classes were // allowed, there would be a race below (us vs. concurrent object_setIvar) if (!(cls->data()->flags & RW_CONSTRUCTING)) { _objc_inform("*** Can't set ivar layout for already-registered " "class '%s'", cls->nameForLogging()); return; } class_ro_t *ro_w = make_ro_writeable(cls->data()); try_free(ro_w->ivarLayout); ro_w->ivarLayout = ustrdupMaybeNil(layout); } /*********************************************************************** * class_setWeakIvarLayout * Changes the class's weak ivar layout. * nil layout means no weak ivars * The class must be under construction. * fixme: sanity-check layout vs instance size? * fixme: sanity-check layout vs superclass? * Locking: acquires runtimeLock **********************************************************************/ void class_setWeakIvarLayout(Class cls, const uint8_t *layout) { if (!cls) return; mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); // Can only change layout of in-construction classes. // note: if modifications to post-construction classes were // allowed, there would be a race below (us vs. concurrent object_setIvar) if (!(cls->data()->flags & RW_CONSTRUCTING)) { _objc_inform("*** Can't set weak ivar layout for already-registered " "class '%s'", cls->nameForLogging()); return; } class_ro_t *ro_w = make_ro_writeable(cls->data()); try_free(ro_w->weakIvarLayout); ro_w->weakIvarLayout = ustrdupMaybeNil(layout); } /*********************************************************************** * getIvar * Look up an ivar by name. * Locking: runtimeLock must be read- or write-locked by the caller. **********************************************************************/ static ivar_t *getIvar(Class cls, const char *name) { runtimeLock.assertLocked(); const ivar_list_t *ivars; assert(cls->isRealized()); if ((ivars = cls->data()->ro->ivars)) { for (auto& ivar : *ivars) { if (!ivar.offset) continue; // anonymous bitfield // ivar.name may be nil for anonymous bitfields etc. if (ivar.name && 0 == strcmp(name, ivar.name)) { return &ivar; } } } return nil; } /*********************************************************************** * _class_getClassForIvar * Given a class and an ivar that is in it or one of its superclasses, * find the actual class that defined the ivar. **********************************************************************/ Class _class_getClassForIvar(Class cls, Ivar ivar) { mutex_locker_t lock(runtimeLock); for ( ; cls; cls = cls->superclass) { if (auto ivars = cls->data()->ro->ivars) { if (ivars->containsIvar(ivar)) { return cls; } } } return nil; } /*********************************************************************** * _class_getVariable * fixme * Locking: read-locks runtimeLock **********************************************************************/ Ivar _class_getVariable(Class cls, const char *name) { mutex_locker_t lock(runtimeLock); for ( ; cls; cls = cls->superclass) { ivar_t *ivar = getIvar(cls, name); if (ivar) { return ivar; } } return nil; } /*********************************************************************** * class_conformsToProtocol * fixme * Locking: read-locks runtimeLock **********************************************************************/ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) { protocol_t *proto = newprotocol(proto_gen); if (!cls) return NO; if (!proto_gen) return NO; mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); assert(cls->isRealized()); for (const auto& proto_ref : cls->data()->protocols) { protocol_t *p = remapProtocol(proto_ref); if (p == proto || protocol_conformsToProtocol_nolock(p, proto)) { return YES; } } return NO; } /********************************************************************** * addMethod * fixme * Locking: runtimeLock must be held by the caller **********************************************************************/ static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace) { IMP result = nil; runtimeLock.assertLocked(); checkIsKnownClass(cls); assert(types); assert(cls->isRealized()); method_t *m; if ((m = getMethodNoSuper_nolock(cls, name))) { // already exists if (!replace) { result = m->imp; } else { result = _method_setImplementation(cls, m, imp); } } else { // fixme optimize method_list_t *newlist; newlist = (method_list_t *)calloc(sizeof(*newlist), 1); newlist->entsizeAndFlags = (uint32_t)sizeof(method_t) | fixed_up_method_list; newlist->count = 1; newlist->first.name = name; newlist->first.types = strdupIfMutable(types); newlist->first.imp = imp; prepareMethodLists(cls, &newlist, 1, NO, NO); cls->data()->methods.attachLists(&newlist, 1); flushCaches(cls); result = nil; } return result; } /********************************************************************** * addMethods * Add the given methods to a class in bulk. * Returns the selectors which could not be added, when replace == NO and a * method already exists. The returned selectors are NULL terminated and must be * freed by the caller. They are NULL if no failures occurred. * Locking: runtimeLock must be held by the caller **********************************************************************/ static SEL * addMethods(Class cls, const SEL *names, const IMP *imps, const char **types, uint32_t count, bool replace, uint32_t *outFailedCount) { runtimeLock.assertLocked(); assert(names); assert(imps); assert(types); assert(cls->isRealized()); method_list_t *newlist; size_t newlistSize = method_list_t::byteSize(sizeof(method_t), count); newlist = (method_list_t *)calloc(newlistSize, 1); newlist->entsizeAndFlags = (uint32_t)sizeof(method_t) | fixed_up_method_list; newlist->count = 0; method_t *newlistMethods = &newlist->first; SEL *failedNames = nil; uint32_t failedCount = 0; for (uint32_t i = 0; i < count; i++) { method_t *m; if ((m = getMethodNoSuper_nolock(cls, names[i]))) { // already exists if (!replace) { // report failure if (failedNames == nil) { // allocate an extra entry for a trailing NULL in case // every method fails failedNames = (SEL *)calloc(sizeof(*failedNames), count + 1); } failedNames[failedCount] = m->name; failedCount++; } else { _method_setImplementation(cls, m, imps[i]); } } else { method_t *newmethod = &newlistMethods[newlist->count]; newmethod->name = names[i]; newmethod->types = strdupIfMutable(types[i]); newmethod->imp = imps[i]; newlist->count++; } } if (newlist->count > 0) { // fixme resize newlist because it may have been over-allocated above. // Note that realloc() alone doesn't work due to ptrauth. method_t::SortBySELAddress sorter; std::stable_sort(newlist->begin(), newlist->end(), sorter); prepareMethodLists(cls, &newlist, 1, NO, NO); cls->data()->methods.attachLists(&newlist, 1); flushCaches(cls); } else { // Attaching the method list to the class consumes it. If we don't // do that, we have to free the memory ourselves. free(newlist); } if (outFailedCount) *outFailedCount = failedCount; return failedNames; } BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { if (!cls) return NO; mutex_locker_t lock(runtimeLock); return ! addMethod(cls, name, imp, types ?: "", NO); } IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) { if (!cls) return nil; mutex_locker_t lock(runtimeLock); return addMethod(cls, name, imp, types ?: "", YES); } SEL * class_addMethodsBulk(Class cls, const SEL *names, const IMP *imps, const char **types, uint32_t count, uint32_t *outFailedCount) { if (!cls) { if (outFailedCount) *outFailedCount = count; return (SEL *)memdup(names, count * sizeof(*names)); } mutex_locker_t lock(runtimeLock); return addMethods(cls, names, imps, types, count, NO, outFailedCount); } void class_replaceMethodsBulk(Class cls, const SEL *names, const IMP *imps, const char **types, uint32_t count) { if (!cls) return; mutex_locker_t lock(runtimeLock); addMethods(cls, names, imps, types, count, YES, nil); } /*********************************************************************** * class_addIvar * Adds an ivar to a class. * Locking: acquires runtimeLock **********************************************************************/ BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *type) { if (!cls) return NO; if (!type) type = ""; if (name && 0 == strcmp(name, "")) name = nil; mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); assert(cls->isRealized()); // No class variables if (cls->isMetaClass()) { return NO; } // Can only add ivars to in-construction classes. if (!(cls->data()->flags & RW_CONSTRUCTING)) { return NO; } // Check for existing ivar with this name, unless it's anonymous. // Check for too-big ivar. // fixme check for superclass ivar too? if ((name && getIvar(cls, name)) || size > UINT32_MAX) { return NO; } class_ro_t *ro_w = make_ro_writeable(cls->data()); // fixme allocate less memory here ivar_list_t *oldlist, *newlist; if ((oldlist = (ivar_list_t *)cls->data()->ro->ivars)) { size_t oldsize = oldlist->byteSize(); newlist = (ivar_list_t *)calloc(oldsize + oldlist->entsize(), 1); memcpy(newlist, oldlist, oldsize); free(oldlist); } else { newlist = (ivar_list_t *)calloc(sizeof(ivar_list_t), 1); newlist->entsizeAndFlags = (uint32_t)sizeof(ivar_t); } uint32_t offset = cls->unalignedInstanceSize(); uint32_t alignMask = (1<get(newlist->count++); #if __x86_64__ // Deliberately over-allocate the ivar offset variable. // Use calloc() to clear all 64 bits. See the note in struct ivar_t. ivar.offset = (int32_t *)(int64_t *)calloc(sizeof(int64_t), 1); #else ivar.offset = (int32_t *)malloc(sizeof(int32_t)); #endif *ivar.offset = offset; ivar.name = name ? strdupIfMutable(name) : nil; ivar.type = strdupIfMutable(type); ivar.alignment_raw = alignment; ivar.size = (uint32_t)size; ro_w->ivars = newlist; cls->setInstanceSize((uint32_t)(offset + size)); // Ivar layout updated in registerClass. return YES; } /*********************************************************************** * class_addProtocol * Adds a protocol to a class. * Locking: acquires runtimeLock **********************************************************************/ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) { protocol_t *protocol = newprotocol(protocol_gen); if (!cls) return NO; if (class_conformsToProtocol(cls, protocol_gen)) return NO; mutex_locker_t lock(runtimeLock); assert(cls->isRealized()); // fixme optimize protocol_list_t *protolist = (protocol_list_t *) malloc(sizeof(protocol_list_t) + sizeof(protocol_t *)); protolist->count = 1; protolist->list[0] = (protocol_ref_t)protocol; cls->data()->protocols.attachLists(&protolist, 1); // fixme metaclass? return YES; } /*********************************************************************** * class_addProperty * Adds a property to a class. * Locking: acquires runtimeLock **********************************************************************/ static bool _class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attrs, unsigned int count, bool replace) { if (!cls) return NO; if (!name) return NO; property_t *prop = class_getProperty(cls, name); if (prop && !replace) { // already exists, refuse to replace return NO; } else if (prop) { // replace existing mutex_locker_t lock(runtimeLock); try_free(prop->attributes); prop->attributes = copyPropertyAttributeString(attrs, count); return YES; } else { mutex_locker_t lock(runtimeLock); assert(cls->isRealized()); property_list_t *proplist = (property_list_t *) malloc(sizeof(*proplist)); proplist->count = 1; proplist->entsizeAndFlags = sizeof(proplist->first); proplist->first.name = strdupIfMutable(name); proplist->first.attributes = copyPropertyAttributeString(attrs, count); cls->data()->properties.attachLists(&proplist, 1); return YES; } } BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attrs, unsigned int n) { return _class_addProperty(cls, name, attrs, n, NO); } void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attrs, unsigned int n) { _class_addProperty(cls, name, attrs, n, YES); } /*********************************************************************** * look_up_class * Look up a class by name, and realize it. * Locking: acquires runtimeLock **********************************************************************/ Class look_up_class(const char *name, bool includeUnconnected __attribute__((unused)), bool includeClassHandler __attribute__((unused))) { if (!name) return nil; Class result; bool unrealized; { mutex_locker_t lock(runtimeLock); result = getClass(name); unrealized = result && !result->isRealized(); } if (unrealized) { mutex_locker_t lock(runtimeLock); realizeClass(result); } return result; } /*********************************************************************** * objc_duplicateClass * fixme * Locking: acquires runtimeLock **********************************************************************/ Class objc_duplicateClass(Class original, const char *name, size_t extraBytes) { Class duplicate; mutex_locker_t lock(runtimeLock); checkIsKnownClass(original); assert(original->isRealized()); assert(!original->isMetaClass()); duplicate = alloc_class_for_subclass(original, extraBytes); duplicate->initClassIsa(original->ISA()); duplicate->superclass = original->superclass; duplicate->cache.initializeToEmpty(); class_rw_t *rw = (class_rw_t *)calloc(sizeof(*original->data()), 1); rw->flags = (original->data()->flags | RW_COPIED_RO | RW_REALIZING); rw->version = original->data()->version; rw->firstSubclass = nil; rw->nextSiblingClass = nil; duplicate->bits = original->bits; duplicate->setData(rw); rw->ro = (class_ro_t *) memdup(original->data()->ro, sizeof(*original->data()->ro)); *(char **)&rw->ro->name = strdupIfMutable(name); rw->methods = original->data()->methods.duplicate(); // fixme dies when categories are added to the base rw->properties = original->data()->properties; rw->protocols = original->data()->protocols; duplicate->chooseClassArrayIndex(); if (duplicate->superclass) { addSubclass(duplicate->superclass, duplicate); // duplicate->isa == original->isa so don't addSubclass() for it } else { addRootClass(duplicate); } // Don't methodize class - construction above is correct addNamedClass(duplicate, duplicate->data()->ro->name); addClassTableEntry(duplicate, /*addMeta=*/false); if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s' (duplicate of %s) %p %p", name, original->nameForLogging(), (void*)duplicate, duplicate->data()->ro); } duplicate->clearInfo(RW_REALIZING); return duplicate; } /*********************************************************************** * objc_initializeClassPair * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ // &UnsetLayout is the default ivar layout during class construction static const uint8_t UnsetLayout = 0; static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta) { runtimeLock.assertLocked(); class_ro_t *cls_ro_w, *meta_ro_w; cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1)); meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1)); cls_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1); meta_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1); cls->data()->ro = cls_ro_w; meta->data()->ro = meta_ro_w; // Set basic info cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING; meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING; cls->data()->version = 0; meta->data()->version = 7; cls_ro_w->flags = 0; meta_ro_w->flags = RO_META; if (!superclass) { cls_ro_w->flags |= RO_ROOT; meta_ro_w->flags |= RO_ROOT; } if (superclass) { cls_ro_w->instanceStart = superclass->unalignedInstanceSize(); meta_ro_w->instanceStart = superclass->ISA()->unalignedInstanceSize(); cls->setInstanceSize(cls_ro_w->instanceStart); meta->setInstanceSize(meta_ro_w->instanceStart); } else { cls_ro_w->instanceStart = 0; meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class); cls->setInstanceSize((uint32_t)sizeof(id)); // just an isa meta->setInstanceSize(meta_ro_w->instanceStart); } cls_ro_w->name = strdupIfMutable(name); meta_ro_w->name = strdupIfMutable(name); cls_ro_w->ivarLayout = &UnsetLayout; cls_ro_w->weakIvarLayout = &UnsetLayout; meta->chooseClassArrayIndex(); cls->chooseClassArrayIndex(); // Connect to superclasses and metaclasses cls->initClassIsa(meta); if (superclass) { meta->initClassIsa(superclass->ISA()->ISA()); cls->superclass = superclass; meta->superclass = superclass->ISA(); addSubclass(superclass, cls); addSubclass(superclass->ISA(), meta); } else { meta->initClassIsa(meta); cls->superclass = Nil; meta->superclass = cls; addRootClass(cls); addSubclass(cls, meta); } cls->cache.initializeToEmpty(); meta->cache.initializeToEmpty(); addClassTableEntry(cls); } /*********************************************************************** * verifySuperclass * Sanity-check the superclass provided to * objc_allocateClassPair, objc_initializeClassPair, or objc_readClassPair. **********************************************************************/ bool verifySuperclass(Class superclass, bool rootOK) { if (!superclass) { // Superclass does not exist. // If subclass may be a root class, this is OK. // If subclass must not be a root class, this is bad. return rootOK; } // Superclass must be realized. if (! superclass->isRealized()) return false; // Superclass must not be under construction. if (superclass->data()->flags & RW_CONSTRUCTING) return false; return true; } /*********************************************************************** * objc_initializeClassPair **********************************************************************/ Class objc_initializeClassPair(Class superclass, const char *name, Class cls, Class meta) { mutex_locker_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn't kosher. if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) { return nil; } objc_initializeClassPair_internal(superclass, name, cls, meta); return cls; } /*********************************************************************** * objc_allocateClassPair * fixme * Locking: acquires runtimeLock **********************************************************************/ Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) { Class cls, meta; mutex_locker_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn't kosher. if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) { return nil; } // Allocate new classes. cls = alloc_class_for_subclass(superclass, extraBytes); meta = alloc_class_for_subclass(superclass, extraBytes); // fixme mangle the name if it looks swift-y? objc_initializeClassPair_internal(superclass, name, cls, meta); return cls; } /*********************************************************************** * objc_registerClassPair * fixme * Locking: acquires runtimeLock **********************************************************************/ void objc_registerClassPair(Class cls) { mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); if ((cls->data()->flags & RW_CONSTRUCTED) || (cls->ISA()->data()->flags & RW_CONSTRUCTED)) { _objc_inform("objc_registerClassPair: class '%s' was already " "registered!", cls->data()->ro->name); return; } if (!(cls->data()->flags & RW_CONSTRUCTING) || !(cls->ISA()->data()->flags & RW_CONSTRUCTING)) { _objc_inform("objc_registerClassPair: class '%s' was not " "allocated with objc_allocateClassPair!", cls->data()->ro->name); return; } // Clear "under construction" bit, set "done constructing" bit cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING); cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING); // Add to named class table. addNamedClass(cls, cls->data()->ro->name); } /*********************************************************************** * objc_readClassPair() * Read a class and metaclass as written by a compiler. * Assumes the class and metaclass are not referenced by other things * that might need to be fixed up (such as categories and subclasses). * Does not call +load. * Returns the class pointer, or nil. * * Locking: runtimeLock acquired by map_images **********************************************************************/ Class objc_readClassPair(Class bits, const struct objc_image_info *info) { mutex_locker_t lock(runtimeLock); // No info bits are significant yet. (void)info; // Fail if the superclass isn't kosher. bool rootOK = bits->data()->flags & RO_ROOT; if (!verifySuperclass(bits->superclass, rootOK)){ return nil; } // Duplicate classes are allowed, just like they are for image loading. // readClass will complain about the duplicate. Class cls = readClass(bits, false/*bundle*/, false/*shared cache*/); if (cls != bits) { // This function isn't allowed to remap anything. _objc_fatal("objc_readClassPair for class %s changed %p to %p", cls->nameForLogging(), bits, cls); } realizeClass(cls); return cls; } /*********************************************************************** * detach_class * Disconnect a class from other data structures. * Exception: does not remove the class from the +load list * Call this before free_class. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void detach_class(Class cls, bool isMeta) { runtimeLock.assertLocked(); // categories not yet attached to this class removeAllUnattachedCategoriesForClass(cls); // superclass's subclass list if (cls->isRealized()) { Class supercls = cls->superclass; if (supercls) { removeSubclass(supercls, cls); } else { removeRootClass(cls); } } // class tables and +load queue if (!isMeta) { removeNamedClass(cls, cls->mangledName()); } NXHashRemove(allocatedClasses, cls); } /*********************************************************************** * free_class * Frees a class's data structures. * Call this after detach_class. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void free_class(Class cls) { runtimeLock.assertLocked(); if (! cls->isRealized()) return; auto rw = cls->data(); auto ro = rw->ro; cache_delete(cls); for (auto& meth : rw->methods) { try_free(meth.types); } rw->methods.tryFree(); const ivar_list_t *ivars = ro->ivars; if (ivars) { for (auto& ivar : *ivars) { try_free(ivar.offset); try_free(ivar.name); try_free(ivar.type); } try_free(ivars); } for (auto& prop : rw->properties) { try_free(prop.name); try_free(prop.attributes); } rw->properties.tryFree(); rw->protocols.tryFree(); try_free(ro->ivarLayout); try_free(ro->weakIvarLayout); try_free(ro->name); try_free(ro); try_free(rw); try_free(cls); } void objc_disposeClassPair(Class cls) { mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); if (!(cls->data()->flags & (RW_CONSTRUCTED|RW_CONSTRUCTING)) || !(cls->ISA()->data()->flags & (RW_CONSTRUCTED|RW_CONSTRUCTING))) { // class not allocated with objc_allocateClassPair // disposing still-unregistered class is OK! _objc_inform("objc_disposeClassPair: class '%s' was not " "allocated with objc_allocateClassPair!", cls->data()->ro->name); return; } if (cls->isMetaClass()) { _objc_inform("objc_disposeClassPair: class '%s' is a metaclass, " "not a class!", cls->data()->ro->name); return; } // Shouldn't have any live subclasses. if (cls->data()->firstSubclass) { _objc_inform("objc_disposeClassPair: class '%s' still has subclasses, " "including '%s'!", cls->data()->ro->name, cls->data()->firstSubclass->nameForLogging()); } if (cls->ISA()->data()->firstSubclass) { _objc_inform("objc_disposeClassPair: class '%s' still has subclasses, " "including '%s'!", cls->data()->ro->name, cls->ISA()->data()->firstSubclass->nameForLogging()); } // don't remove_class_from_loadable_list() // - it's not there and we don't have the lock detach_class(cls->ISA(), YES); detach_class(cls, NO); free_class(cls->ISA()); free_class(cls); } /*********************************************************************** * objc_constructInstance * Creates an instance of `cls` at the location pointed to by `bytes`. * `bytes` must point to at least class_getInstanceSize(cls) bytes of * well-aligned zero-filled memory. * The new object's isa is set. Any C++ constructors are called. * Returns `bytes` if successful. Returns nil if `cls` or `bytes` is * nil, or if C++ constructors fail. * Note: class_createInstance() and class_createInstances() preflight this. **********************************************************************/ id objc_constructInstance(Class cls, void *bytes) { if (!cls || !bytes) return nil; id obj = (id)bytes; // Read class's info bits all at once for performance bool hasCxxCtor = cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); if (fast) { obj->initInstanceIsa(cls, hasCxxDtor); } else { obj->initIsa(cls); } if (hasCxxCtor) { return object_cxxConstructFromClass(obj, cls); } else { return obj; } } /*********************************************************************** * class_createInstance * fixme * Locking: none **********************************************************************/ static __attribute__((always_inline)) id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { if (!cls) return nil; assert(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (!zone && fast) { obj = (id)calloc(1, size); if (!obj) return nil; obj->initInstanceIsa(cls, hasCxxDtor); } else { if (zone) { obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (!obj) return nil; // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } return obj; } id class_createInstance(Class cls, size_t extraBytes) { return _class_createInstanceFromZone(cls, extraBytes, nil); } /*********************************************************************** * class_createInstances * fixme * Locking: none **********************************************************************/ #if SUPPORT_NONPOINTER_ISA #warning fixme optimize class_createInstances #endif unsigned class_createInstances(Class cls, size_t extraBytes, id *results, unsigned num_requested) { return _class_createInstancesFromZone(cls, extraBytes, nil, results, num_requested); } /*********************************************************************** * object_copyFromZone * fixme * Locking: none **********************************************************************/ static id _object_copyFromZone(id oldObj, size_t extraBytes, void *zone) { if (!oldObj) return nil; if (oldObj->isTaggedPointer()) return oldObj; // fixme this doesn't handle C++ ivars correctly (#4619414) Class cls = oldObj->ISA(); size_t size; id obj = _class_createInstanceFromZone(cls, extraBytes, zone, false, &size); if (!obj) return nil; // Copy everything except the isa, which was already set above. uint8_t *copyDst = (uint8_t *)obj + sizeof(Class); uint8_t *copySrc = (uint8_t *)oldObj + sizeof(Class); size_t copySize = size - sizeof(Class); memmove(copyDst, copySrc, copySize); fixupCopiedIvars(obj, oldObj); return obj; } /*********************************************************************** * object_copy * fixme * Locking: none **********************************************************************/ id object_copy(id oldObj, size_t extraBytes) { return _object_copyFromZone(oldObj, extraBytes, malloc_default_zone()); } #if SUPPORT_ZONES /*********************************************************************** * class_createInstanceFromZone * fixme * Locking: none **********************************************************************/ id class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) { return _class_createInstanceFromZone(cls, extraBytes, zone); } /*********************************************************************** * object_copyFromZone * fixme * Locking: none **********************************************************************/ id object_copyFromZone(id oldObj, size_t extraBytes, void *zone) { return _object_copyFromZone(oldObj, extraBytes, zone); } #endif /*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. **********************************************************************/ void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); } return obj; } /*********************************************************************** * object_dispose * fixme * Locking: none **********************************************************************/ id object_dispose(id obj) { if (!obj) return nil; objc_destructInstance(obj); free(obj); return nil; } /*********************************************************************** * _objc_getFreedObjectClass * fixme * Locking: none **********************************************************************/ Class _objc_getFreedObjectClass (void) { return nil; } /*********************************************************************** * Tagged pointer objects. * * Tagged pointer objects store the class and the object value in the * object pointer; the "pointer" does not actually point to anything. * * Tagged pointer objects currently use this representation: * (LSB) * 1 bit set if tagged, clear if ordinary object pointer * 3 bits tag index * 60 bits payload * (MSB) * The tag index defines the object's class. * The payload format is defined by the object's class. * * If the tag index is 0b111, the tagged pointer object uses an * "extended" representation, allowing more classes but with smaller payloads: * (LSB) * 1 bit set if tagged, clear if ordinary object pointer * 3 bits 0b111 * 8 bits extended tag index * 52 bits payload * (MSB) * * Some architectures reverse the MSB and LSB in these representations. * * This representation is subject to change. Representation-agnostic SPI is: * objc-internal.h for class implementers. * objc-gdb.h for debuggers. **********************************************************************/ #if !SUPPORT_TAGGED_POINTERS // These variables are always provided for debuggers. uintptr_t objc_debug_taggedpointer_obfuscator = 0; uintptr_t objc_debug_taggedpointer_mask = 0; unsigned objc_debug_taggedpointer_slot_shift = 0; uintptr_t objc_debug_taggedpointer_slot_mask = 0; unsigned objc_debug_taggedpointer_payload_lshift = 0; unsigned objc_debug_taggedpointer_payload_rshift = 0; Class objc_debug_taggedpointer_classes[1] = { nil }; uintptr_t objc_debug_taggedpointer_ext_mask = 0; unsigned objc_debug_taggedpointer_ext_slot_shift = 0; uintptr_t objc_debug_taggedpointer_ext_slot_mask = 0; unsigned objc_debug_taggedpointer_ext_payload_lshift = 0; unsigned objc_debug_taggedpointer_ext_payload_rshift = 0; Class objc_debug_taggedpointer_ext_classes[1] = { nil }; static void disableTaggedPointers() { } static void initializeTaggedPointerObfuscator(void) { } #else // The "slot" used in the class table and given to the debugger // includes the is-tagged bit. This makes objc_msgSend faster. // The "ext" representation doesn't do that. uintptr_t objc_debug_taggedpointer_obfuscator; uintptr_t objc_debug_taggedpointer_mask = _OBJC_TAG_MASK; unsigned objc_debug_taggedpointer_slot_shift = _OBJC_TAG_SLOT_SHIFT; uintptr_t objc_debug_taggedpointer_slot_mask = _OBJC_TAG_SLOT_MASK; unsigned objc_debug_taggedpointer_payload_lshift = _OBJC_TAG_PAYLOAD_LSHIFT; unsigned objc_debug_taggedpointer_payload_rshift = _OBJC_TAG_PAYLOAD_RSHIFT; // objc_debug_taggedpointer_classes is defined in objc-msg-*.s uintptr_t objc_debug_taggedpointer_ext_mask = _OBJC_TAG_EXT_MASK; unsigned objc_debug_taggedpointer_ext_slot_shift = _OBJC_TAG_EXT_SLOT_SHIFT; uintptr_t objc_debug_taggedpointer_ext_slot_mask = _OBJC_TAG_EXT_SLOT_MASK; unsigned objc_debug_taggedpointer_ext_payload_lshift = _OBJC_TAG_EXT_PAYLOAD_LSHIFT; unsigned objc_debug_taggedpointer_ext_payload_rshift = _OBJC_TAG_EXT_PAYLOAD_RSHIFT; // objc_debug_taggedpointer_ext_classes is defined in objc-msg-*.s static void disableTaggedPointers() { objc_debug_taggedpointer_mask = 0; objc_debug_taggedpointer_slot_shift = 0; objc_debug_taggedpointer_slot_mask = 0; objc_debug_taggedpointer_payload_lshift = 0; objc_debug_taggedpointer_payload_rshift = 0; objc_debug_taggedpointer_ext_mask = 0; objc_debug_taggedpointer_ext_slot_shift = 0; objc_debug_taggedpointer_ext_slot_mask = 0; objc_debug_taggedpointer_ext_payload_lshift = 0; objc_debug_taggedpointer_ext_payload_rshift = 0; } // Returns a pointer to the class's storage in the tagged class arrays. // Assumes the tag is a valid basic tag. static Class * classSlotForBasicTagIndex(objc_tag_index_t tag) { uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK); uintptr_t obfuscatedTag = tag ^ tagObfuscator; // Array index in objc_tag_classes includes the tagged bit itself #if SUPPORT_MSB_TAGGED_POINTERS return &objc_tag_classes[0x8 | obfuscatedTag]; #else return &objc_tag_classes[(obfuscatedTag << 1) | 1]; #endif } // Returns a pointer to the class's storage in the tagged class arrays, // or nil if the tag is out of range. static Class * classSlotForTagIndex(objc_tag_index_t tag) { if (tag >= OBJC_TAG_First60BitPayload && tag <= OBJC_TAG_Last60BitPayload) { return classSlotForBasicTagIndex(tag); } if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) { int index = tag - OBJC_TAG_First52BitPayload; uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK); return &objc_tag_ext_classes[index ^ tagObfuscator]; } return nil; } /*********************************************************************** * initializeTaggedPointerObfuscator * Initialize objc_debug_taggedpointer_obfuscator with randomness. * * The tagged pointer obfuscator is intended to make it more difficult * for an attacker to construct a particular object as a tagged pointer, * in the presence of a buffer overflow or other write control over some * memory. The obfuscator is XORed with the tagged pointers when setting * or retrieving payload values. They are filled with randomness on first * use. **********************************************************************/ static void initializeTaggedPointerObfuscator(void) { if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || // Set the obfuscator to zero for apps linked against older SDKs, // in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } else { // Pull random data into the variable, then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; } } /*********************************************************************** * _objc_registerTaggedPointerClass * Set the class to use for the given tagged pointer index. * Aborts if the tag is out of range, or if the tag is already * used by some other class. **********************************************************************/ void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls) { if (objc_debug_taggedpointer_mask == 0) { _objc_fatal("tagged pointers are disabled"); } Class *slot = classSlotForTagIndex(tag); if (!slot) { _objc_fatal("tag index %u is invalid", (unsigned int)tag); } Class oldCls = *slot; if (cls && oldCls && cls != oldCls) { _objc_fatal("tag index %u used for two different classes " "(was %p %s, now %p %s)", tag, oldCls, oldCls->nameForLogging(), cls, cls->nameForLogging()); } *slot = cls; // Store a placeholder class in the basic tag slot that is // reserved for the extended tag space, if it isn't set already. // Do this lazily when the first extended tag is registered so // that old debuggers characterize bogus pointers correctly more often. if (tag < OBJC_TAG_First60BitPayload || tag > OBJC_TAG_Last60BitPayload) { Class *extSlot = classSlotForBasicTagIndex(OBJC_TAG_RESERVED_7); if (*extSlot == nil) { extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer; *extSlot = (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer; } } } /*********************************************************************** * _objc_getClassForTag * Returns the class that is using the given tagged pointer tag. * Returns nil if no class is using that tag or the tag is out of range. **********************************************************************/ Class _objc_getClassForTag(objc_tag_index_t tag) { Class *slot = classSlotForTagIndex(tag); if (slot) return *slot; else return nil; } #endif #if SUPPORT_FIXUP OBJC_EXTERN void objc_msgSend_fixup(void); OBJC_EXTERN void objc_msgSendSuper2_fixup(void); OBJC_EXTERN void objc_msgSend_stret_fixup(void); OBJC_EXTERN void objc_msgSendSuper2_stret_fixup(void); #if defined(__i386__) || defined(__x86_64__) OBJC_EXTERN void objc_msgSend_fpret_fixup(void); #endif #if defined(__x86_64__) OBJC_EXTERN void objc_msgSend_fp2ret_fixup(void); #endif OBJC_EXTERN void objc_msgSend_fixedup(void); OBJC_EXTERN void objc_msgSendSuper2_fixedup(void); OBJC_EXTERN void objc_msgSend_stret_fixedup(void); OBJC_EXTERN void objc_msgSendSuper2_stret_fixedup(void); #if defined(__i386__) || defined(__x86_64__) OBJC_EXTERN void objc_msgSend_fpret_fixedup(void); #endif #if defined(__x86_64__) OBJC_EXTERN void objc_msgSend_fp2ret_fixedup(void); #endif /*********************************************************************** * fixupMessageRef * Repairs an old vtable dispatch call site. * vtable dispatch itself is not supported. **********************************************************************/ static void fixupMessageRef(message_ref_t *msg) { msg->sel = sel_registerName((const char *)msg->sel); if (msg->imp == &objc_msgSend_fixup) { if (msg->sel == SEL_alloc) { msg->imp = (IMP)&objc_alloc; } else if (msg->sel == SEL_allocWithZone) { msg->imp = (IMP)&objc_allocWithZone; } else if (msg->sel == SEL_retain) { msg->imp = (IMP)&objc_retain; } else if (msg->sel == SEL_release) { msg->imp = (IMP)&objc_release; } else if (msg->sel == SEL_autorelease) { msg->imp = (IMP)&objc_autorelease; } else { msg->imp = &objc_msgSend_fixedup; } } else if (msg->imp == &objc_msgSendSuper2_fixup) { msg->imp = &objc_msgSendSuper2_fixedup; } else if (msg->imp == &objc_msgSend_stret_fixup) { msg->imp = &objc_msgSend_stret_fixedup; } else if (msg->imp == &objc_msgSendSuper2_stret_fixup) { msg->imp = &objc_msgSendSuper2_stret_fixedup; } #if defined(__i386__) || defined(__x86_64__) else if (msg->imp == &objc_msgSend_fpret_fixup) { msg->imp = &objc_msgSend_fpret_fixedup; } #endif #if defined(__x86_64__) else if (msg->imp == &objc_msgSend_fp2ret_fixup) { msg->imp = &objc_msgSend_fp2ret_fixedup; } #endif } // SUPPORT_FIXUP #endif // ProKit SPI static Class setSuperclass(Class cls, Class newSuper) { Class oldSuper; runtimeLock.assertLocked(); assert(cls->isRealized()); assert(newSuper->isRealized()); oldSuper = cls->superclass; removeSubclass(oldSuper, cls); removeSubclass(oldSuper->ISA(), cls->ISA()); cls->superclass = newSuper; cls->ISA()->superclass = newSuper->ISA(); addSubclass(newSuper, cls); addSubclass(newSuper->ISA(), cls->ISA()); // Flush subclass's method caches. flushCaches(cls); flushCaches(cls->ISA()); return oldSuper; } Class class_setSuperclass(Class cls, Class newSuper) { mutex_locker_t lock(runtimeLock); return setSuperclass(cls, newSuper); } // __OBJC2__ #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-runtime-old.h ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_RUNTIME_OLD_H #define _OBJC_RUNTIME_OLD_H #include "objc-private.h" #define CLS_CLASS 0x1 #define CLS_META 0x2 #define CLS_INITIALIZED 0x4 #define CLS_POSING 0x8 #define CLS_MAPPED 0x10 #define CLS_FLUSH_CACHE 0x20 #define CLS_GROW_CACHE 0x40 #define CLS_NEED_BIND 0x80 #define CLS_METHOD_ARRAY 0x100 // the JavaBridge constructs classes with these markers #define CLS_JAVA_HYBRID 0x200 #define CLS_JAVA_CLASS 0x400 // thread-safe +initialize #define CLS_INITIALIZING 0x800 // bundle unloading #define CLS_FROM_BUNDLE 0x1000 // C++ ivar support #define CLS_HAS_CXX_STRUCTORS 0x2000 // Lazy method list arrays #define CLS_NO_METHOD_ARRAY 0x4000 // +load implementation #define CLS_HAS_LOAD_METHOD 0x8000 // objc_allocateClassPair API #define CLS_CONSTRUCTING 0x10000 // visibility=hidden #define CLS_HIDDEN 0x20000 // available for use; was CLS_FINALIZE_ON_MAIN_THREAD #define CLS_40000 0x40000 // Lazy property list arrays #define CLS_NO_PROPERTY_ARRAY 0x80000 // +load implementation #define CLS_CONNECTED 0x100000 #define CLS_LOADED 0x200000 // objc_allocateClassPair API #define CLS_CONSTRUCTED 0x400000 // class is leaf for cache flushing #define CLS_LEAF 0x800000 // class instances may have associative references #define CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS 0x1000000 // available for use; was CLS_HAS_INSTANCE_SPECIFIC_LAYOUT #define CLS_2000000 0x2000000 // class compiled with ARC #define CLS_IS_ARC 0x4000000 // class is not ARC but has ARC-style weak ivar layout #define CLS_HAS_WEAK_WITHOUT_ARC 0x8000000 // Terminator for array of method lists #define END_OF_METHODS_LIST ((struct old_method_list*)-1) #define ISCLASS(cls) (((cls)->info & CLS_CLASS) != 0) #define ISMETA(cls) (((cls)->info & CLS_META) != 0) #define GETMETA(cls) (ISMETA(cls) ? (cls) : (cls)->ISA()) struct old_class_ext { uint32_t size; const uint8_t *weak_ivar_layout; struct old_property_list **propertyLists; }; struct old_category { char *category_name; char *class_name; struct old_method_list *instance_methods; struct old_method_list *class_methods; struct old_protocol_list *protocols; // Fields below this point are in version 7 or later only. uint32_t size; struct old_property_list *instance_properties; // Check size for fields below this point. struct old_property_list *class_properties; bool hasClassPropertiesField() const { return size >= offsetof(old_category, class_properties) + sizeof(class_properties); } }; struct old_ivar { char *ivar_name; char *ivar_type; int ivar_offset; #ifdef __LP64__ int space; #endif }; struct old_ivar_list { int ivar_count; #ifdef __LP64__ int space; #endif /* variable length structure */ struct old_ivar ivar_list[1]; }; struct old_method { SEL method_name; char *method_types; IMP method_imp; }; struct old_method_list { void *obsolete; int method_count; #ifdef __LP64__ int space; #endif /* variable length structure */ struct old_method method_list[1]; }; struct old_protocol { Class isa; const char *protocol_name; struct old_protocol_list *protocol_list; struct objc_method_description_list *instance_methods; struct objc_method_description_list *class_methods; }; struct old_protocol_list { struct old_protocol_list *next; long count; struct old_protocol *list[1]; }; struct old_protocol_ext { uint32_t size; struct objc_method_description_list *optional_instance_methods; struct objc_method_description_list *optional_class_methods; struct old_property_list *instance_properties; const char **extendedMethodTypes; struct old_property_list *class_properties; bool hasClassPropertiesField() const { return size >= offsetof(old_protocol_ext, class_properties) + sizeof(class_properties); } }; struct old_property { const char *name; const char *attributes; }; struct old_property_list { uint32_t entsize; uint32_t count; struct old_property first; }; struct objc_class : objc_object { Class superclass; const char *name; uint32_t version; uint32_t info; uint32_t instance_size; struct old_ivar_list *ivars; struct old_method_list **methodLists; Cache cache; struct old_protocol_list *protocols; // CLS_EXT only const uint8_t *ivar_layout; struct old_class_ext *ext; void setInfo(uint32_t set) { OSAtomicOr32Barrier(set, (volatile uint32_t *)&info); } void clearInfo(uint32_t clear) { OSAtomicXor32Barrier(clear, (volatile uint32_t *)&info); } // set and clear must not overlap void changeInfo(uint32_t set, uint32_t clear) { assert((set & clear) == 0); uint32_t oldf, newf; do { oldf = this->info; newf = (oldf | set) & ~clear; } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&info)); } bool hasCxxCtor() { // set_superclass propagates the flag from the superclass. return info & CLS_HAS_CXX_STRUCTORS; } bool hasCxxDtor() { return hasCxxCtor(); // one bit for both ctor and dtor } // Return YES if the class's ivars are managed by ARC, // or the class is MRC but has ARC-style weak ivars. bool hasAutomaticIvars() { return info & (CLS_IS_ARC | CLS_HAS_WEAK_WITHOUT_ARC); } // Return YES if the class's ivars are managed by ARC. bool isARC() { return info & CLS_IS_ARC; } bool hasCustomRR() { return true; } void setHasCustomRR(bool = false) { } void setHasDefaultRR() { } void printCustomRR(bool) { } bool hasCustomAWZ() { return true; } void setHasCustomAWZ(bool = false) { } void setHasDefaultAWZ() { } void printCustomAWZ(bool) { } bool instancesHaveAssociatedObjects() { return info & CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS; } void setInstancesHaveAssociatedObjects() { setInfo(CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS); } bool shouldGrowCache() { return info & CLS_GROW_CACHE; } void setShouldGrowCache(bool grow) { if (grow) setInfo(CLS_GROW_CACHE); else clearInfo(CLS_GROW_CACHE); } // +initialize bits are stored on the metaclass only bool isInitializing() { return getMeta()->info & CLS_INITIALIZING; } // +initialize bits are stored on the metaclass only void setInitializing() { getMeta()->setInfo(CLS_INITIALIZING); } // +initialize bits are stored on the metaclass only bool isInitialized() { return getMeta()->info & CLS_INITIALIZED; } // +initialize bits are stored on the metaclass only void setInitialized() { getMeta()->changeInfo(CLS_INITIALIZED, CLS_INITIALIZING); } bool isLoadable() { // A class registered for +load is ready for +load to be called // if it is connected. return isConnected(); } IMP getLoadMethod(); bool isFuture(); bool isConnected(); const char *mangledName() { return name; } const char *demangledName() { return name; } const char *nameForLogging() { return name; } bool isRootClass() { return superclass == nil; } bool isRootMetaclass() { return ISA() == (Class)this; } bool isMetaClass() { return info & CLS_META; } // NOT identical to this->ISA() when this is a metaclass Class getMeta() { if (isMetaClass()) return (Class)this; else return this->ISA(); } // May be unaligned depending on class's ivars. uint32_t unalignedInstanceStart() { // This is not simply superclass->instance_size. // superclass->instance_size is padded to its sizeof() boundary, // which may envelop one of this class's ivars. // That in turn would break ARC-style ivar layouts. // Instead, we use the address of this class's first ivar when possible. if (!superclass) return 0; if (!ivars || ivars->ivar_count == 0) return superclass->instance_size; return ivars->ivar_list[0].ivar_offset; } // Class's instance start rounded up to a pointer-size boundary. // This is used for ARC layout bitmaps. uint32_t alignedInstanceStart() { return word_align(unalignedInstanceStart()); } // May be unaligned depending on class's ivars. uint32_t unalignedInstanceSize() { return instance_size; } // Class's ivar size rounded up to a pointer-size boundary. uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; } }; #include "hashtable2.h" __BEGIN_DECLS #define oldprotocol(proto) ((struct old_protocol *)proto) #define oldmethod(meth) ((struct old_method *)meth) #define oldcategory(cat) ((struct old_category *)cat) #define oldivar(ivar) ((struct old_ivar *)ivar) #define oldproperty(prop) ((struct old_property *)prop) extern NXHashTable *class_hash; extern void unload_class(Class cls); extern IMP lookupNamedMethodInMethodList(struct old_method_list *mlist, const char *meth_name); extern void _objc_insertMethods(Class cls, struct old_method_list *mlist, struct old_category *cat); extern void _objc_removeMethods(Class cls, struct old_method_list *mlist); extern void _objc_flush_caches (Class cls); extern bool _class_addProperties(Class cls, struct old_property_list *additions); extern bool _class_hasLoadMethod(Class cls); extern void change_class_references(Class imposter, Class original, Class copy, bool changeSuperRefs); extern void flush_marked_caches(void); extern void set_superclass(Class cls, Class supercls, bool cls_is_new); extern void try_free(const void *p); extern struct old_property *property_list_nth(const struct old_property_list *plist, uint32_t i); extern struct old_property **copyPropertyList(struct old_property_list *plist, unsigned int *outCount); extern struct objc_method_description * lookup_protocol_method(struct old_protocol *proto, SEL aSel, bool isRequiredMethod, bool isInstanceMethod, bool recursive); // used by flush_caches outside objc-cache.m extern void _cache_flush(Class cls); #ifdef OBJC_INSTRUMENTED extern unsigned int LinearFlushCachesCount; extern unsigned int LinearFlushCachesVisitedCount; extern unsigned int MaxLinearFlushCachesVisitedCount; extern unsigned int NonlinearFlushCachesCount; extern unsigned int NonlinearFlushCachesClassCount; extern unsigned int NonlinearFlushCachesVisitedCount; extern unsigned int MaxNonlinearFlushCachesVisitedCount; extern unsigned int IdealFlushCachesCount; extern unsigned int MaxIdealFlushCachesCount; #endif __END_DECLS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-runtime-old.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-runtime-old.m * Support for old-ABI classes and images. **********************************************************************/ /*********************************************************************** * Class loading and connecting (GrP 2004-2-11) * * When images are loaded (during program startup or otherwise), the * runtime needs to load classes and categories from the images, connect * classes to superclasses and categories to parent classes, and call * +load methods. * * The Objective-C runtime can cope with classes arriving in any order. * That is, a class may be discovered by the runtime before some * superclass is known. To handle out-of-order class loads, the * runtime uses a "pending class" system. * * (Historical note) * Panther and earlier: many classes arrived out-of-order because of * the poorly-ordered callback from dyld. However, the runtime's * pending mechanism only handled "missing superclass" and not * "present superclass but missing higher class". See Radar #3225652. * Tiger: The runtime's pending mechanism was augmented to handle * arbitrary missing classes. In addition, dyld was rewritten and * now sends the callbacks in strictly bottom-up link order. * The pending mechanism may now be needed only for rare and * hard to construct programs. * (End historical note) * * A class when first seen in an image is considered "unconnected". * It is stored in `unconnected_class_hash`. If all of the class's * superclasses exist and are already "connected", then the new class * can be connected to its superclasses and moved to `class_hash` for * normal use. Otherwise, the class waits in `unconnected_class_hash` * until the superclasses finish connecting. * * A "connected" class is * (1) in `class_hash`, * (2) connected to its superclasses, * (3) has no unconnected superclasses, * (4) is otherwise initialized and ready for use, and * (5) is eligible for +load if +load has not already been called. * * An "unconnected" class is * (1) in `unconnected_class_hash`, * (2) not connected to its superclasses, * (3) has an immediate superclass which is either missing or unconnected, * (4) is not ready for use, and * (5) is not yet eligible for +load. * * Image mapping is NOT CURRENTLY THREAD-SAFE with respect to just about * anything. Image mapping IS RE-ENTRANT in several places: superclass * lookup may cause ZeroLink to load another image, and +load calls may * cause dyld to load another image. * * Image mapping sequence: * * Read all classes in all new images. * Add them all to unconnected_class_hash. * Note any +load implementations before categories are attached. * Attach any pending categories. * Read all categories in all new images. * Attach categories whose parent class exists (connected or not), * and pend the rest. * Mark them all eligible for +load (if implemented), even if the * parent class is missing. * Try to connect all classes in all new images. * If the superclass is missing, pend the class * If the superclass is unconnected, try to recursively connect it * If the superclass is connected: * connect the class * mark the class eligible for +load, if implemented * fix up any pended classrefs referring to the class * connect any pended subclasses of the class * Resolve selector refs and class refs in all new images. * Class refs whose classes still do not exist are pended. * Fix up protocol objects in all new images. * Call +load for classes and categories. * May include classes or categories that are not in these images, * but are newly eligible because of these image. * Class +loads will be called superclass-first because of the * superclass-first nature of the connecting process. * Category +load needs to be deferred until the parent class is * connected and has had its +load called. * * Performance: all classes are read before any categories are read. * Fewer categories need be pended for lack of a parent class. * * Performance: all categories are attempted to be attached before * any classes are connected. Fewer class caches need be flushed. * (Unconnected classes and their respective subclasses are guaranteed * to be un-messageable, so their caches will be empty.) * * Performance: all classes are read before any classes are connected. * Fewer classes need be pended for lack of a superclass. * * Correctness: all selector and class refs are fixed before any * protocol fixups or +load methods. libobjc itself contains selector * and class refs which are used in protocol fixup and +load. * * Correctness: +load methods are scheduled in bottom-up link order. * This constraint is in addition to superclass order. Some +load * implementations expect to use another class in a linked-to library, * even if the two classes don't share a direct superclass relationship. * * Correctness: all classes are scanned for +load before any categories * are attached. Otherwise, if a category implements +load and its class * has no class methods, the class's +load scan would find the category's * +load method, which would then be called twice. * * Correctness: pended class refs are not fixed up until the class is * connected. Classes with missing weak superclasses remain unconnected. * Class refs to classes with missing weak superclasses must be nil. * Therefore class refs to unconnected classes must remain un-fixed. * **********************************************************************/ #if !__OBJC2__ #include "objc-private.h" #include "objc-runtime-old.h" #include "objc-file-old.h" #include "objc-cache-old.h" #include "objc-loadmethod.h" typedef struct _objc_unresolved_category { struct _objc_unresolved_category *next; old_category *cat; // may be nil long version; } _objc_unresolved_category; typedef struct _PendingSubclass { Class subclass; // subclass to finish connecting; may be nil struct _PendingSubclass *next; } PendingSubclass; typedef struct _PendingClassRef { Class *ref; // class reference to fix up; may be nil // (ref & 1) is a metaclass reference struct _PendingClassRef *next; } PendingClassRef; static uintptr_t classHash(void *info, Class data); static int classIsEqual(void *info, Class name, Class cls); static int _objc_defaultClassHandler(const char *clsName); static inline NXMapTable *pendingClassRefsMapTable(void); static inline NXMapTable *pendingSubclassesMapTable(void); static void pendClassInstallation(Class cls, const char *superName); static void pendClassReference(Class *ref, const char *className, bool isMeta); static void resolve_references_to_class(Class cls); static void resolve_subclasses_of_class(Class cls); static void really_connect_class(Class cls, Class supercls); static bool connect_class(Class cls); static void map_method_descs (struct objc_method_description_list * methods, bool copy); static void _objcTweakMethodListPointerForClass(Class cls); static inline void _objc_add_category(Class cls, old_category *category, int version); static bool _objc_add_category_flush_caches(Class cls, old_category *category, int version); static _objc_unresolved_category *reverse_cat(_objc_unresolved_category *cat); static void resolve_categories_for_class(Class cls); static bool _objc_register_category(old_category *cat, int version); // Function called when a class is loaded from an image void (*callbackFunction)(Class, Category) = 0; // Hash table of classes NXHashTable * class_hash = 0; static NXHashTablePrototype classHashPrototype = { (uintptr_t (*) (const void *, const void *)) classHash, (int (*)(const void *, const void *, const void *)) classIsEqual, NXNoEffectFree, 0 }; // Hash table of unconnected classes static NXHashTable *unconnected_class_hash = nil; // Exported copy of class_hash variable (hook for debugging tools) NXHashTable *_objc_debug_class_hash = nil; // Category and class registries // Keys are COPIES of strings, to prevent stale pointers with unloaded bundles // Use NXMapKeyCopyingInsert and NXMapKeyFreeingRemove static NXMapTable * category_hash = nil; // Keys are COPIES of strings, to prevent stale pointers with unloaded bundles // Use NXMapKeyCopyingInsert and NXMapKeyFreeingRemove static NXMapTable * pendingClassRefsMap = nil; static NXMapTable * pendingSubclassesMap = nil; // Protocols static NXMapTable *protocol_map = nil; // name -> protocol static NXMapTable *protocol_ext_map = nil; // protocol -> protocol ext // Function pointer objc_getClass calls through when class is not found static int (*objc_classHandler) (const char *) = _objc_defaultClassHandler; // Function pointer called by objc_getClass and objc_lookupClass when // class is not found. _objc_classLoader is called before objc_classHandler. static BOOL (*_objc_classLoader)(const char *) = nil; /*********************************************************************** * objc_dump_class_hash. Log names of all known classes. **********************************************************************/ void objc_dump_class_hash(void) { NXHashTable *table; unsigned count; Class data; NXHashState state; table = class_hash; count = 0; state = NXInitHashState (table); while (NXNextHashState (table, &state, (void **) &data)) printf ("class %d: %s\n", ++count, data->nameForLogging()); } /*********************************************************************** * _objc_init_class_hash. Return the class lookup table, create it if * necessary. **********************************************************************/ void _objc_init_class_hash(void) { // Do nothing if class hash table already exists if (class_hash) return; // class_hash starts small, with only enough capacity for libobjc itself. // If a second library is found by map_images(), class_hash is immediately // resized to capacity 1024 to cut down on rehashes. // Old numbers: A smallish Foundation+AppKit program will have // about 520 classes. Larger apps (like IB or WOB) have more like // 800 classes. Some customers have massive quantities of classes. // Foundation-only programs aren't likely to notice the ~6K loss. class_hash = NXCreateHashTable(classHashPrototype, 16, nil); _objc_debug_class_hash = class_hash; } /*********************************************************************** * objc_getClassList. Return the known classes. **********************************************************************/ int objc_getClassList(Class *buffer, int bufferLen) { NXHashState state; Class cls; int cnt, num; mutex_locker_t lock(classLock); if (!class_hash) return 0; num = NXCountHashTable(class_hash); if (nil == buffer) return num; cnt = 0; state = NXInitHashState(class_hash); while (cnt < bufferLen && NXNextHashState(class_hash, &state, (void **)&cls)) { buffer[cnt++] = cls; } return num; } /*********************************************************************** * objc_copyClassList * Returns pointers to all classes. * This requires all classes be realized, which is regretfully non-lazy. * * outCount may be nil. *outCount is the number of classes returned. * If the returned array is not nil, it is nil-terminated and must be * freed with free(). * Locking: acquires classLock **********************************************************************/ Class * objc_copyClassList(unsigned int *outCount) { Class *result; unsigned int count; mutex_locker_t lock(classLock); result = nil; count = class_hash ? NXCountHashTable(class_hash) : 0; if (count > 0) { Class cls; NXHashState state = NXInitHashState(class_hash); result = (Class *)malloc((1+count) * sizeof(Class)); count = 0; while (NXNextHashState(class_hash, &state, (void **)&cls)) { result[count++] = cls; } result[count] = nil; } if (outCount) *outCount = count; return result; } /*********************************************************************** * objc_copyProtocolList * Returns pointers to all protocols. * Locking: acquires classLock **********************************************************************/ Protocol * __unsafe_unretained * objc_copyProtocolList(unsigned int *outCount) { int count, i; Protocol *proto; const char *name; NXMapState state; Protocol **result; mutex_locker_t lock(classLock); count = NXCountMapTable(protocol_map); if (count == 0) { if (outCount) *outCount = 0; return nil; } result = (Protocol **)calloc(1 + count, sizeof(Protocol *)); i = 0; state = NXInitMapState(protocol_map); while (NXNextMapState(protocol_map, &state, (const void **)&name, (const void **)&proto)) { result[i++] = proto; } result[i++] = nil; assert(i == count+1); if (outCount) *outCount = count; return result; } /*********************************************************************** * objc_getClasses. Return class lookup table. * * NOTE: This function is very dangerous, since you cannot safely use * the hashtable without locking it, and the lock is private! **********************************************************************/ void *objc_getClasses(void) { OBJC_WARN_DEPRECATED; // Return the class lookup hash table return class_hash; } /*********************************************************************** * classHash. **********************************************************************/ static uintptr_t classHash(void *info, Class data) { // Nil classes hash to zero if (!data) return 0; // Call through to real hash function return _objc_strhash (data->mangledName()); } /*********************************************************************** * classIsEqual. Returns whether the class names match. If we ever * check more than the name, routines like objc_lookUpClass have to * change as well. **********************************************************************/ static int classIsEqual(void *info, Class name, Class cls) { // Standard string comparison return strcmp(name->mangledName(), cls->mangledName()) == 0; } // Unresolved future classes static NXHashTable *future_class_hash = nil; // Resolved future<->original classes static NXMapTable *future_class_to_original_class_map = nil; static NXMapTable *original_class_to_future_class_map = nil; // CF requests about 20 future classes; HIToolbox requests one. #define FUTURE_COUNT 32 /*********************************************************************** * setOriginalClassForFutureClass * Record resolution of a future class. **********************************************************************/ static void setOriginalClassForFutureClass(Class futureClass, Class originalClass) { if (!future_class_to_original_class_map) { future_class_to_original_class_map = NXCreateMapTable(NXPtrValueMapPrototype, FUTURE_COUNT); original_class_to_future_class_map = NXCreateMapTable(NXPtrValueMapPrototype, FUTURE_COUNT); } NXMapInsert (future_class_to_original_class_map, futureClass, originalClass); NXMapInsert (original_class_to_future_class_map, originalClass, futureClass); if (PrintFuture) { _objc_inform("FUTURE: using %p instead of %p for %s", (void*)futureClass, (void*)originalClass, originalClass->name); } } /*********************************************************************** * getOriginalClassForFutureClass * getFutureClassForOriginalClass * Switch between a future class and its corresponding original class. * The future class is the one actually in use. * The original class is the one from disk. **********************************************************************/ /* static Class getOriginalClassForFutureClass(Class futureClass) { if (!future_class_to_original_class_map) return Nil; return NXMapGet (future_class_to_original_class_map, futureClass); } */ static Class getFutureClassForOriginalClass(Class originalClass) { if (!original_class_to_future_class_map) return Nil; return (Class)NXMapGet(original_class_to_future_class_map, originalClass); } /*********************************************************************** * makeFutureClass * Initialize the memory in *cls with an unresolved future class with the * given name. The memory is recorded in future_class_hash. **********************************************************************/ static void makeFutureClass(Class cls, const char *name) { // CF requests about 20 future classes, plus HIToolbox has one. if (!future_class_hash) { future_class_hash = NXCreateHashTable(classHashPrototype, FUTURE_COUNT, nil); } cls->name = strdup(name); NXHashInsert(future_class_hash, cls); if (PrintFuture) { _objc_inform("FUTURE: reserving %p for %s", (void*)cls, name); } } /*********************************************************************** * _objc_allocateFutureClass * Allocate an unresolved future class for the given class name. * Returns any existing allocation if one was already made. * Assumes the named class doesn't exist yet. * Not thread safe. **********************************************************************/ Class _objc_allocateFutureClass(const char *name) { Class cls; if (future_class_hash) { objc_class query; query.name = name; if ((cls = (Class)NXHashGet(future_class_hash, &query))) { // Already have a future class for this name. return cls; } } cls = _calloc_class(sizeof(objc_class)); makeFutureClass(cls, name); return cls; } /*********************************************************************** * objc_getFutureClass. Return the id of the named class. * If the class does not exist, return an uninitialized class * structure that will be used for the class when and if it * does get loaded. * Not thread safe. **********************************************************************/ Class objc_getFutureClass(const char *name) { Class cls; // YES unconnected, NO class handler // (unconnected is OK because it will someday be the real class) cls = look_up_class(name, YES, NO); if (cls) { if (PrintFuture) { _objc_inform("FUTURE: found %p already in use for %s", (void*)cls, name); } return cls; } // No class or future class with that name yet. Make one. // fixme not thread-safe with respect to // simultaneous library load or getFutureClass. return _objc_allocateFutureClass(name); } BOOL _class_isFutureClass(Class cls) { return cls && cls->isFuture(); } bool objc_class::isFuture() { return future_class_hash && NXHashGet(future_class_hash, this); } /*********************************************************************** * _objc_defaultClassHandler. Default objc_classHandler. Does nothing. **********************************************************************/ static int _objc_defaultClassHandler(const char *clsName) { // Return zero so objc_getClass doesn't bother re-searching return 0; } /*********************************************************************** * objc_setClassHandler. Set objc_classHandler to the specified value. * * NOTE: This should probably deal with userSuppliedHandler being nil, * because the objc_classHandler caller does not check... it would bus * error. It would make sense to handle nil by restoring the default * handler. Is anyone hacking with this, though? **********************************************************************/ void objc_setClassHandler(int (*userSuppliedHandler)(const char *)) { OBJC_WARN_DEPRECATED; objc_classHandler = userSuppliedHandler; } /*********************************************************************** * _objc_setClassLoader * Similar to objc_setClassHandler, but objc_classLoader is used for * both objc_getClass() and objc_lookupClass(), and objc_classLoader * pre-empts objc_classHandler. **********************************************************************/ void _objc_setClassLoader(BOOL (*newClassLoader)(const char *)) { _objc_classLoader = newClassLoader; } /*********************************************************************** * objc_getProtocol * Get a protocol by name, or nil. **********************************************************************/ Protocol *objc_getProtocol(const char *name) { mutex_locker_t lock(classLock); if (!protocol_map) return nil; return (Protocol *)NXMapGet(protocol_map, name); } /*********************************************************************** * look_up_class * Map a class name to a class using various methods. * This is the common implementation of objc_lookUpClass and objc_getClass, * and is also used internally to get additional search options. * Sequence: * 1. class_hash * 2. unconnected_class_hash (optional) * 3. classLoader callback * 4. classHandler callback (optional) **********************************************************************/ Class look_up_class(const char *aClassName, bool includeUnconnected, bool includeClassHandler) { bool includeClassLoader = YES; // class loader cannot be skipped Class result = nil; struct objc_class query; query.name = aClassName; retry: if (!result && class_hash) { // Check ordinary classes mutex_locker_t lock(classLock); result = (Class)NXHashGet(class_hash, &query); } if (!result && includeUnconnected && unconnected_class_hash) { // Check not-yet-connected classes mutex_locker_t lock(classLock); result = (Class)NXHashGet(unconnected_class_hash, &query); } if (!result && includeClassLoader && _objc_classLoader) { // Try class loader callback if ((*_objc_classLoader)(aClassName)) { // Re-try lookup without class loader includeClassLoader = NO; goto retry; } } if (!result && includeClassHandler && objc_classHandler) { // Try class handler callback if ((*objc_classHandler)(aClassName)) { // Re-try lookup without class handler or class loader includeClassLoader = NO; includeClassHandler = NO; goto retry; } } return result; } /*********************************************************************** * objc_class::isConnected * Returns TRUE if class cls is connected. * A connected class has either a connected superclass or a nil superclass, * and is present in class_hash. **********************************************************************/ bool objc_class::isConnected() { mutex_locker_t lock(classLock); return NXHashMember(class_hash, this); } /*********************************************************************** * pendingClassRefsMapTable. Return a pointer to the lookup table for * pending class refs. **********************************************************************/ static inline NXMapTable *pendingClassRefsMapTable(void) { // Allocate table if needed if (!pendingClassRefsMap) { pendingClassRefsMap = NXCreateMapTable(NXStrValueMapPrototype, 10); } // Return table pointer return pendingClassRefsMap; } /*********************************************************************** * pendingSubclassesMapTable. Return a pointer to the lookup table for * pending subclasses. **********************************************************************/ static inline NXMapTable *pendingSubclassesMapTable(void) { // Allocate table if needed if (!pendingSubclassesMap) { pendingSubclassesMap = NXCreateMapTable(NXStrValueMapPrototype, 10); } // Return table pointer return pendingSubclassesMap; } /*********************************************************************** * pendClassInstallation * Finish connecting class cls when its superclass becomes connected. * Check for multiple pends of the same class because connect_class does not. **********************************************************************/ static void pendClassInstallation(Class cls, const char *superName) { NXMapTable *table; PendingSubclass *pending; PendingSubclass *oldList; PendingSubclass *l; // Create and/or locate pending class lookup table table = pendingSubclassesMapTable (); // Make sure this class isn't already in the pending list. oldList = (PendingSubclass *)NXMapGet(table, superName); for (l = oldList; l != nil; l = l->next) { if (l->subclass == cls) return; // already here, nothing to do } // Create entry referring to this class pending = (PendingSubclass *)malloc(sizeof(PendingSubclass)); pending->subclass = cls; // Link new entry into head of list of entries for this class pending->next = oldList; // (Re)place entry list in the table NXMapKeyCopyingInsert (table, superName, pending); } /*********************************************************************** * pendClassReference * Fix up a class ref when the class with the given name becomes connected. **********************************************************************/ static void pendClassReference(Class *ref, const char *className, bool isMeta) { NXMapTable *table; PendingClassRef *pending; // Create and/or locate pending class lookup table table = pendingClassRefsMapTable (); // Create entry containing the class reference pending = (PendingClassRef *)malloc(sizeof(PendingClassRef)); pending->ref = ref; if (isMeta) { pending->ref = (Class *)((uintptr_t)pending->ref | 1); } // Link new entry into head of list of entries for this class pending->next = (PendingClassRef *)NXMapGet(table, className); // (Re)place entry list in the table NXMapKeyCopyingInsert (table, className, pending); if (PrintConnecting) { _objc_inform("CONNECT: pended reference to class '%s%s' at %p", className, isMeta ? " (meta)" : "", (void *)ref); } } /*********************************************************************** * resolve_references_to_class * Fix up any pending class refs to this class. **********************************************************************/ static void resolve_references_to_class(Class cls) { PendingClassRef *pending; if (!pendingClassRefsMap) return; // no unresolved refs for any class pending = (PendingClassRef *)NXMapGet(pendingClassRefsMap, cls->name); if (!pending) return; // no unresolved refs for this class NXMapKeyFreeingRemove(pendingClassRefsMap, cls->name); if (PrintConnecting) { _objc_inform("CONNECT: resolving references to class '%s'", cls->name); } while (pending) { PendingClassRef *next = pending->next; if (pending->ref) { bool isMeta = (uintptr_t)pending->ref & 1; Class *ref = (Class *)((uintptr_t)pending->ref & ~(uintptr_t)1); *ref = isMeta ? cls->ISA() : cls; } free(pending); pending = next; } if (NXCountMapTable(pendingClassRefsMap) == 0) { NXFreeMapTable(pendingClassRefsMap); pendingClassRefsMap = nil; } } /*********************************************************************** * resolve_subclasses_of_class * Fix up any pending subclasses of this class. **********************************************************************/ static void resolve_subclasses_of_class(Class cls) { PendingSubclass *pending; if (!pendingSubclassesMap) return; // no unresolved subclasses pending = (PendingSubclass *)NXMapGet(pendingSubclassesMap, cls->name); if (!pending) return; // no unresolved subclasses for this class NXMapKeyFreeingRemove(pendingSubclassesMap, cls->name); // Destroy the pending table if it's now empty, to save memory. if (NXCountMapTable(pendingSubclassesMap) == 0) { NXFreeMapTable(pendingSubclassesMap); pendingSubclassesMap = nil; } if (PrintConnecting) { _objc_inform("CONNECT: resolving subclasses of class '%s'", cls->name); } while (pending) { PendingSubclass *next = pending->next; if (pending->subclass) connect_class(pending->subclass); free(pending); pending = next; } } /*********************************************************************** * really_connect_class * Connect cls to superclass supercls unconditionally. * Also adjust the class hash tables and handle pended subclasses. * * This should be called from connect_class() ONLY. **********************************************************************/ static void really_connect_class(Class cls, Class supercls) { Class oldCls; // Connect superclass pointers. set_superclass(cls, supercls, YES); // Done! cls->info |= CLS_CONNECTED; { mutex_locker_t lock(classLock); // Update hash tables. NXHashRemove(unconnected_class_hash, cls); oldCls = (Class)NXHashInsert(class_hash, cls); // Delete unconnected_class_hash if it is now empty. if (NXCountHashTable(unconnected_class_hash) == 0) { NXFreeHashTable(unconnected_class_hash); unconnected_class_hash = nil; } // No duplicate classes allowed. // Duplicates should have been rejected by _objc_read_classes_from_image assert(!oldCls); } // Fix up pended class refs to this class, if any resolve_references_to_class(cls); // Connect newly-connectable subclasses resolve_subclasses_of_class(cls); // Debugging: if this class has ivars, make sure this class's ivars don't // overlap with its super's. This catches some broken fragile base classes. // Do not use super->instance_size vs. self->ivar[0] to check this. // Ivars may be packed across instance_size boundaries. if (DebugFragileSuperclasses && cls->ivars && cls->ivars->ivar_count) { Class ivar_cls = supercls; // Find closest superclass that has some ivars, if one exists. while (ivar_cls && (!ivar_cls->ivars || ivar_cls->ivars->ivar_count == 0)) { ivar_cls = ivar_cls->superclass; } if (ivar_cls) { // Compare superclass's last ivar to this class's first ivar old_ivar *super_ivar = &ivar_cls->ivars->ivar_list[ivar_cls->ivars->ivar_count - 1]; old_ivar *self_ivar = &cls->ivars->ivar_list[0]; // fixme could be smarter about super's ivar size if (self_ivar->ivar_offset <= super_ivar->ivar_offset) { _objc_inform("WARNING: ivars of superclass '%s' and " "subclass '%s' overlap; superclass may have " "changed since subclass was compiled", ivar_cls->name, cls->name); } } } } /*********************************************************************** * connect_class * Connect class cls to its superclasses, if possible. * If cls becomes connected, move it from unconnected_class_hash * to connected_class_hash. * Returns TRUE if cls is connected. * Returns FALSE if cls could not be connected for some reason * (missing superclass or still-unconnected superclass) **********************************************************************/ static bool connect_class(Class cls) { if (cls->isConnected()) { // This class is already connected to its superclass. // Do nothing. return TRUE; } else if (cls->superclass == nil) { // This class is a root class. // Connect it to itself. if (PrintConnecting) { _objc_inform("CONNECT: class '%s' now connected (root class)", cls->name); } really_connect_class(cls, nil); return TRUE; } else { // This class is not a root class and is not yet connected. // Connect it if its superclass and root class are already connected. // Otherwise, add this class to the to-be-connected list, // pending the completion of its superclass and root class. // At this point, cls->superclass and cls->ISA()->ISA() are still STRINGS char *supercls_name = (char *)cls->superclass; Class supercls; // YES unconnected, YES class handler if (nil == (supercls = look_up_class(supercls_name, YES, YES))) { // Superclass does not exist yet. // pendClassInstallation will handle duplicate pends of this class pendClassInstallation(cls, supercls_name); if (PrintConnecting) { _objc_inform("CONNECT: class '%s' NOT connected (missing super)", cls->name); } return FALSE; } if (! connect_class(supercls)) { // Superclass exists but is not yet connected. // pendClassInstallation will handle duplicate pends of this class pendClassInstallation(cls, supercls_name); if (PrintConnecting) { _objc_inform("CONNECT: class '%s' NOT connected (unconnected super)", cls->name); } return FALSE; } // Superclass exists and is connected. // Connect this class to the superclass. if (PrintConnecting) { _objc_inform("CONNECT: class '%s' now connected", cls->name); } really_connect_class(cls, supercls); return TRUE; } } /*********************************************************************** * _objc_read_categories_from_image. * Read all categories from the given image. * Install them on their parent classes, or register them for later * installation. * Returns YES if some method caches now need to be flushed. **********************************************************************/ static bool _objc_read_categories_from_image (header_info * hi) { Module mods; size_t midx; bool needFlush = NO; if (hi->info()->isReplacement()) { // Ignore any categories in this image return NO; } // Major loop - process all modules in the header mods = hi->mod_ptr; // NOTE: The module and category lists are traversed backwards // to preserve the pre-10.4 processing order. Changing the order // would have a small chance of introducing binary compatibility bugs. midx = hi->mod_count; while (midx-- > 0) { unsigned int index; unsigned int total; // Nothing to do for a module without a symbol table if (mods[midx].symtab == nil) continue; // Total entries in symbol table (class entries followed // by category entries) total = mods[midx].symtab->cls_def_cnt + mods[midx].symtab->cat_def_cnt; // Minor loop - register all categories from given module index = total; while (index-- > mods[midx].symtab->cls_def_cnt) { old_category *cat = (old_category *)mods[midx].symtab->defs[index]; needFlush |= _objc_register_category(cat, (int)mods[midx].version); } } return needFlush; } /*********************************************************************** * _objc_read_classes_from_image. * Read classes from the given image, perform assorted minor fixups, * scan for +load implementation. * Does not connect classes to superclasses. * Does attach pended categories to the classes. * Adds all classes to unconnected_class_hash. class_hash is unchanged. **********************************************************************/ static void _objc_read_classes_from_image(header_info *hi) { unsigned int index; unsigned int midx; Module mods; int isBundle = headerIsBundle(hi); if (hi->info()->isReplacement()) { // Ignore any classes in this image return; } // class_hash starts small, enough only for libobjc itself. // If other Objective-C libraries are found, immediately resize // class_hash, assuming that Foundation and AppKit are about // to add lots of classes. { mutex_locker_t lock(classLock); if (hi->mhdr() != libobjc_header && _NXHashCapacity(class_hash) < 1024) { _NXHashRehashToCapacity(class_hash, 1024); } } // Major loop - process all modules in the image mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx += 1) { // Skip module containing no classes if (mods[midx].symtab == nil) continue; // Minor loop - process all the classes in given module for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) { Class newCls, oldCls; bool rejected; // Locate the class description pointer newCls = (Class)mods[midx].symtab->defs[index]; // Classes loaded from Mach-O bundles can be unloaded later. // Nothing uses this class yet, so cls->setInfo is not needed. if (isBundle) newCls->info |= CLS_FROM_BUNDLE; if (isBundle) newCls->ISA()->info |= CLS_FROM_BUNDLE; // Use common static empty cache instead of nil if (newCls->cache == nil) newCls->cache = (Cache) &_objc_empty_cache; if (newCls->ISA()->cache == nil) newCls->ISA()->cache = (Cache) &_objc_empty_cache; // Set metaclass version newCls->ISA()->version = mods[midx].version; // methodLists is nil or a single list, not an array newCls->info |= CLS_NO_METHOD_ARRAY|CLS_NO_PROPERTY_ARRAY; newCls->ISA()->info |= CLS_NO_METHOD_ARRAY|CLS_NO_PROPERTY_ARRAY; // class has no subclasses for cache flushing newCls->info |= CLS_LEAF; newCls->ISA()->info |= CLS_LEAF; if (mods[midx].version >= 6) { // class structure has ivar_layout and ext fields newCls->info |= CLS_EXT; newCls->ISA()->info |= CLS_EXT; } // Check for +load implementation before categories are attached if (_class_hasLoadMethod(newCls)) { newCls->ISA()->info |= CLS_HAS_LOAD_METHOD; } // Install into unconnected_class_hash. { mutex_locker_t lock(classLock); if (future_class_hash) { Class futureCls = (Class) NXHashRemove(future_class_hash, newCls); if (futureCls) { // Another class structure for this class was already // prepared by objc_getFutureClass(). Use it instead. free((char *)futureCls->name); memcpy(futureCls, newCls, sizeof(objc_class)); setOriginalClassForFutureClass(futureCls, newCls); newCls = futureCls; if (NXCountHashTable(future_class_hash) == 0) { NXFreeHashTable(future_class_hash); future_class_hash = nil; } } } if (!unconnected_class_hash) { unconnected_class_hash = NXCreateHashTable(classHashPrototype, 128, nil); } if ((oldCls = (Class)NXHashGet(class_hash, newCls)) || (oldCls = (Class)NXHashGet(unconnected_class_hash, newCls))) { // Another class with this name exists. Complain and reject. inform_duplicate(newCls->name, oldCls, newCls); rejected = YES; } else { NXHashInsert(unconnected_class_hash, newCls); rejected = NO; } } if (!rejected) { // Attach pended categories for this class, if any resolve_categories_for_class(newCls); } } } } /*********************************************************************** * _objc_connect_classes_from_image. * Connect the classes in the given image to their superclasses, * or register them for later connection if any superclasses are missing. **********************************************************************/ static void _objc_connect_classes_from_image(header_info *hi) { unsigned int index; unsigned int midx; Module mods; bool replacement = hi->info()->isReplacement(); // Major loop - process all modules in the image mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx += 1) { // Skip module containing no classes if (mods[midx].symtab == nil) continue; // Minor loop - process all the classes in given module for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) { Class cls = (Class)mods[midx].symtab->defs[index]; if (! replacement) { bool connected; Class futureCls = getFutureClassForOriginalClass(cls); if (futureCls) { // objc_getFutureClass() requested a different class // struct. Fix up the original struct's superclass // field for [super ...] use, but otherwise perform // fixups on the new class struct only. const char *super_name = (const char *) cls->superclass; if (super_name) cls->superclass = objc_getClass(super_name); cls = futureCls; } connected = connect_class(cls); if (connected && callbackFunction) { (*callbackFunction)(cls, 0); } } else { // Replacement image - fix up superclass only (#3704817) // And metaclass's superclass (#5351107) const char *super_name = (const char *) cls->superclass; if (super_name) { cls->superclass = objc_getClass(super_name); // metaclass's superclass is superclass's metaclass cls->ISA()->superclass = cls->superclass->ISA(); } else { // Replacement for a root class // cls->superclass already nil // root metaclass's superclass is root class cls->ISA()->superclass = cls; } } } } } /*********************************************************************** * _objc_map_class_refs_for_image. Convert the class ref entries from * a class name string pointer to a class pointer. If the class does * not yet exist, the reference is added to a list of pending references * to be fixed up at a later date. **********************************************************************/ static void fix_class_ref(Class *ref, const char *name, bool isMeta) { Class cls; // Get pointer to class of this name // NO unconnected, YES class loader // (real class with weak-missing superclass is unconnected now) cls = look_up_class(name, NO, YES); if (cls) { // Referenced class exists. Fix up the reference. *ref = isMeta ? cls->ISA() : cls; } else { // Referenced class does not exist yet. Insert nil for now // (weak-linking) and fix up the reference if the class arrives later. pendClassReference (ref, name, isMeta); *ref = nil; } } static void _objc_map_class_refs_for_image (header_info * hi) { Class *cls_refs; size_t count; unsigned int index; // Locate class refs in image cls_refs = _getObjcClassRefs (hi, &count); if (cls_refs) { // Process each class ref for (index = 0; index < count; index += 1) { // Ref is initially class name char* const char *name = (const char *) cls_refs[index]; if (!name) continue; fix_class_ref(&cls_refs[index], name, NO /*never meta*/); } } } /*********************************************************************** * _objc_remove_pending_class_refs_in_image * Delete any pending class ref fixups for class refs in the given image, * because the image is about to be unloaded. **********************************************************************/ static void removePendingReferences(Class *refs, size_t count) { Class *end = refs + count; if (!refs) return; if (!pendingClassRefsMap) return; // Search the pending class ref table for class refs in this range. // The class refs may have already been stomped with nil, // so there's no way to recover the original class name. { const char *key; PendingClassRef *pending; NXMapState state = NXInitMapState(pendingClassRefsMap); while(NXNextMapState(pendingClassRefsMap, &state, (const void **)&key, (const void **)&pending)) { for ( ; pending != nil; pending = pending->next) { if (pending->ref >= refs && pending->ref < end) { pending->ref = nil; } } } } } static void _objc_remove_pending_class_refs_in_image(header_info *hi) { Class *cls_refs; size_t count; // Locate class refs in this image cls_refs = _getObjcClassRefs(hi, &count); removePendingReferences(cls_refs, count); } /*********************************************************************** * map_selrefs. For each selector in the specified array, * replace the name pointer with a uniqued selector. * If copy is TRUE, all selector data is always copied. This is used * for registering selectors from unloadable bundles, so the selector * can still be used after the bundle's data segment is unmapped. * Returns YES if dst was written to, NO if it was unchanged. **********************************************************************/ static inline void map_selrefs(SEL *sels, size_t count, bool copy) { size_t index; if (!sels) return; mutex_locker_t lock(selLock); // Process each selector for (index = 0; index < count; index += 1) { SEL sel; // Lookup pointer to uniqued string sel = sel_registerNameNoLock((const char *) sels[index], copy); // Replace this selector with uniqued one (avoid // modifying the VM page if this would be a NOP) if (sels[index] != sel) { sels[index] = sel; } } } /*********************************************************************** * map_method_descs. For each method in the specified method list, * replace the name pointer with a uniqued selector. * If copy is TRUE, all selector data is always copied. This is used * for registering selectors from unloadable bundles, so the selector * can still be used after the bundle's data segment is unmapped. **********************************************************************/ static void map_method_descs (struct objc_method_description_list * methods, bool copy) { int index; if (!methods) return; mutex_locker_t lock(selLock); // Process each method for (index = 0; index < methods->count; index += 1) { struct objc_method_description * method; SEL sel; // Get method entry to fix up method = &methods->list[index]; // Lookup pointer to uniqued string sel = sel_registerNameNoLock((const char *) method->name, copy); // Replace this selector with uniqued one (avoid // modifying the VM page if this would be a NOP) if (method->name != sel) method->name = sel; } } /*********************************************************************** * ext_for_protocol * Returns the protocol extension for the given protocol. * Returns nil if the protocol has no extension. **********************************************************************/ static old_protocol_ext *ext_for_protocol(old_protocol *proto) { if (!proto) return nil; if (!protocol_ext_map) return nil; else return (old_protocol_ext *)NXMapGet(protocol_ext_map, proto); } /*********************************************************************** * lookup_method * Search a protocol method list for a selector. **********************************************************************/ static struct objc_method_description * lookup_method(struct objc_method_description_list *mlist, SEL aSel) { if (mlist) { int i; for (i = 0; i < mlist->count; i++) { if (mlist->list[i].name == aSel) { return mlist->list+i; } } } return nil; } /*********************************************************************** * lookup_protocol_method * Search for a selector in a protocol * (and optionally recursively all incorporated protocols) **********************************************************************/ struct objc_method_description * lookup_protocol_method(old_protocol *proto, SEL aSel, bool isRequiredMethod, bool isInstanceMethod, bool recursive) { struct objc_method_description *m = nil; old_protocol_ext *ext; if (isRequiredMethod) { if (isInstanceMethod) { m = lookup_method(proto->instance_methods, aSel); } else { m = lookup_method(proto->class_methods, aSel); } } else if ((ext = ext_for_protocol(proto))) { if (isInstanceMethod) { m = lookup_method(ext->optional_instance_methods, aSel); } else { m = lookup_method(ext->optional_class_methods, aSel); } } if (!m && recursive && proto->protocol_list) { int i; for (i = 0; !m && i < proto->protocol_list->count; i++) { m = lookup_protocol_method(proto->protocol_list->list[i], aSel, isRequiredMethod,isInstanceMethod,true); } } return m; } /*********************************************************************** * protocol_getName * Returns the name of the given protocol. **********************************************************************/ const char *protocol_getName(Protocol *p) { old_protocol *proto = oldprotocol(p); if (!proto) return "nil"; return proto->protocol_name; } /*********************************************************************** * protocol_getMethodDescription * Returns the description of a named method. * Searches either required or optional methods. * Searches either instance or class methods. **********************************************************************/ struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod) { struct objc_method_description empty = {nil, nil}; old_protocol *proto = oldprotocol(p); struct objc_method_description *desc; if (!proto) return empty; desc = lookup_protocol_method(proto, aSel, isRequiredMethod, isInstanceMethod, true); if (desc) return *desc; else return empty; } /*********************************************************************** * protocol_copyMethodDescriptionList * Returns an array of method descriptions from a protocol. * Copies either required or optional methods. * Copies either instance or class methods. **********************************************************************/ struct objc_method_description * protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount) { struct objc_method_description_list *mlist = nil; old_protocol *proto = oldprotocol(p); old_protocol_ext *ext; unsigned int i, count; struct objc_method_description *result; if (!proto) { if (outCount) *outCount = 0; return nil; } if (isRequiredMethod) { if (isInstanceMethod) { mlist = proto->instance_methods; } else { mlist = proto->class_methods; } } else if ((ext = ext_for_protocol(proto))) { if (isInstanceMethod) { mlist = ext->optional_instance_methods; } else { mlist = ext->optional_class_methods; } } if (!mlist) { if (outCount) *outCount = 0; return nil; } count = mlist->count; result = (struct objc_method_description *) calloc(count + 1, sizeof(struct objc_method_description)); for (i = 0; i < count; i++) { result[i] = mlist->list[i]; } if (outCount) *outCount = count; return result; } objc_property_t protocol_getProperty(Protocol *p, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty) { old_protocol *proto = oldprotocol(p); old_protocol_ext *ext; old_protocol_list *proto_list; if (!proto || !name) return nil; if (!isRequiredProperty) { // Only required properties are currently supported return nil; } if ((ext = ext_for_protocol(proto))) { old_property_list *plist; if (isInstanceProperty) plist = ext->instance_properties; else if (ext->hasClassPropertiesField()) plist = ext->class_properties; else plist = nil; if (plist) { uint32_t i; for (i = 0; i < plist->count; i++) { old_property *prop = property_list_nth(plist, i); if (0 == strcmp(name, prop->name)) { return (objc_property_t)prop; } } } } if ((proto_list = proto->protocol_list)) { int i; for (i = 0; i < proto_list->count; i++) { objc_property_t prop = protocol_getProperty((Protocol *)proto_list->list[i], name, isRequiredProperty, isInstanceProperty); if (prop) return prop; } } return nil; } objc_property_t * protocol_copyPropertyList2(Protocol *p, unsigned int *outCount, BOOL isRequiredProperty, BOOL isInstanceProperty) { old_property **result = nil; old_protocol_ext *ext; old_property_list *plist; old_protocol *proto = oldprotocol(p); if (! (ext = ext_for_protocol(proto)) || !isRequiredProperty) { // Only required properties are currently supported. if (outCount) *outCount = 0; return nil; } if (isInstanceProperty) plist = ext->instance_properties; else if (ext->hasClassPropertiesField()) plist = ext->class_properties; else plist = nil; result = copyPropertyList(plist, outCount); return (objc_property_t *)result; } objc_property_t *protocol_copyPropertyList(Protocol *p, unsigned int *outCount) { return protocol_copyPropertyList2(p, outCount, YES, YES); } /*********************************************************************** * protocol_copyProtocolList * Copies this protocol's incorporated protocols. * Does not copy those protocol's incorporated protocols in turn. **********************************************************************/ Protocol * __unsafe_unretained * protocol_copyProtocolList(Protocol *p, unsigned int *outCount) { unsigned int count = 0; Protocol **result = nil; old_protocol *proto = oldprotocol(p); if (!proto) { if (outCount) *outCount = 0; return nil; } if (proto->protocol_list) { count = (unsigned int)proto->protocol_list->count; } if (count > 0) { unsigned int i; result = (Protocol **)malloc((count+1) * sizeof(Protocol *)); for (i = 0; i < count; i++) { result[i] = (Protocol *)proto->protocol_list->list[i]; } result[i] = nil; } if (outCount) *outCount = count; return result; } BOOL protocol_conformsToProtocol(Protocol *self_gen, Protocol *other_gen) { old_protocol *self = oldprotocol(self_gen); old_protocol *other = oldprotocol(other_gen); if (!self || !other) { return NO; } if (0 == strcmp(self->protocol_name, other->protocol_name)) { return YES; } if (self->protocol_list) { int i; for (i = 0; i < self->protocol_list->count; i++) { old_protocol *proto = self->protocol_list->list[i]; if (0 == strcmp(other->protocol_name, proto->protocol_name)) { return YES; } if (protocol_conformsToProtocol((Protocol *)proto, other_gen)) { return YES; } } } return NO; } BOOL protocol_isEqual(Protocol *self, Protocol *other) { if (self == other) return YES; if (!self || !other) return NO; if (!protocol_conformsToProtocol(self, other)) return NO; if (!protocol_conformsToProtocol(other, self)) return NO; return YES; } /*********************************************************************** * _protocol_getMethodTypeEncoding * Return the @encode string for the requested protocol method. * Returns nil if the compiler did not emit any extended @encode data. * Locking: runtimeLock must not be held by the caller **********************************************************************/ const char * _protocol_getMethodTypeEncoding(Protocol *proto_gen, SEL sel, BOOL isRequiredMethod, BOOL isInstanceMethod) { old_protocol *proto = oldprotocol(proto_gen); if (!proto) return nil; old_protocol_ext *ext = ext_for_protocol(proto); if (!ext) return nil; if (ext->size < offsetof(old_protocol_ext, extendedMethodTypes) + sizeof(ext->extendedMethodTypes)) return nil; if (! ext->extendedMethodTypes) return nil; struct objc_method_description *m = lookup_protocol_method(proto, sel, isRequiredMethod, isInstanceMethod, false); if (!m) { // No method with that name. Search incorporated protocols. if (proto->protocol_list) { for (int i = 0; i < proto->protocol_list->count; i++) { const char *enc = _protocol_getMethodTypeEncoding((Protocol *)proto->protocol_list->list[i], sel, isRequiredMethod, isInstanceMethod); if (enc) return enc; } } return nil; } int i = 0; if (isRequiredMethod && isInstanceMethod) { i += ((uintptr_t)m - (uintptr_t)proto->instance_methods) / sizeof(proto->instance_methods->list[0]); goto done; } else if (proto->instance_methods) { i += proto->instance_methods->count; } if (isRequiredMethod && !isInstanceMethod) { i += ((uintptr_t)m - (uintptr_t)proto->class_methods) / sizeof(proto->class_methods->list[0]); goto done; } else if (proto->class_methods) { i += proto->class_methods->count; } if (!isRequiredMethod && isInstanceMethod) { i += ((uintptr_t)m - (uintptr_t)ext->optional_instance_methods) / sizeof(ext->optional_instance_methods->list[0]); goto done; } else if (ext->optional_instance_methods) { i += ext->optional_instance_methods->count; } if (!isRequiredMethod && !isInstanceMethod) { i += ((uintptr_t)m - (uintptr_t)ext->optional_class_methods) / sizeof(ext->optional_class_methods->list[0]); goto done; } else if (ext->optional_class_methods) { i += ext->optional_class_methods->count; } done: return ext->extendedMethodTypes[i]; } /*********************************************************************** * objc_allocateProtocol * Creates a new protocol. The protocol may not be used until * objc_registerProtocol() is called. * Returns nil if a protocol with the same name already exists. * Locking: acquires classLock **********************************************************************/ Protocol * objc_allocateProtocol(const char *name) { Class cls = objc_getClass("__IncompleteProtocol"); assert(cls); mutex_locker_t lock(classLock); if (NXMapGet(protocol_map, name)) return nil; old_protocol *result = (old_protocol *) calloc(1, sizeof(old_protocol) + sizeof(old_protocol_ext)); old_protocol_ext *ext = (old_protocol_ext *)(result+1); result->isa = cls; result->protocol_name = strdup(name); ext->size = sizeof(old_protocol_ext); // fixme reserve name without installing NXMapInsert(protocol_ext_map, result, result+1); return (Protocol *)result; } /*********************************************************************** * objc_registerProtocol * Registers a newly-constructed protocol. The protocol is now * ready for use and immutable. * Locking: acquires classLock **********************************************************************/ void objc_registerProtocol(Protocol *proto_gen) { old_protocol *proto = oldprotocol(proto_gen); Class oldcls = objc_getClass("__IncompleteProtocol"); Class cls = objc_getClass("Protocol"); mutex_locker_t lock(classLock); if (proto->isa == cls) { _objc_inform("objc_registerProtocol: protocol '%s' was already " "registered!", proto->protocol_name); return; } if (proto->isa != oldcls) { _objc_inform("objc_registerProtocol: protocol '%s' was not allocated " "with objc_allocateProtocol!", proto->protocol_name); return; } proto->isa = cls; NXMapKeyCopyingInsert(protocol_map, proto->protocol_name, proto); } /*********************************************************************** * protocol_addProtocol * Adds an incorporated protocol to another protocol. * No method enforcement is performed. * `proto` must be under construction. `addition` must not. * Locking: acquires classLock **********************************************************************/ void protocol_addProtocol(Protocol *proto_gen, Protocol *addition_gen) { old_protocol *proto = oldprotocol(proto_gen); old_protocol *addition = oldprotocol(addition_gen); Class cls = objc_getClass("__IncompleteProtocol"); if (!proto_gen) return; if (!addition_gen) return; mutex_locker_t lock(classLock); if (proto->isa != cls) { _objc_inform("protocol_addProtocol: modified protocol '%s' is not " "under construction!", proto->protocol_name); return; } if (addition->isa == cls) { _objc_inform("protocol_addProtocol: added protocol '%s' is still " "under construction!", addition->protocol_name); return; } old_protocol_list *protolist = proto->protocol_list; if (protolist) { size_t size = sizeof(old_protocol_list) + protolist->count * sizeof(protolist->list[0]); protolist = (old_protocol_list *) realloc(protolist, size); } else { protolist = (old_protocol_list *) calloc(1, sizeof(old_protocol_list)); } protolist->list[protolist->count++] = addition; proto->protocol_list = protolist; } /*********************************************************************** * protocol_addMethodDescription * Adds a method to a protocol. The protocol must be under construction. * Locking: acquires classLock **********************************************************************/ static void _protocol_addMethod(struct objc_method_description_list **list, SEL name, const char *types) { if (!*list) { *list = (struct objc_method_description_list *) calloc(sizeof(struct objc_method_description_list), 1); } else { size_t size = sizeof(struct objc_method_description_list) + (*list)->count * sizeof(struct objc_method_description); *list = (struct objc_method_description_list *) realloc(*list, size); } struct objc_method_description *desc = &(*list)->list[(*list)->count++]; desc->name = name; desc->types = strdup(types ?: ""); } void protocol_addMethodDescription(Protocol *proto_gen, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod) { old_protocol *proto = oldprotocol(proto_gen); Class cls = objc_getClass("__IncompleteProtocol"); if (!proto_gen) return; mutex_locker_t lock(classLock); if (proto->isa != cls) { _objc_inform("protocol_addMethodDescription: protocol '%s' is not " "under construction!", proto->protocol_name); return; } if (isRequiredMethod && isInstanceMethod) { _protocol_addMethod(&proto->instance_methods, name, types); } else if (isRequiredMethod && !isInstanceMethod) { _protocol_addMethod(&proto->class_methods, name, types); } else if (!isRequiredMethod && isInstanceMethod) { old_protocol_ext *ext = (old_protocol_ext *)(proto+1); _protocol_addMethod(&ext->optional_instance_methods, name, types); } else /* !isRequiredMethod && !isInstanceMethod) */ { old_protocol_ext *ext = (old_protocol_ext *)(proto+1); _protocol_addMethod(&ext->optional_class_methods, name, types); } } /*********************************************************************** * protocol_addProperty * Adds a property to a protocol. The protocol must be under construction. * Locking: acquires classLock **********************************************************************/ static void _protocol_addProperty(old_property_list **plist, const char *name, const objc_property_attribute_t *attrs, unsigned int count) { if (!*plist) { *plist = (old_property_list *) calloc(sizeof(old_property_list), 1); (*plist)->entsize = sizeof(old_property); } else { *plist = (old_property_list *) realloc(*plist, sizeof(old_property_list) + (*plist)->count * (*plist)->entsize); } old_property *prop = property_list_nth(*plist, (*plist)->count++); prop->name = strdup(name); prop->attributes = copyPropertyAttributeString(attrs, count); } void protocol_addProperty(Protocol *proto_gen, const char *name, const objc_property_attribute_t *attrs, unsigned int count, BOOL isRequiredProperty, BOOL isInstanceProperty) { old_protocol *proto = oldprotocol(proto_gen); Class cls = objc_getClass("__IncompleteProtocol"); if (!proto) return; if (!name) return; mutex_locker_t lock(classLock); if (proto->isa != cls) { _objc_inform("protocol_addProperty: protocol '%s' is not " "under construction!", proto->protocol_name); return; } old_protocol_ext *ext = ext_for_protocol(proto); if (isRequiredProperty && isInstanceProperty) { _protocol_addProperty(&ext->instance_properties, name, attrs, count); } else if (isRequiredProperty && !isInstanceProperty) { _protocol_addProperty(&ext->class_properties, name, attrs, count); } // else if (!isRequiredProperty && isInstanceProperty) { // _protocol_addProperty(&ext->optional_instance_properties, name, attrs, count); //} // else /* !isRequiredProperty && !isInstanceProperty) */ { // _protocol_addProperty(&ext->optional_class_properties, name, attrs, count); //} } /*********************************************************************** * _objc_fixup_protocol_objects_for_image. For each protocol in the * specified image, selectorize the method names and add to the protocol hash. **********************************************************************/ static bool versionIsExt(uintptr_t version, const char *names, size_t size) { // CodeWarrior used isa field for string "Protocol" // from section __OBJC,__class_names. rdar://4951638 // gcc (10.4 and earlier) used isa field for version number; // the only version number used on Mac OS X was 2. // gcc (10.5 and later) uses isa field for ext pointer if (version < 4096 /* not PAGE_SIZE */) { return NO; } if (version >= (uintptr_t)names && version < (uintptr_t)(names + size)) { return NO; } return YES; } static void fix_protocol(old_protocol *proto, Class protocolClass, bool isBundle, const char *names, size_t names_size) { uintptr_t version; if (!proto) return; version = (uintptr_t)proto->isa; // Set the protocol's isa proto->isa = protocolClass; // Fix up method lists // fixme share across duplicates map_method_descs (proto->instance_methods, isBundle); map_method_descs (proto->class_methods, isBundle); // Fix up ext, if any if (versionIsExt(version, names, names_size)) { old_protocol_ext *ext = (old_protocol_ext *)version; NXMapInsert(protocol_ext_map, proto, ext); map_method_descs (ext->optional_instance_methods, isBundle); map_method_descs (ext->optional_class_methods, isBundle); } // Record the protocol it if we don't have one with this name yet // fixme bundles - copy protocol // fixme unloading if (!NXMapGet(protocol_map, proto->protocol_name)) { NXMapKeyCopyingInsert(protocol_map, proto->protocol_name, proto); if (PrintProtocols) { _objc_inform("PROTOCOLS: protocol at %p is %s", proto, proto->protocol_name); } } else { // duplicate - do nothing if (PrintProtocols) { _objc_inform("PROTOCOLS: protocol at %p is %s (duplicate)", proto, proto->protocol_name); } } } static void _objc_fixup_protocol_objects_for_image (header_info * hi) { Class protocolClass = objc_getClass("Protocol"); size_t count, i; old_protocol **protos; int isBundle = headerIsBundle(hi); const char *names; size_t names_size; mutex_locker_t lock(classLock); // Allocate the protocol registry if necessary. if (!protocol_map) { protocol_map = NXCreateMapTable(NXStrValueMapPrototype, 32); } if (!protocol_ext_map) { protocol_ext_map = NXCreateMapTable(NXPtrValueMapPrototype, 32); } protos = _getObjcProtocols(hi, &count); names = _getObjcClassNames(hi, &names_size); for (i = 0; i < count; i++) { fix_protocol(protos[i], protocolClass, isBundle, names, names_size); } } /*********************************************************************** * _objc_fixup_selector_refs. Register all of the selectors in each * image, and fix them all up. **********************************************************************/ static void _objc_fixup_selector_refs (const header_info *hi) { size_t count; SEL *sels; bool preoptimized = hi->isPreoptimized(); if (PrintPreopt) { if (preoptimized) { _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors in %s", hi->fname()); } else if (hi->info()->optimizedByDyld()) { _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors in %s", hi->fname()); } } if (preoptimized) return; sels = _getObjcSelectorRefs (hi, &count); map_selrefs(sels, count, headerIsBundle(hi)); } static inline bool _is_threaded() { #if TARGET_OS_WIN32 return YES; #else return pthread_is_threaded_np() != 0; #endif } #if !TARGET_OS_WIN32 /*********************************************************************** * unmap_image * Process the given image which is about to be unmapped by dyld. * mh is mach_header instead of headerType because that's what * dyld_priv.h says even for 64-bit. **********************************************************************/ void unmap_image(const char *path __unused, const struct mach_header *mh) { recursive_mutex_locker_t lock(loadMethodLock); unmap_image_nolock(mh); } /*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. **********************************************************************/ void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) { recursive_mutex_locker_t lock(loadMethodLock); map_images_nolock(count, paths, mhdrs); } /*********************************************************************** * load_images * Process +load in the given images which are being mapped in by dyld. * * Locking: acquires classLock and loadMethodLock **********************************************************************/ extern void prepare_load_methods(const headerType *mhdr); void load_images(const char *path __unused, const struct mach_header *mh) { recursive_mutex_locker_t lock(loadMethodLock); // Discover +load methods prepare_load_methods((const headerType *)mh); // Call +load methods (without classLock - re-entrant) call_load_methods(); } #endif /*********************************************************************** * _read_images * Perform metadata processing for hCount images starting with firstNewHeader **********************************************************************/ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClass) { uint32_t i; bool categoriesLoaded = NO; if (!class_hash) _objc_init_class_hash(); // Parts of this order are important for correctness or performance. // Read classes from all images. for (i = 0; i < hCount; i++) { _objc_read_classes_from_image(hList[i]); } // Read categories from all images. // But not if any other threads are running - they might // call a category method before the fixups below are complete. if (!_is_threaded()) { bool needFlush = NO; for (i = 0; i < hCount; i++) { needFlush |= _objc_read_categories_from_image(hList[i]); } if (needFlush) flush_marked_caches(); categoriesLoaded = YES; } // Connect classes from all images. for (i = 0; i < hCount; i++) { _objc_connect_classes_from_image(hList[i]); } // Fix up class refs, selector refs, and protocol objects from all images. for (i = 0; i < hCount; i++) { _objc_map_class_refs_for_image(hList[i]); _objc_fixup_selector_refs(hList[i]); _objc_fixup_protocol_objects_for_image(hList[i]); } // Read categories from all images. // But not if this is the only thread - it's more // efficient to attach categories earlier if safe. if (!categoriesLoaded) { bool needFlush = NO; for (i = 0; i < hCount; i++) { needFlush |= _objc_read_categories_from_image(hList[i]); } if (needFlush) flush_marked_caches(); } // Multi-threaded category load MUST BE LAST to avoid a race. } /*********************************************************************** * prepare_load_methods * Schedule +load for classes in this image, any un-+load-ed * superclasses in other images, and any categories in this image. **********************************************************************/ // Recursively schedule +load for cls and any un-+load-ed superclasses. // cls must already be connected. static void schedule_class_load(Class cls) { if (cls->info & CLS_LOADED) return; if (cls->superclass) schedule_class_load(cls->superclass); add_class_to_loadable_list(cls); cls->info |= CLS_LOADED; } void prepare_load_methods(const headerType *mhdr) { Module mods; unsigned int midx; header_info *hi; for (hi = FirstHeader; hi; hi = hi->getNext()) { if (mhdr == hi->mhdr()) break; } if (!hi) return; if (hi->info()->isReplacement()) { // Ignore any classes in this image return; } // Major loop - process all modules in the image mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx += 1) { unsigned int index; // Skip module containing no classes if (mods[midx].symtab == nil) continue; // Minor loop - process all the classes in given module for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) { // Locate the class description pointer Class cls = (Class)mods[midx].symtab->defs[index]; if (cls->info & CLS_CONNECTED) { schedule_class_load(cls); } } } // Major loop - process all modules in the header mods = hi->mod_ptr; // NOTE: The module and category lists are traversed backwards // to preserve the pre-10.4 processing order. Changing the order // would have a small chance of introducing binary compatibility bugs. midx = (unsigned int)hi->mod_count; while (midx-- > 0) { unsigned int index; unsigned int total; Symtab symtab = mods[midx].symtab; // Nothing to do for a module without a symbol table if (mods[midx].symtab == nil) continue; // Total entries in symbol table (class entries followed // by category entries) total = mods[midx].symtab->cls_def_cnt + mods[midx].symtab->cat_def_cnt; // Minor loop - register all categories from given module index = total; while (index-- > mods[midx].symtab->cls_def_cnt) { old_category *cat = (old_category *)symtab->defs[index]; add_category_to_loadable_list((Category)cat); } } } #if TARGET_OS_WIN32 void unload_class(Class cls) { } #else /*********************************************************************** * _objc_remove_classes_in_image * Remove all classes in the given image from the runtime, because * the image is about to be unloaded. * Things to clean up: * class_hash * unconnected_class_hash * pending subclasses list (only if class is still unconnected) * loadable class list * class's method caches * class refs in all other images **********************************************************************/ // Re-pend any class references in refs that point into [start..end) static void rependClassReferences(Class *refs, size_t count, uintptr_t start, uintptr_t end) { size_t i; if (!refs) return; // Process each class ref for (i = 0; i < count; i++) { if ((uintptr_t)(refs[i]) >= start && (uintptr_t)(refs[i]) < end) { pendClassReference(&refs[i], refs[i]->name, refs[i]->info & CLS_META); refs[i] = nil; } } } void try_free(const void *p) { if (p && malloc_size(p)) free((void *)p); } // Deallocate all memory in a method list static void unload_mlist(old_method_list *mlist) { int i; for (i = 0; i < mlist->method_count; i++) { try_free(mlist->method_list[i].method_types); } try_free(mlist); } static void unload_property_list(old_property_list *proplist) { uint32_t i; if (!proplist) return; for (i = 0; i < proplist->count; i++) { old_property *prop = property_list_nth(proplist, i); try_free(prop->name); try_free(prop->attributes); } try_free(proplist); } // Deallocate all memory in a class. void unload_class(Class cls) { // Free method cache // This dereferences the cache contents; do this before freeing methods if (cls->cache && cls->cache != &_objc_empty_cache) { _cache_free(cls->cache); } // Free ivar lists if (cls->ivars) { int i; for (i = 0; i < cls->ivars->ivar_count; i++) { try_free(cls->ivars->ivar_list[i].ivar_name); try_free(cls->ivars->ivar_list[i].ivar_type); } try_free(cls->ivars); } // Free fixed-up method lists and method list array if (cls->methodLists) { // more than zero method lists if (cls->info & CLS_NO_METHOD_ARRAY) { // one method list unload_mlist((old_method_list *)cls->methodLists); } else { // more than one method list old_method_list **mlistp; for (mlistp = cls->methodLists; *mlistp != nil && *mlistp != END_OF_METHODS_LIST; mlistp++) { unload_mlist(*mlistp); } free(cls->methodLists); } } // Free protocol list old_protocol_list *protos = cls->protocols; while (protos) { old_protocol_list *dead = protos; protos = protos->next; try_free(dead); } if ((cls->info & CLS_EXT)) { if (cls->ext) { // Free property lists and property list array if (cls->ext->propertyLists) { // more than zero property lists if (cls->info & CLS_NO_PROPERTY_ARRAY) { // one property list old_property_list *proplist = (old_property_list *)cls->ext->propertyLists; unload_property_list(proplist); } else { // more than one property list old_property_list **plistp; for (plistp = cls->ext->propertyLists; *plistp != nil; plistp++) { unload_property_list(*plistp); } try_free(cls->ext->propertyLists); } } // Free weak ivar layout try_free(cls->ext->weak_ivar_layout); // Free ext try_free(cls->ext); } // Free non-weak ivar layout try_free(cls->ivar_layout); } // Free class name try_free(cls->name); // Free cls try_free(cls); } static void _objc_remove_classes_in_image(header_info *hi) { unsigned int index; unsigned int midx; Module mods; mutex_locker_t lock(classLock); // Major loop - process all modules in the image mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx += 1) { // Skip module containing no classes if (mods[midx].symtab == nil) continue; // Minor loop - process all the classes in given module for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) { Class cls; // Locate the class description pointer cls = (Class)mods[midx].symtab->defs[index]; // Remove from loadable class list, if present remove_class_from_loadable_list(cls); // Remove from unconnected_class_hash and pending subclasses if (unconnected_class_hash && NXHashMember(unconnected_class_hash, cls)) { NXHashRemove(unconnected_class_hash, cls); if (pendingSubclassesMap) { // Find this class in its superclass's pending list char *supercls_name = (char *)cls->superclass; PendingSubclass *pending = (PendingSubclass *) NXMapGet(pendingSubclassesMap, supercls_name); for ( ; pending != nil; pending = pending->next) { if (pending->subclass == cls) { pending->subclass = Nil; break; } } } } // Remove from class_hash NXHashRemove(class_hash, cls); // Free heap memory pointed to by the class unload_class(cls->ISA()); unload_class(cls); } } // Search all other images for class refs that point back to this range. // Un-fix and re-pend any such class refs. // Get the location of the dying image's __OBJC segment uintptr_t seg; unsigned long seg_size; seg = (uintptr_t)getsegmentdata(hi->mhdr(), "__OBJC", &seg_size); header_info *other_hi; for (other_hi = FirstHeader; other_hi != nil; other_hi = other_hi->getNext()) { Class *other_refs; size_t count; if (other_hi == hi) continue; // skip the image being unloaded // Fix class refs in the other image other_refs = _getObjcClassRefs(other_hi, &count); rependClassReferences(other_refs, count, seg, seg+seg_size); } } /*********************************************************************** * _objc_remove_categories_in_image * Remove all categories in the given image from the runtime, because * the image is about to be unloaded. * Things to clean up: * unresolved category list * loadable category list **********************************************************************/ static void _objc_remove_categories_in_image(header_info *hi) { Module mods; unsigned int midx; // Major loop - process all modules in the header mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx++) { unsigned int index; unsigned int total; Symtab symtab = mods[midx].symtab; // Nothing to do for a module without a symbol table if (symtab == nil) continue; // Total entries in symbol table (class entries followed // by category entries) total = symtab->cls_def_cnt + symtab->cat_def_cnt; // Minor loop - check all categories from given module for (index = symtab->cls_def_cnt; index < total; index++) { old_category *cat = (old_category *)symtab->defs[index]; // Clean up loadable category list remove_category_from_loadable_list((Category)cat); // Clean up category_hash if (category_hash) { _objc_unresolved_category *cat_entry = (_objc_unresolved_category *)NXMapGet(category_hash, cat->class_name); for ( ; cat_entry != nil; cat_entry = cat_entry->next) { if (cat_entry->cat == cat) { cat_entry->cat = nil; break; } } } } } } /*********************************************************************** * unload_paranoia * Various paranoid debugging checks that look for poorly-behaving * unloadable bundles. * Called by _objc_unmap_image when OBJC_UNLOAD_DEBUG is set. **********************************************************************/ static void unload_paranoia(header_info *hi) { // Get the location of the dying image's __OBJC segment uintptr_t seg; unsigned long seg_size; seg = (uintptr_t)getsegmentdata(hi->mhdr(), "__OBJC", &seg_size); _objc_inform("UNLOAD DEBUG: unloading image '%s' [%p..%p]", hi->fname(), (void *)seg, (void*)(seg+seg_size)); mutex_locker_t lock(classLock); // Make sure the image contains no categories on surviving classes. { Module mods; unsigned int midx; // Major loop - process all modules in the header mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx++) { unsigned int index; unsigned int total; Symtab symtab = mods[midx].symtab; // Nothing to do for a module without a symbol table if (symtab == nil) continue; // Total entries in symbol table (class entries followed // by category entries) total = symtab->cls_def_cnt + symtab->cat_def_cnt; // Minor loop - check all categories from given module for (index = symtab->cls_def_cnt; index < total; index++) { old_category *cat = (old_category *)symtab->defs[index]; struct objc_class query; query.name = cat->class_name; if (NXHashMember(class_hash, &query)) { _objc_inform("UNLOAD DEBUG: dying image contains category '%s(%s)' on surviving class '%s'!", cat->class_name, cat->category_name, cat->class_name); } } } } // Make sure no surviving class is in the dying image. // Make sure no surviving class has a superclass in the dying image. // fixme check method implementations too { Class cls; NXHashState state; state = NXInitHashState(class_hash); while (NXNextHashState(class_hash, &state, (void **)&cls)) { if ((vm_address_t)cls >= seg && (vm_address_t)cls < seg+seg_size) { _objc_inform("UNLOAD DEBUG: dying image contains surviving class '%s'!", cls->name); } if ((vm_address_t)cls->superclass >= seg && (vm_address_t)cls->superclass < seg+seg_size) { _objc_inform("UNLOAD DEBUG: dying image contains superclass '%s' of surviving class '%s'!", cls->superclass->name, cls->name); } } } } /*********************************************************************** * _unload_image * Only handles MH_BUNDLE for now. * Locking: loadMethodLock acquired by unmap_image **********************************************************************/ void _unload_image(header_info *hi) { loadMethodLock.assertLocked(); // Cleanup: // Remove image's classes from the class list and free auxiliary data. // Remove image's unresolved or loadable categories and free auxiliary data // Remove image's unresolved class refs. _objc_remove_classes_in_image(hi); _objc_remove_categories_in_image(hi); _objc_remove_pending_class_refs_in_image(hi); if (hi->proto_refs) try_free(hi->proto_refs); // Perform various debugging checks if requested. if (DebugUnload) unload_paranoia(hi); } #endif /*********************************************************************** * objc_addClass. Add the specified class to the table of known classes, * after doing a little verification and fixup. **********************************************************************/ void objc_addClass (Class cls) { OBJC_WARN_DEPRECATED; // Synchronize access to hash table mutex_locker_t lock(classLock); // Make sure both the class and the metaclass have caches! // Clear all bits of the info fields except CLS_CLASS and CLS_META. // Normally these bits are already clear but if someone tries to cons // up their own class on the fly they might need to be cleared. if (cls->cache == nil) { cls->cache = (Cache) &_objc_empty_cache; cls->info = CLS_CLASS; } if (cls->ISA()->cache == nil) { cls->ISA()->cache = (Cache) &_objc_empty_cache; cls->ISA()->info = CLS_META; } // methodLists should be: // 1. nil (Tiger and later only) // 2. A -1 terminated method list array // In either case, CLS_NO_METHOD_ARRAY remains clear. // If the user manipulates the method list directly, // they must use the magic private format. // Add the class to the table (void) NXHashInsert (class_hash, cls); // Superclass is no longer a leaf for cache flushing if (cls->superclass && (cls->superclass->info & CLS_LEAF)) { cls->superclass->clearInfo(CLS_LEAF); cls->superclass->ISA()->clearInfo(CLS_LEAF); } } /*********************************************************************** * _objcTweakMethodListPointerForClass. * Change the class's method list pointer to a method list array. * Does nothing if the method list pointer is already a method list array. * If the class is currently in use, methodListLock must be held by the caller. **********************************************************************/ static void _objcTweakMethodListPointerForClass(Class cls) { old_method_list * originalList; const int initialEntries = 4; size_t mallocSize; old_method_list ** ptr; // Do nothing if methodLists is already an array. if (cls->methodLists && !(cls->info & CLS_NO_METHOD_ARRAY)) return; // Remember existing list originalList = (old_method_list *) cls->methodLists; // Allocate and zero a method list array mallocSize = sizeof(old_method_list *) * initialEntries; ptr = (old_method_list **) calloc(1, mallocSize); // Insert the existing list into the array ptr[initialEntries - 1] = END_OF_METHODS_LIST; ptr[0] = originalList; // Replace existing list with array cls->methodLists = ptr; cls->clearInfo(CLS_NO_METHOD_ARRAY); } /*********************************************************************** * _objc_insertMethods. * Adds methods to a class. * Does not flush any method caches. * Does not take any locks. * If the class is already in use, use class_addMethods() instead. **********************************************************************/ void _objc_insertMethods(Class cls, old_method_list *mlist, old_category *cat) { old_method_list ***list; old_method_list **ptr; ptrdiff_t endIndex; size_t oldSize; size_t newSize; if (!cls->methodLists) { // cls has no methods - simply use this method list cls->methodLists = (old_method_list **)mlist; cls->setInfo(CLS_NO_METHOD_ARRAY); return; } // Log any existing methods being replaced if (PrintReplacedMethods) { int i; for (i = 0; i < mlist->method_count; i++) { extern IMP findIMPInClass(Class cls, SEL sel); SEL sel = sel_registerName((char *)mlist->method_list[i].method_name); IMP newImp = mlist->method_list[i].method_imp; IMP oldImp; if ((oldImp = findIMPInClass(cls, sel))) { logReplacedMethod(cls->name, sel, ISMETA(cls), cat ? cat->category_name : nil, oldImp, newImp); } } } // Create method list array if necessary _objcTweakMethodListPointerForClass(cls); list = &cls->methodLists; // Locate unused entry for insertion point ptr = *list; while ((*ptr != 0) && (*ptr != END_OF_METHODS_LIST)) ptr += 1; // If array is full, add to it if (*ptr == END_OF_METHODS_LIST) { // Calculate old and new dimensions endIndex = ptr - *list; oldSize = (endIndex + 1) * sizeof(void *); newSize = oldSize + sizeof(old_method_list *); // only increase by 1 // Grow the method list array by one. *list = (old_method_list **)realloc(*list, newSize); // Zero out addition part of new array bzero (&((*list)[endIndex]), newSize - oldSize); // Place new end marker (*list)[(newSize/sizeof(void *)) - 1] = END_OF_METHODS_LIST; // Insertion point corresponds to old array end ptr = &((*list)[endIndex]); } // Right shift existing entries by one bcopy (*list, (*list) + 1, (uint8_t *)ptr - (uint8_t *)*list); // Insert at method list at beginning of array **list = mlist; } /*********************************************************************** * _objc_removeMethods. * Remove methods from a class. * Does not take any locks. * Does not flush any method caches. * If the class is currently in use, use class_removeMethods() instead. **********************************************************************/ void _objc_removeMethods(Class cls, old_method_list *mlist) { old_method_list ***list; old_method_list **ptr; if (cls->methodLists == nil) { // cls has no methods return; } if (cls->methodLists == (old_method_list **)mlist) { // mlist is the class's only method list - erase it cls->methodLists = nil; return; } if (cls->info & CLS_NO_METHOD_ARRAY) { // cls has only one method list, and this isn't it - do nothing return; } // cls has a method list array - search it list = &cls->methodLists; // Locate list in the array ptr = *list; while (*ptr != mlist) { // fix for radar # 2538790 if ( *ptr == END_OF_METHODS_LIST ) return; ptr += 1; } // Remove this entry *ptr = 0; // Left shift the following entries while (*(++ptr) != END_OF_METHODS_LIST) *(ptr-1) = *ptr; *(ptr-1) = 0; } /*********************************************************************** * _objc_add_category. Install the specified category's methods and * protocols into the class it augments. * The class is assumed not to be in use yet: no locks are taken and * no method caches are flushed. **********************************************************************/ static inline void _objc_add_category(Class cls, old_category *category, int version) { if (PrintConnecting) { _objc_inform("CONNECT: attaching category '%s (%s)'", cls->name, category->category_name); } // Augment instance methods if (category->instance_methods) _objc_insertMethods (cls, category->instance_methods, category); // Augment class methods if (category->class_methods) _objc_insertMethods (cls->ISA(), category->class_methods, category); // Augment protocols if ((version >= 5) && category->protocols) { if (cls->ISA()->version >= 5) { category->protocols->next = cls->protocols; cls->protocols = category->protocols; cls->ISA()->protocols = category->protocols; } else { _objc_inform ("unable to add protocols from category %s...\n", category->category_name); _objc_inform ("class `%s' must be recompiled\n", category->class_name); } } // Augment instance properties if (version >= 7 && category->instance_properties) { if (cls->ISA()->version >= 6) { _class_addProperties(cls, category->instance_properties); } else { _objc_inform ("unable to add instance properties from category %s...\n", category->category_name); _objc_inform ("class `%s' must be recompiled\n", category->class_name); } } // Augment class properties if (version >= 7 && category->hasClassPropertiesField() && category->class_properties) { if (cls->ISA()->version >= 6) { _class_addProperties(cls->ISA(), category->class_properties); } else { _objc_inform ("unable to add class properties from category %s...\n", category->category_name); _objc_inform ("class `%s' must be recompiled\n", category->class_name); } } } /*********************************************************************** * _objc_add_category_flush_caches. Install the specified category's * methods into the class it augments, and flush the class' method cache. * Return YES if some method caches now need to be flushed. **********************************************************************/ static bool _objc_add_category_flush_caches(Class cls, old_category *category, int version) { bool needFlush = NO; // Install the category's methods into its intended class { mutex_locker_t lock(methodListLock); _objc_add_category (cls, category, version); } // Queue for cache flushing so category's methods can get called if (category->instance_methods) { cls->setInfo(CLS_FLUSH_CACHE); needFlush = YES; } if (category->class_methods) { cls->ISA()->setInfo(CLS_FLUSH_CACHE); needFlush = YES; } return needFlush; } /*********************************************************************** * reverse_cat * Reverse the given linked list of pending categories. * The pending category list is built backwards, and needs to be * reversed before actually attaching the categories to a class. * Returns the head of the new linked list. **********************************************************************/ static _objc_unresolved_category *reverse_cat(_objc_unresolved_category *cat) { _objc_unresolved_category *prev; _objc_unresolved_category *cur; _objc_unresolved_category *ahead; if (!cat) return nil; prev = nil; cur = cat; ahead = cat->next; while (cur) { ahead = cur->next; cur->next = prev; prev = cur; cur = ahead; } return prev; } /*********************************************************************** * resolve_categories_for_class. * Install all existing categories intended for the specified class. * cls must be a true class and not a metaclass. **********************************************************************/ static void resolve_categories_for_class(Class cls) { _objc_unresolved_category * pending; _objc_unresolved_category * next; // Nothing to do if there are no categories at all if (!category_hash) return; // Locate and remove first element in category list // associated with this class pending = (_objc_unresolved_category *) NXMapKeyFreeingRemove (category_hash, cls->name); // Traverse the list of categories, if any, registered for this class // The pending list is built backwards. Reverse it and walk forwards. pending = reverse_cat(pending); while (pending) { if (pending->cat) { // Install the category // use the non-flush-cache version since we are only // called from the class intialization code _objc_add_category(cls, pending->cat, (int)pending->version); } // Delink and reclaim this registration next = pending->next; free(pending); pending = next; } } /*********************************************************************** * _objc_resolve_categories_for_class. * Public version of resolve_categories_for_class. This was * exported pre-10.4 for Omni et al. to workaround a problem * with too-lazy category attachment. * cls should be a class, but this function can also cope with metaclasses. **********************************************************************/ void _objc_resolve_categories_for_class(Class cls) { // If cls is a metaclass, get the class. // resolve_categories_for_class() requires a real class to work correctly. if (ISMETA(cls)) { if (strncmp(cls->name, "_%", 2) == 0) { // Posee's meta's name is smashed and isn't in the class_hash, // so objc_getClass doesn't work. const char *baseName = strchr(cls->name, '%'); // get posee's real name cls = objc_getClass(baseName); } else { cls = objc_getClass(cls->name); } } resolve_categories_for_class(cls); } /*********************************************************************** * _objc_register_category. * Process a category read from an image. * If the category's class exists, attach the category immediately. * Classes that need cache flushing are marked but not flushed. * If the category's class does not exist yet, pend the category for * later attachment. Pending categories are attached in the order * they were discovered. * Returns YES if some method caches now need to be flushed. **********************************************************************/ static bool _objc_register_category(old_category *cat, int version) { _objc_unresolved_category * new_cat; _objc_unresolved_category * old; Class theClass; // If the category's class exists, attach the category. if ((theClass = objc_lookUpClass(cat->class_name))) { return _objc_add_category_flush_caches(theClass, cat, version); } // If the category's class exists but is unconnected, // then attach the category to the class but don't bother // flushing any method caches (because they must be empty). // YES unconnected, NO class_handler if ((theClass = look_up_class(cat->class_name, YES, NO))) { _objc_add_category(theClass, cat, version); return NO; } // Category's class does not exist yet. // Save the category for later attachment. if (PrintConnecting) { _objc_inform("CONNECT: pending category '%s (%s)'", cat->class_name, cat->category_name); } // Create category lookup table if needed if (!category_hash) category_hash = NXCreateMapTable(NXStrValueMapPrototype, 128); // Locate an existing list of categories, if any, for the class. old = (_objc_unresolved_category *) NXMapGet (category_hash, cat->class_name); // Register the category to be fixed up later. // The category list is built backwards, and is reversed again // by resolve_categories_for_class(). new_cat = (_objc_unresolved_category *) malloc(sizeof(_objc_unresolved_category)); new_cat->next = old; new_cat->cat = cat; new_cat->version = version; (void) NXMapKeyCopyingInsert (category_hash, cat->class_name, new_cat); return NO; } const char **objc_copyImageNames(unsigned int *outCount) { header_info *hi; int count = 0; int max = HeaderCount; #if TARGET_OS_WIN32 const TCHAR **names = (const TCHAR **)calloc(max+1, sizeof(TCHAR *)); #else const char **names = (const char **)calloc(max+1, sizeof(char *)); #endif for (hi = FirstHeader; hi != NULL && count < max; hi = hi->getNext()) { #if TARGET_OS_WIN32 if (hi->moduleName) { names[count++] = hi->moduleName; } #else const char *fname = hi->fname(); if (fname) { names[count++] = fname; } #endif } names[count] = NULL; if (count == 0) { // Return NULL instead of empty list if there are no images free((void *)names); names = NULL; } if (outCount) *outCount = count; return names; } static const char ** _objc_copyClassNamesForImage(header_info *hi, unsigned int *outCount) { Module mods; unsigned int m; const char **list; int count; int allocated; list = nil; count = 0; allocated = 0; mods = hi->mod_ptr; for (m = 0; m < hi->mod_count; m++) { int d; if (!mods[m].symtab) continue; for (d = 0; d < mods[m].symtab->cls_def_cnt; d++) { Class cls = (Class)mods[m].symtab->defs[d]; // fixme what about future-ified classes? if (cls->isConnected()) { if (count == allocated) { allocated = allocated*2 + 16; list = (const char **) realloc((void *)list, allocated * sizeof(char *)); } list[count++] = cls->name; } } } if (count > 0) { // nil-terminate non-empty list if (count == allocated) { allocated = allocated+1; list = (const char **) realloc((void *)list, allocated * sizeof(char *)); } list[count] = nil; } if (outCount) *outCount = count; return list; } /********************************************************************** * **********************************************************************/ const char ** objc_copyClassNamesForImage(const char *image, unsigned int *outCount) { header_info *hi; if (!image) { if (outCount) *outCount = 0; return NULL; } // Find the image. for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { #if TARGET_OS_WIN32 if (0 == wcscmp((TCHAR *)image, hi->moduleName)) break; #else if (0 == strcmp(image, hi->fname())) break; #endif } if (!hi) { if (outCount) *outCount = 0; return NULL; } return _objc_copyClassNamesForImage(hi, outCount); } /********************************************************************** * **********************************************************************/ const char ** objc_copyClassNamesForImageHeader(const struct mach_header *mh, unsigned int *outCount) { header_info *hi; if (!mh) { if (outCount) *outCount = 0; return NULL; } // Find the image. for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { if (hi->mhdr() == (const headerType *)mh) break; } if (!hi) { if (outCount) *outCount = 0; return NULL; } return _objc_copyClassNamesForImage(hi, outCount); } Class gdb_class_getClass(Class cls) { const char *className = cls->name; if(!className || !strlen(className)) return Nil; Class rCls = look_up_class(className, NO, NO); return rCls; } Class gdb_object_getClass(id obj) { if (!obj) return nil; return gdb_class_getClass(obj->getIsa()); } /*********************************************************************** * objc_setMultithreaded. **********************************************************************/ void objc_setMultithreaded (BOOL flag) { OBJC_WARN_DEPRECATED; // Nothing here. Thread synchronization in the runtime is always active. } /*********************************************************************** * Lock management **********************************************************************/ mutex_t selLock; mutex_t classLock; mutex_t methodListLock; mutex_t cacheUpdateLock; recursive_mutex_t loadMethodLock; void lock_init(void) { } #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-runtime.h ================================================ #include #include ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-runtime.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-runtime.m * Copyright 1988-1996, NeXT Software, Inc. * Author: s. naroff * **********************************************************************/ /*********************************************************************** * Imports. **********************************************************************/ #include "objc-private.h" #include "objc-loadmethod.h" #include "message.h" /*********************************************************************** * Exports. **********************************************************************/ /* Linker metadata symbols */ // NSObject was in Foundation/CF on macOS < 10.8. #if TARGET_OS_OSX #if __OBJC2__ const char __objc_nsobject_class_10_5 = 0; const char __objc_nsobject_class_10_6 = 0; const char __objc_nsobject_class_10_7 = 0; const char __objc_nsobject_metaclass_10_5 = 0; const char __objc_nsobject_metaclass_10_6 = 0; const char __objc_nsobject_metaclass_10_7 = 0; const char __objc_nsobject_isa_10_5 = 0; const char __objc_nsobject_isa_10_6 = 0; const char __objc_nsobject_isa_10_7 = 0; #else const char __objc_nsobject_class_10_5 = 0; const char __objc_nsobject_class_10_6 = 0; const char __objc_nsobject_class_10_7 = 0; #endif #endif // Settings from environment variables #define OPTION(var, env, help) bool var = false; #include "objc-env.h" #undef OPTION struct option_t { bool* var; const char *env; const char *help; size_t envlen; }; const option_t Settings[] = { #define OPTION(var, env, help) option_t{&var, #env, help, strlen(#env)}, #include "objc-env.h" #undef OPTION }; // objc's key for pthread_getspecific static tls_key_t _objc_pthread_key; // Selectors SEL SEL_load = NULL; SEL SEL_initialize = NULL; SEL SEL_resolveInstanceMethod = NULL; SEL SEL_resolveClassMethod = NULL; SEL SEL_cxx_construct = NULL; SEL SEL_cxx_destruct = NULL; SEL SEL_retain = NULL; SEL SEL_release = NULL; SEL SEL_autorelease = NULL; SEL SEL_retainCount = NULL; SEL SEL_alloc = NULL; SEL SEL_allocWithZone = NULL; SEL SEL_dealloc = NULL; SEL SEL_copy = NULL; SEL SEL_new = NULL; SEL SEL_forwardInvocation = NULL; SEL SEL_tryRetain = NULL; SEL SEL_isDeallocating = NULL; SEL SEL_retainWeakReference = NULL; SEL SEL_allowsWeakReference = NULL; header_info *FirstHeader = 0; // NULL means empty list header_info *LastHeader = 0; // NULL means invalid; recompute it int HeaderCount = 0; // Set to true on the child side of fork() // if the parent process was multithreaded when fork() was called. bool MultithreadedForkChild = false; /*********************************************************************** * objc_noop_imp. Used when we need to install a do-nothing method somewhere. **********************************************************************/ id objc_noop_imp(id self, SEL _cmd __unused) { return self; } /*********************************************************************** * objc_getClass. Return the id of the named class. If the class does * not exist, call _objc_classLoader and then objc_classHandler, either of * which may create a new class. * Warning: doesn't work if aClassName is the name of a posed-for class's isa! **********************************************************************/ Class objc_getClass(const char *aClassName) { if (!aClassName) return Nil; // NO unconnected, YES class handler return look_up_class(aClassName, NO, YES); } /*********************************************************************** * objc_getRequiredClass. * Same as objc_getClass, but kills the process if the class is not found. * This is used by ZeroLink, where failing to find a class would be a * compile-time link error without ZeroLink. **********************************************************************/ Class objc_getRequiredClass(const char *aClassName) { Class cls = objc_getClass(aClassName); if (!cls) _objc_fatal("link error: class '%s' not found.", aClassName); return cls; } /*********************************************************************** * objc_lookUpClass. Return the id of the named class. * If the class does not exist, call _objc_classLoader, which may create * a new class. * * Formerly objc_getClassWithoutWarning () **********************************************************************/ Class objc_lookUpClass(const char *aClassName) { if (!aClassName) return Nil; // NO unconnected, NO class handler return look_up_class(aClassName, NO, NO); } /*********************************************************************** * objc_getMetaClass. Return the id of the meta class the named class. * Warning: doesn't work if aClassName is the name of a posed-for class's isa! **********************************************************************/ Class objc_getMetaClass(const char *aClassName) { Class cls; if (!aClassName) return Nil; cls = objc_getClass (aClassName); if (!cls) { _objc_inform ("class `%s' not linked into application", aClassName); return Nil; } return cls->ISA(); } /*********************************************************************** * appendHeader. Add a newly-constructed header_info to the list. **********************************************************************/ void appendHeader(header_info *hi) { // Add the header to the header list. // The header is appended to the list, to preserve the bottom-up order. HeaderCount++; hi->setNext(NULL); if (!FirstHeader) { // list is empty FirstHeader = LastHeader = hi; } else { if (!LastHeader) { // list is not empty, but LastHeader is invalid - recompute it LastHeader = FirstHeader; while (LastHeader->getNext()) LastHeader = LastHeader->getNext(); } // LastHeader is now valid LastHeader->setNext(hi); LastHeader = hi; } } /*********************************************************************** * removeHeader * Remove the given header from the header list. * FirstHeader is updated. * LastHeader is set to NULL. Any code that uses LastHeader must * detect this NULL and recompute LastHeader by traversing the list. **********************************************************************/ void removeHeader(header_info *hi) { header_info *prev = NULL; header_info *current = NULL; for (current = FirstHeader; current != NULL; current = current->getNext()) { if (current == hi) { header_info *deadHead = current; // Remove from the linked list. if (prev) prev->setNext(current->getNext()); else FirstHeader = current->getNext(); // no prev so removing head // Update LastHeader if necessary. if (LastHeader == deadHead) { LastHeader = NULL; // will be recomputed next time it's used } HeaderCount--; break; } prev = current; } } /*********************************************************************** * environ_init * Read environment variables that affect the runtime. * Also print environment variable help, if requested. **********************************************************************/ void environ_init(void) { if (issetugid()) { // All environment variables are silently ignored when setuid or setgid // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves. return; } bool PrintHelp = false; bool PrintOptions = false; bool maybeMallocDebugging = false; // Scan environ[] directly instead of calling getenv() a lot. // This optimizes the case where none are set. for (char **p = *_NSGetEnviron(); *p != nil; p++) { if (0 == strncmp(*p, "Malloc", 6) || 0 == strncmp(*p, "DYLD", 4) || 0 == strncmp(*p, "NSZombiesEnabled", 16)) { maybeMallocDebugging = true; } if (0 != strncmp(*p, "OBJC_", 5)) continue; if (0 == strncmp(*p, "OBJC_HELP=", 10)) { PrintHelp = true; continue; } if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) { PrintOptions = true; continue; } const char *value = strchr(*p, '='); if (!*value) continue; value++; for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) { const option_t *opt = &Settings[i]; if ((size_t)(value - *p) == 1+opt->envlen && 0 == strncmp(*p, opt->env, opt->envlen)) { *opt->var = (0 == strcmp(value, "YES")); break; } } } // Special case: enable some autorelease pool debugging // when some malloc debugging is enabled // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO. if (maybeMallocDebugging) { const char *insert = getenv("DYLD_INSERT_LIBRARIES"); const char *zombie = getenv("NSZombiesEnabled"); const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION"); if ((getenv("MallocStackLogging") || getenv("MallocStackLoggingNoCompact") || (zombie && (*zombie == 'Y' || *zombie == 'y')) || (insert && strstr(insert, "libgmalloc"))) && (!pooldebug || 0 == strcmp(pooldebug, "YES"))) { DebugPoolAllocation = true; } } // Print OBJC_HELP and OBJC_PRINT_OPTIONS output. if (PrintHelp || PrintOptions) { if (PrintHelp) { _objc_inform("Objective-C runtime debugging. Set variable=YES to enable."); _objc_inform("OBJC_HELP: describe available environment variables"); if (PrintOptions) { _objc_inform("OBJC_HELP is set"); } _objc_inform("OBJC_PRINT_OPTIONS: list which options are set"); } if (PrintOptions) { _objc_inform("OBJC_PRINT_OPTIONS is set"); } for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) { const option_t *opt = &Settings[i]; if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help); if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env); } } } /*********************************************************************** * logReplacedMethod * OBJC_PRINT_REPLACED_METHODS implementation **********************************************************************/ void logReplacedMethod(const char *className, SEL s, bool isMeta, const char *catName, IMP oldImp, IMP newImp) { const char *oldImage = "??"; const char *newImage = "??"; // Silently ignore +load replacement because category +load is special if (s == SEL_load) return; #if TARGET_OS_WIN32 // don't know dladdr()/dli_fname equivalent #else Dl_info dl; if (dladdr((void*)oldImp, &dl) && dl.dli_fname) oldImage = dl.dli_fname; if (dladdr((void*)newImp, &dl) && dl.dli_fname) newImage = dl.dli_fname; #endif _objc_inform("REPLACED: %c[%s %s] %s%s (IMP was %p (%s), now %p (%s))", isMeta ? '+' : '-', className, sel_getName(s), catName ? "by category " : "", catName ? catName : "", oldImp, oldImage, newImp, newImage); } /*********************************************************************** * _objc_fetch_pthread_data * Fetch objc's pthread data for this thread. * If the data doesn't exist yet and create is NO, return NULL. * If the data doesn't exist yet and create is YES, allocate and return it. **********************************************************************/ _objc_pthread_data *_objc_fetch_pthread_data(bool create) { _objc_pthread_data *data; data = (_objc_pthread_data *)tls_get(_objc_pthread_key); if (!data && create) { data = (_objc_pthread_data *) calloc(1, sizeof(_objc_pthread_data)); tls_set(_objc_pthread_key, data); } return data; } /*********************************************************************** * _objc_pthread_destroyspecific * Destructor for objc's per-thread data. * arg shouldn't be NULL, but we check anyway. **********************************************************************/ extern void _destroyInitializingClassList(struct _objc_initializing_classes *list); void _objc_pthread_destroyspecific(void *arg) { _objc_pthread_data *data = (_objc_pthread_data *)arg; if (data != NULL) { _destroyInitializingClassList(data->initializingClasses); _destroySyncCache(data->syncCache); _destroyAltHandlerList(data->handlerList); for (int i = 0; i < (int)countof(data->printableNames); i++) { if (data->printableNames[i]) { free(data->printableNames[i]); } } // add further cleanup here... free(data); } } void tls_init(void) { #if SUPPORT_DIRECT_THREAD_KEYS _objc_pthread_key = TLS_DIRECT_KEY; pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific); #else _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific); #endif } /*********************************************************************** * _objcInit * Former library initializer. This function is now merely a placeholder * for external callers. All runtime initialization has now been moved * to map_images() and _objc_init. **********************************************************************/ void _objcInit(void) { // do nothing } /*********************************************************************** * objc_setForwardHandler **********************************************************************/ #if !__OBJC2__ // Default forward handler (nil) goes to forward:: dispatch. void *_objc_forward_handler = nil; void *_objc_forward_stret_handler = nil; #else // Default forward handler halts the process. __attribute__((noreturn)) void objc_defaultForwardHandler(id self, SEL sel) { _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p " "(no message forward handler is installed)", class_isMetaClass(object_getClass(self)) ? '+' : '-', object_getClassName(self), sel_getName(sel), self); } void *_objc_forward_handler = (void*)objc_defaultForwardHandler; #if SUPPORT_STRET struct stret { int i[100]; }; __attribute__((noreturn)) struct stret objc_defaultForwardStretHandler(id self, SEL sel) { objc_defaultForwardHandler(self, sel); } void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler; #endif #endif void objc_setForwardHandler(void *fwd, void *fwd_stret) { _objc_forward_handler = fwd; #if SUPPORT_STRET _objc_forward_stret_handler = fwd_stret; #endif } #if !__OBJC2__ // GrP fixme extern "C" Class _objc_getOrigClass(const char *name); #endif static BOOL internal_class_getImageName(Class cls, const char **outName) { #if !__OBJC2__ cls = _objc_getOrigClass(cls->demangledName()); #endif auto result = dyld_image_path_containing_address(cls); *outName = result; return (result != nil); } static ChainedHookFunction GetImageNameHook{internal_class_getImageName}; void objc_setHook_getImageName(objc_hook_getImageName newValue, objc_hook_getImageName *outOldValue) { GetImageNameHook.set(newValue, outOldValue); } const char *class_getImageName(Class cls) { if (!cls) return nil; const char *name; if (GetImageNameHook.get()(cls, &name)) return name; else return nil; } /********************************************************************** * Fast Enumeration Support **********************************************************************/ static void (*enumerationMutationHandler)(id); /********************************************************************** * objc_enumerationMutation * called by compiler when a mutation is detected during foreach iteration **********************************************************************/ void objc_enumerationMutation(id object) { if (enumerationMutationHandler == nil) { _objc_fatal("mutation detected during 'for(... in ...)' enumeration of object %p.", (void*)object); } (*enumerationMutationHandler)(object); } /********************************************************************** * objc_setEnumerationMutationHandler * an entry point to customize mutation error handing **********************************************************************/ void objc_setEnumerationMutationHandler(void (*handler)(id)) { enumerationMutationHandler = handler; } /********************************************************************** * Associative Reference Support **********************************************************************/ id objc_getAssociatedObject(id object, const void *key) { return _object_get_associative_reference(object, (void *)key); } void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, (void *)key, value, policy); } void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object); } } #if SUPPORT_GC_COMPAT #include // GC preflight for an app executable. enum GCness { WithGC = 1, WithoutGC = 0, Error = -1 }; // Overloaded template wrappers around clang's overflow-checked arithmetic. template bool uadd_overflow(T x, T y, T* sum); template bool usub_overflow(T x, T y, T* diff); template bool umul_overflow(T x, T y, T* prod); template bool sadd_overflow(T x, T y, T* sum); template bool ssub_overflow(T x, T y, T* diff); template bool smul_overflow(T x, T y, T* prod); template <> bool uadd_overflow(unsigned x, unsigned y, unsigned* sum) { return __builtin_uadd_overflow(x, y, sum); } template <> bool uadd_overflow(unsigned long x, unsigned long y, unsigned long* sum) { return __builtin_uaddl_overflow(x, y, sum); } template <> bool uadd_overflow(unsigned long long x, unsigned long long y, unsigned long long* sum) { return __builtin_uaddll_overflow(x, y, sum); } template <> bool usub_overflow(unsigned x, unsigned y, unsigned* diff) { return __builtin_usub_overflow(x, y, diff); } template <> bool usub_overflow(unsigned long x, unsigned long y, unsigned long* diff) { return __builtin_usubl_overflow(x, y, diff); } template <> bool usub_overflow(unsigned long long x, unsigned long long y, unsigned long long* diff) { return __builtin_usubll_overflow(x, y, diff); } template <> bool umul_overflow(unsigned x, unsigned y, unsigned* prod) { return __builtin_umul_overflow(x, y, prod); } template <> bool umul_overflow(unsigned long x, unsigned long y, unsigned long* prod) { return __builtin_umull_overflow(x, y, prod); } template <> bool umul_overflow(unsigned long long x, unsigned long long y, unsigned long long* prod) { return __builtin_umulll_overflow(x, y, prod); } template <> bool sadd_overflow(signed x, signed y, signed* sum) { return __builtin_sadd_overflow(x, y, sum); } template <> bool sadd_overflow(signed long x, signed long y, signed long* sum) { return __builtin_saddl_overflow(x, y, sum); } template <> bool sadd_overflow(signed long long x, signed long long y, signed long long* sum) { return __builtin_saddll_overflow(x, y, sum); } template <> bool ssub_overflow(signed x, signed y, signed* diff) { return __builtin_ssub_overflow(x, y, diff); } template <> bool ssub_overflow(signed long x, signed long y, signed long* diff) { return __builtin_ssubl_overflow(x, y, diff); } template <> bool ssub_overflow(signed long long x, signed long long y, signed long long* diff) { return __builtin_ssubll_overflow(x, y, diff); } template <> bool smul_overflow(signed x, signed y, signed* prod) { return __builtin_smul_overflow(x, y, prod); } template <> bool smul_overflow(signed long x, signed long y, signed long* prod) { return __builtin_smull_overflow(x, y, prod); } template <> bool smul_overflow(signed long long x, signed long long y, signed long long* prod) { return __builtin_smulll_overflow(x, y, prod); } // Range-checking subview of a file. class FileSlice { int fd; uint64_t sliceOffset; uint64_t sliceSize; public: FileSlice() : fd(-1), sliceOffset(0), sliceSize(0) { } FileSlice(int newfd, uint64_t newOffset, uint64_t newSize) : fd(newfd) , sliceOffset(newOffset) , sliceSize(newSize) { } // Read bytes from this slice. // Returns YES if all bytes were read successfully. bool pread(void *buf, uint64_t readSize, uint64_t readOffset = 0) { uint64_t readEnd; if (uadd_overflow(readOffset, readSize, &readEnd)) return NO; if (readEnd > sliceSize) return NO; uint64_t preadOffset; if (uadd_overflow(sliceOffset, readOffset, &preadOffset)) return NO; int64_t readed = ::pread(fd, buf, (size_t)readSize, preadOffset); if (readed < 0 || (uint64_t)readed != readSize) return NO; return YES; } // Create a new slice that is a subset of this slice. // Returnes YES if successful. bool slice(uint64_t newOffset, uint64_t newSize, FileSlice& result) { // fixme arithmetic overflow uint64_t newEnd; if (uadd_overflow(newOffset, newSize, &newEnd)) return NO; if (newEnd > sliceSize) return NO; if (uadd_overflow(sliceOffset, newOffset, &result.sliceOffset)) { return NO; } result.sliceSize = newSize; result.fd = fd; return YES; } // Shorten this slice in place by removing a range from the start. bool advance(uint64_t distance) { if (distance > sliceSize) return NO; if (uadd_overflow(sliceOffset, distance, &sliceOffset)) return NO; if (usub_overflow(sliceSize, distance, &sliceSize)) return NO; return YES; } }; // Arch32 and Arch64 are used to specialize sliceRequiresGC() // to interrogate old-ABI i386 and new-ABI x86_64 files. struct Arch32 { using mh_t = struct mach_header; using segment_command_t = struct segment_command; using section_t = struct section; enum : cpu_type_t { cputype = CPU_TYPE_X86 }; enum : int { segment_cmd = LC_SEGMENT }; static bool isObjCSegment(const char *segname) { return segnameEquals(segname, "__OBJC"); } static bool isImageInfoSection(const char *sectname) { return sectnameEquals(sectname, "__image_info"); } static bool countClasses(FileSlice file, section_t& sect, int& classCount, int& classrefCount) { if (sectnameEquals(sect.sectname, "__cls_refs")) { classrefCount += sect.size / 4; } else if (sectnameEquals(sect.sectname, "__module_info")) { struct module_t { uint32_t version; uint32_t size; uint32_t name; // not bound uint32_t symtab; // not bound }; size_t mod_count = sect.size / sizeof(module_t); if (mod_count == 0) { // no classes defined } else if (mod_count > 1) { // AppleScriptObjC apps only have one module. // Disqualify this app by setting classCount to non-zero. // We don't actually need an accurate count. classCount = 1; } else if (mod_count == 1) { FileSlice moduleSlice; if (!file.slice(sect.offset, sect.size, moduleSlice)) return NO; module_t module; if (!moduleSlice.pread(&module, sizeof(module))) return NO; if (module.symtab) { // AppleScriptObjC apps only have a module with no symtab. // Disqualify this app by setting classCount to non-zero. // We don't actually need an accurate count. classCount = 1; } } } return YES; } }; struct Arch64 { using mh_t = struct mach_header_64; using segment_command_t = struct segment_command_64; using section_t = struct section_64; enum : cpu_type_t { cputype = CPU_TYPE_X86_64 }; enum : int { segment_cmd = LC_SEGMENT_64 }; static bool isObjCSegment(const char *segname) { return segnameEquals(segname, "__DATA") || segnameEquals(segname, "__DATA_CONST") || segnameEquals(segname, "__DATA_DIRTY"); } static bool isImageInfoSection(const char *sectname) { return sectnameEquals(sectname, "__objc_imageinfo"); } static bool countClasses(FileSlice, section_t& sect, int& classCount, int& classrefCount) { if (sectnameEquals(sect.sectname, "__objc_classlist")) { classCount += sect.size / 8; } else if (sectnameEquals(sect.sectname, "__objc_classrefs")) { classrefCount += sect.size / 8; } return YES; } }; #define SANE_HEADER_SIZE (32*1024) template static int sliceRequiresGC(typename Arch::mh_t mh, FileSlice file) { // We assume there is only one arch per pointer size that can support GC. // (i386 and x86_64) if (mh.cputype != Arch::cputype) return 0; // We only check the main executable. if (mh.filetype != MH_EXECUTE) return 0; // Look for ObjC segment. // Look for AppleScriptObjC linkage. FileSlice cmds; if (!file.slice(sizeof(mh), mh.sizeofcmds, cmds)) return Error; // Exception: Some AppleScriptObjC apps built for GC can run without GC. // 1. executable defines no classes // 2. executable references NSBundle only // 3. executable links to AppleScriptObjC.framework // Note that shouldRejectGCApp() also knows about this. bool wantsGC = NO; bool linksToAppleScriptObjC = NO; int classCount = 0; int classrefCount = 0; // Disallow abusively-large executables that could hang this checker. // dyld performs similar checks (MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE) if (mh.sizeofcmds > SANE_HEADER_SIZE) return Error; if (mh.ncmds > mh.sizeofcmds / sizeof(struct load_command)) return Error; for (uint32_t cmdindex = 0; cmdindex < mh.ncmds; cmdindex++) { struct load_command lc; if (!cmds.pread(&lc, sizeof(lc))) return Error; // Disallow abusively-small load commands that could hang this checker. // dyld performs a similar check. if (lc.cmdsize < sizeof(lc)) return Error; if (lc.cmd == LC_LOAD_DYLIB || lc.cmd == LC_LOAD_UPWARD_DYLIB || lc.cmd == LC_LOAD_WEAK_DYLIB || lc.cmd == LC_REEXPORT_DYLIB) { // Look for AppleScriptObjC linkage. FileSlice dylibSlice; if (!cmds.slice(0, lc.cmdsize, dylibSlice)) return Error; struct dylib_command dylib; if (!dylibSlice.pread(&dylib, sizeof(dylib))) return Error; const char *asoFramework = "/System/Library/Frameworks/AppleScriptObjC.framework" "/Versions/A/AppleScriptObjC"; size_t asoLen = strlen(asoFramework); FileSlice nameSlice; if (dylibSlice.slice(dylib.dylib.name.offset, asoLen, nameSlice)) { char name[asoLen]; if (!nameSlice.pread(name, asoLen)) return Error; if (0 == memcmp(name, asoFramework, asoLen)) { linksToAppleScriptObjC = YES; } } } else if (lc.cmd == Arch::segment_cmd) { typename Arch::segment_command_t seg; if (!cmds.pread(&seg, sizeof(seg))) return Error; if (Arch::isObjCSegment(seg.segname)) { // ObjC segment. // Look for image info section. // Look for class implementations and class references. FileSlice sections; if (!cmds.slice(0, seg.cmdsize, sections)) return Error; if (!sections.advance(sizeof(seg))) return Error; for (uint32_t segindex = 0; segindex < seg.nsects; segindex++) { typename Arch::section_t sect; if (!sections.pread(§, sizeof(sect))) return Error; if (!Arch::isObjCSegment(sect.segname)) return Error; if (!Arch::countClasses(file, sect, classCount, classrefCount)) { return Error; } if ((sect.flags & SECTION_TYPE) == S_REGULAR && Arch::isImageInfoSection(sect.sectname)) { // ObjC image info section. // Check its contents. FileSlice section; if (!file.slice(sect.offset, sect.size, section)) { return Error; } // The subset of objc_image_info that was in use for GC. struct { uint32_t version; uint32_t flags; } ii; if (!section.pread(&ii, sizeof(ii))) return Error; if (ii.flags & (1<<1)) { // App wants GC. // Don't return yet because we need to // check the AppleScriptObjC exception. wantsGC = YES; } } if (!sections.advance(sizeof(sect))) return Error; } } } if (!cmds.advance(lc.cmdsize)) return Error; } if (!wantsGC) { // No GC bit set. return WithoutGC; } else if (linksToAppleScriptObjC && classCount == 0 && classrefCount == 1) { // Has GC bit but falls under the AppleScriptObjC exception. return WithoutGC; } else { // Has GC bit and is not AppleScriptObjC. return WithGC; } } static int sliceRequiresGC(FileSlice file) { // Read mach-o header. struct mach_header_64 mh; if (!file.pread(&mh, sizeof(mh))) return Error; // Check header magic. We assume only host-endian slices can support GC. switch (mh.magic) { case MH_MAGIC: return sliceRequiresGC(*(struct mach_header *)&mh, file); case MH_MAGIC_64: return sliceRequiresGC(mh, file); default: return WithoutGC; } } // Returns 1 if any slice requires GC. // Returns 0 if no slice requires GC. // Returns -1 on any I/O or file format error. int objc_appRequiresGC(int fd) { struct stat st; if (fstat(fd, &st) < 0) return Error; FileSlice file(fd, 0, st.st_size); // Read fat header, if any. struct fat_header fh; if (! file.pread(&fh, sizeof(fh))) return Error; int result; if (OSSwapBigToHostInt32(fh.magic) == FAT_MAGIC) { // Fat header. size_t nfat_arch = OSSwapBigToHostInt32(fh.nfat_arch); // Disallow abusively-large files that could hang this checker. if (nfat_arch > SANE_HEADER_SIZE/sizeof(struct fat_arch)) return Error; size_t fat_size; if (umul_overflow(nfat_arch, sizeof(struct fat_arch), &fat_size)) { return Error; } FileSlice archlist; if (!file.slice(sizeof(fh), fat_size, archlist)) return Error; result = WithoutGC; for (size_t i = 0; i < nfat_arch; i++) { struct fat_arch fa; if (!archlist.pread(&fa, sizeof(fa))) return Error; if (!archlist.advance(sizeof(fa))) return Error; FileSlice thin; if (!file.slice(OSSwapBigToHostInt32(fa.offset), OSSwapBigToHostInt32(fa.size), thin)) { return Error; } switch (sliceRequiresGC(thin)) { case WithoutGC: break; // no change case WithGC: if (result != Error) result = WithGC; break; case Error: result = Error; break; } } } else { // Thin header or not a header. result = sliceRequiresGC(file); } return result; } // SUPPORT_GC_COMPAT #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-sel-old.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Utilities for registering and looking up selectors. The sole * purpose of the selector tables is a registry whereby there is * exactly one address (selector) associated with a given string * (method name). */ #if !__OBJC2__ #include "objc-private.h" #include "objc-sel-set.h" #if SUPPORT_PREOPT #include static const objc_selopt_t *builtins = NULL; #endif __BEGIN_DECLS static size_t SelrefCount = 0; static const char *_objc_empty_selector = ""; static struct __objc_sel_set *_objc_selectors = NULL; static SEL _objc_search_builtins(const char *key) { #if defined(DUMP_SELECTORS) if (NULL != key) printf("\t\"%s\",\n", key); #endif if (!key) return (SEL)0; if ('\0' == *key) return (SEL)_objc_empty_selector; #if SUPPORT_PREOPT if (builtins) return (SEL)builtins->get(key); #endif return (SEL)0; } const char *sel_getName(SEL sel) { return sel ? (const char *)sel : ""; } BOOL sel_isMapped(SEL name) { SEL sel; if (!name) return NO; sel = _objc_search_builtins((const char *)name); if (sel) return YES; mutex_locker_t lock(selLock); if (_objc_selectors) { sel = __objc_sel_set_get(_objc_selectors, name); } return bool(sel); } static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) { SEL result = 0; if (shouldLock) selLock.assertUnlocked(); else selLock.assertLocked(); if (!name) return (SEL)0; result = _objc_search_builtins(name); if (result) return result; conditional_mutex_locker_t lock(selLock, shouldLock); if (_objc_selectors) { result = __objc_sel_set_get(_objc_selectors, (SEL)name); } if (result) return result; // No match. Insert. if (!_objc_selectors) { _objc_selectors = __objc_sel_set_create(SelrefCount); } if (!result) { result = (SEL)(copy ? strdup(name) : name); __objc_sel_set_add(_objc_selectors, result); #if defined(DUMP_UNKNOWN_SELECTORS) printf("\t\"%s\",\n", name); #endif } return result; } SEL sel_registerName(const char *name) { return __sel_registerName(name, 1, 1); // YES lock, YES copy } SEL sel_registerNameNoLock(const char *name, bool copy) { return __sel_registerName(name, 0, copy); // NO lock, maybe copy } // 2001/1/24 // the majority of uses of this function (which used to return NULL if not found) // did not check for NULL, so, in fact, never return NULL // SEL sel_getUid(const char *name) { return __sel_registerName(name, 2, 1); // YES lock, YES copy } BOOL sel_isEqual(SEL lhs, SEL rhs) { return bool(lhs == rhs); } /*********************************************************************** * sel_init * Initialize selector tables and register selectors used internally. **********************************************************************/ void sel_init(size_t selrefCount) { // save this value for later SelrefCount = selrefCount; #if SUPPORT_PREOPT builtins = preoptimizedSelectors(); #endif // Register selectors used by libobjc #define s(x) SEL_##x = sel_registerNameNoLock(#x, NO) #define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO) mutex_locker_t lock(selLock); s(load); s(initialize); t(resolveInstanceMethod:, resolveInstanceMethod); t(resolveClassMethod:, resolveClassMethod); t(.cxx_construct, cxx_construct); t(.cxx_destruct, cxx_destruct); s(retain); s(release); s(autorelease); s(retainCount); s(alloc); t(allocWithZone:, allocWithZone); s(dealloc); s(copy); s(new); t(forwardInvocation:, forwardInvocation); t(_tryRetain, tryRetain); t(_isDeallocating, isDeallocating); s(retainWeakReference); s(allowsWeakReference); extern SEL FwdSel; FwdSel = sel_registerNameNoLock("forward::", NO); #undef s #undef t } __END_DECLS #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-sel-set.h ================================================ /* * Copyright (c) 2004 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc-sel-set.h * A set of SELs used for SEL uniquing. */ #ifndef _OBJC_SEL_SET_H_ #define _OBJC_SEL_SET_H_ #if !__OBJC2__ #include #include "objc-os.h" __BEGIN_DECLS struct __objc_sel_set; extern struct __objc_sel_set *__objc_sel_set_create(size_t selrefCount); extern SEL __objc_sel_set_get(struct __objc_sel_set *sset, SEL candidate); extern void __objc_sel_set_add(struct __objc_sel_set *sset, SEL value); __END_DECLS #endif #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-sel-set.mm ================================================ /* * Copyright (c) 1999-2004,2008 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc-sel-set.h * A cut-down copy of CFSet used for SEL uniquing. */ // NOTE: even on a 64-bit system, the implementation is still limited // to 32-bit integers (like, the count), but SEL can be any size. #include #include "objc-private.h" #include "objc-sel-set.h" #if !__OBJC2__ #if !SUPPORT_MOD // mod-free power of 2 version #define CONSTRAIN(val, range) ((val) & ((range)-1)) #define SIZE 27 static const uint32_t __objc_sel_set_capacities[SIZE+1] = { 3, 6, 12, 24, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576, 49152, 98304, 196608, 393216, 786432, 1572864, 3145728, 6291456, 12582912, 25165824, 50331648, 100663296, 201326592, UINT32_MAX }; static const uint32_t __objc_sel_set_buckets[SIZE] = { // powers of 2 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456 }; #else // prime version #define CONSTRAIN(val, range) ((val) % (range)) #define SIZE 42 static const uint32_t __objc_sel_set_capacities[SIZE+1] = { 4, 8, 17, 29, 47, 76, 123, 199, 322, 521, 843, 1364, 2207, 3571, 5778, 9349, 15127, 24476, 39603, 64079, 103682, 167761, 271443, 439204, 710647, 1149851, 1860498, 3010349, 4870847, 7881196, 12752043, 20633239, 33385282, 54018521, 87403803, 141422324, 228826127, 370248451, 599074578, 969323029, 1568397607, 2537720636U, UINT32_MAX }; static const uint32_t __objc_sel_set_buckets[SIZE] = { // primes 5, 11, 23, 41, 67, 113, 199, 317, 521, 839, 1361, 2207, 3571, 5779, 9349, 15121, 24473, 39607, 64081, 103681, 167759, 271429, 439199, 710641, 1149857, 1860503, 3010349, 4870843, 7881193, 12752029, 20633237, 33385273, 54018521, 87403763, 141422317, 228826121, 370248451, 599074561, 969323023, 1568397599, 2537720629U, 4106118251U }; #endif struct __objc_sel_set { uint32_t _count; /* number of slots used */ uint32_t _capacity; /* maximum number of used slots */ uint32_t _bucketsNum; /* number of slots */ SEL *_buckets; /* can be NULL if not allocated yet */ }; struct __objc_sel_set_finds { SEL match; uint32_t nomatch; }; // candidate may not be 0; match is 0 if not present static struct __objc_sel_set_finds __objc_sel_set_findBuckets(struct __objc_sel_set *sset, SEL candidate) { struct __objc_sel_set_finds ret = {0, 0xffffffff}; uint32_t probe = CONSTRAIN((uint32_t)_objc_strhash((const char *)candidate), sset->_bucketsNum); for (;;) { SEL currentSel = sset->_buckets[probe]; if (!currentSel) { ret.nomatch = probe; return ret; } else if (!ret.match && 0 == strcmp((const char *)currentSel, (const char *)candidate)) { ret.match = currentSel; } probe++; if (sset->_bucketsNum <= probe) { probe -= sset->_bucketsNum; } } } // create a set with given starting capacity, will resize as needed struct __objc_sel_set *__objc_sel_set_create(size_t selrefs) { uint32_t idx; struct __objc_sel_set *sset = (struct __objc_sel_set *) malloc(sizeof(struct __objc_sel_set)); if (!sset) _objc_fatal("objc_sel_set failure"); sset->_count = 0; // heuristic to convert executable's selrefs count to table size #if TARGET_OS_IPHONE && !TARGET_OS_IOSMAC for (idx = 0; __objc_sel_set_capacities[idx] < selrefs; idx++); if (idx > 0 && selrefs < 1536) idx--; #else if (selrefs < 1024) selrefs = 1024; for (idx = 0; __objc_sel_set_capacities[idx] < selrefs; idx++); idx++; #endif if (SIZE <= idx) _objc_fatal("objc_sel_set failure"); sset->_capacity = __objc_sel_set_capacities[idx]; sset->_bucketsNum = __objc_sel_set_buckets[idx]; sset->_buckets = (SEL *)calloc(sset->_bucketsNum, sizeof(SEL)); if (!sset->_buckets) _objc_fatal("objc_sel_set failure"); return sset; } // returns 0 on failure; candidate may not be 0 SEL __objc_sel_set_get(struct __objc_sel_set *sset, SEL candidate) { return __objc_sel_set_findBuckets(sset, candidate).match; } // value may not be 0; should not be called unless it is known the value is not in the set void __objc_sel_set_add(struct __objc_sel_set *sset, SEL value) { if (sset->_count == sset->_capacity) { SEL *oldbuckets = sset->_buckets; uint32_t oldnbuckets = sset->_bucketsNum; uint32_t idx, capacity = sset->_count + 1; for (idx = 0; __objc_sel_set_capacities[idx] < capacity; idx++); if (SIZE <= idx) _objc_fatal("objc_sel_set failure"); sset->_capacity = __objc_sel_set_capacities[idx]; sset->_bucketsNum = __objc_sel_set_buckets[idx]; sset->_buckets = (SEL *) calloc(sset->_bucketsNum, sizeof(SEL)); if (!sset->_buckets) _objc_fatal("objc_sel_set failure"); for (idx = 0; idx < oldnbuckets; idx++) { SEL currentSel = oldbuckets[idx]; if (currentSel) { uint32_t nomatch = __objc_sel_set_findBuckets(sset, currentSel).nomatch; sset->_buckets[nomatch] = currentSel; } } free(oldbuckets); } { uint32_t nomatch = __objc_sel_set_findBuckets(sset, value).nomatch; sset->_buckets[nomatch] = value; sset->_count++; } } // !__OBJC2__ #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-sel-table.s ================================================ #include #include #if __LP64__ # define PTR(x) .quad x #else # define PTR(x) .long x #endif .section __TEXT,__objc_opt_ro .align 3 .private_extern __objc_opt_data __objc_opt_data: .long 15 /* table.version */ .long 0 /* table.flags */ .long 0 /* table.selopt_offset */ .long 0 /* table.headeropt_ro_offset */ .long 0 /* table.clsopt_offset */ .long 0 /* table.protocolopt_offset */ .long 0 /* table.headeropt_rw_offset */ .space PAGE_MAX_SIZE-28 /* space for selopt, smax/capacity=1048576, blen/mask=524287+1 */ .space 4*(8+256) /* header and scramble */ .space 524288 /* mask tab */ .space 1048576 /* checkbytes */ .space 1048576*4 /* offsets */ /* space for clsopt, smax/capacity=131072, blen/mask=32767+1 */ .space 4*(8+256) /* header and scramble */ .space 32768 /* mask tab */ .space 131072 /* checkbytes */ .space 131072*12 /* offsets to name and class and header_info */ .space 512*8 /* some duplicate classes */ /* space for some demangled protocol names */ .space 1024 /* space for protocolopt, smax/capacity=16384, blen/mask=8191+1 */ .space 4*(8+256) /* header and scramble */ .space 8192 /* mask tab */ .space 16384 /* checkbytes */ .space 16384*8 /* offsets */ /* space for 2048 header_info (RO) structures */ .space 8 + (2048*16) .section __DATA,__objc_opt_rw .align 3 .private_extern __objc_opt_rw_data __objc_opt_rw_data: /* space for 2048 header_info (RW) structures */ .space 8 + (2048*8) /* space for 16384 protocols */ #if __LP64__ .space 16384 * 12 * 8 #else .space 16384 * 12 * 4 #endif /* section of pointers that the shared cache optimizer wants to know about */ .section __DATA,__objc_opt_ptrs .align 3 #if TARGET_OS_OSX && __i386__ // old ABI .globl .objc_class_name_Protocol PTR(.objc_class_name_Protocol) #else // new ABI .globl _OBJC_CLASS_$_Protocol PTR(_OBJC_CLASS_$_Protocol) #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-sel.mm ================================================ /* * Copyright (c) 2012 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #if __OBJC2__ #include "objc-private.h" #include "objc-cache.h" #if SUPPORT_PREOPT static const objc_selopt_t *builtins = NULL; #endif static size_t SelrefCount = 0; static NXMapTable *namedSelectors; static SEL search_builtins(const char *key); /*********************************************************************** * sel_init * Initialize selector tables and register selectors used internally. **********************************************************************/ void sel_init(size_t selrefCount) { // save this value for later SelrefCount = selrefCount; #if SUPPORT_PREOPT builtins = preoptimizedSelectors(); if (PrintPreopt && builtins) { uint32_t occupied = builtins->occupied; uint32_t capacity = builtins->capacity; _objc_inform("PREOPTIMIZATION: using selopt at %p", builtins); _objc_inform("PREOPTIMIZATION: %u selectors", occupied); _objc_inform("PREOPTIMIZATION: %u/%u (%u%%) hash table occupancy", occupied, capacity, (unsigned)(occupied/(double)capacity*100)); } #endif // Register selectors used by libobjc #define s(x) SEL_##x = sel_registerNameNoLock(#x, NO) #define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO) mutex_locker_t lock(selLock); s(load); s(initialize); t(resolveInstanceMethod:, resolveInstanceMethod); t(resolveClassMethod:, resolveClassMethod); t(.cxx_construct, cxx_construct); t(.cxx_destruct, cxx_destruct); s(retain); s(release); s(autorelease); s(retainCount); s(alloc); t(allocWithZone:, allocWithZone); s(dealloc); s(copy); s(new); t(forwardInvocation:, forwardInvocation); t(_tryRetain, tryRetain); t(_isDeallocating, isDeallocating); s(retainWeakReference); s(allowsWeakReference); #undef s #undef t } static SEL sel_alloc(const char *name, bool copy) { selLock.assertLocked(); return (SEL)(copy ? strdupIfMutable(name) : name); } const char *sel_getName(SEL sel) { if (!sel) return ""; return (const char *)(const void*)sel; } BOOL sel_isMapped(SEL sel) { if (!sel) return NO; const char *name = (const char *)(void *)sel; if (sel == search_builtins(name)) return YES; mutex_locker_t lock(selLock); if (namedSelectors) { return (sel == (SEL)NXMapGet(namedSelectors, name)); } return false; } static SEL search_builtins(const char *name) { #if SUPPORT_PREOPT if (builtins) return (SEL)builtins->get(name); #endif return nil; } static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) { SEL result = 0; if (shouldLock) selLock.assertUnlocked(); else selLock.assertLocked(); if (!name) return (SEL)0; result = search_builtins(name); if (result) return result; conditional_mutex_locker_t lock(selLock, shouldLock); if (namedSelectors) { result = (SEL)NXMapGet(namedSelectors, name); } if (result) return result; // No match. Insert. if (!namedSelectors) { namedSelectors = NXCreateMapTable(NXStrValueMapPrototype, (unsigned)SelrefCount); } if (!result) { result = sel_alloc(name, copy); // fixme choose a better container (hash not map for starters) NXMapInsert(namedSelectors, sel_getName(result), result); } return result; } SEL sel_registerName(const char *name) { return __sel_registerName(name, 1, 1); // YES lock, YES copy } SEL sel_registerNameNoLock(const char *name, bool copy) { return __sel_registerName(name, 0, copy); // NO lock, maybe copy } // 2001/1/24 // the majority of uses of this function (which used to return NULL if not found) // did not check for NULL, so, in fact, never return NULL // SEL sel_getUid(const char *name) { return __sel_registerName(name, 2, 1); // YES lock, YES copy } BOOL sel_isEqual(SEL lhs, SEL rhs) { return bool(lhs == rhs); } #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-sync.h ================================================ /* * Copyright (c) 2002, 2006 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef __OBJC_SNYC_H_ #define __OBJC_SNYC_H_ #include /** * Begin synchronizing on 'obj'. * Allocates recursive pthread_mutex associated with 'obj' if needed. * * @param obj The object to begin synchronizing on. * * @return OBJC_SYNC_SUCCESS once lock is acquired. */ OBJC_EXPORT int objc_sync_enter(id _Nonnull obj) OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0); /** * End synchronizing on 'obj'. * * @param obj The object to end synchronizing on. * * @return OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR */ OBJC_EXPORT int objc_sync_exit(id _Nonnull obj) OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0); enum { OBJC_SYNC_SUCCESS = 0, OBJC_SYNC_NOT_OWNING_THREAD_ERROR = -1 }; #endif // __OBJC_SYNC_H_ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-sync.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "objc-private.h" #include "objc-sync.h" // // Allocate a lock only when needed. Since few locks are needed at any point // in time, keep them on a single list. // typedef struct alignas(CacheLineSize) SyncData { struct SyncData* nextData; DisguisedPtr object; int32_t threadCount; // number of THREADS using this block recursive_mutex_t mutex; } SyncData; typedef struct { SyncData *data; unsigned int lockCount; // number of times THIS THREAD locked this block } SyncCacheItem; typedef struct SyncCache { unsigned int allocated; unsigned int used; SyncCacheItem list[0]; } SyncCache; /* Fast cache: two fixed pthread keys store a single SyncCacheItem. This avoids malloc of the SyncCache for threads that only synchronize a single object at a time. SYNC_DATA_DIRECT_KEY == SyncCacheItem.data SYNC_COUNT_DIRECT_KEY == SyncCacheItem.lockCount */ struct SyncList { SyncData *data; spinlock_t lock; constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { } }; // Use multiple parallel lists to decrease contention among unrelated objects. #define LOCK_FOR_OBJ(obj) sDataLists[obj].lock #define LIST_FOR_OBJ(obj) sDataLists[obj].data static StripedMap sDataLists; enum usage { ACQUIRE, RELEASE, CHECK }; static SyncCache *fetch_cache(bool create) { _objc_pthread_data *data; data = _objc_fetch_pthread_data(create); if (!data) return NULL; if (!data->syncCache) { if (!create) { return NULL; } else { int count = 4; data->syncCache = (SyncCache *) calloc(1, sizeof(SyncCache) + count*sizeof(SyncCacheItem)); data->syncCache->allocated = count; } } // Make sure there's at least one open slot in the list. if (data->syncCache->allocated == data->syncCache->used) { data->syncCache->allocated *= 2; data->syncCache = (SyncCache *) realloc(data->syncCache, sizeof(SyncCache) + data->syncCache->allocated * sizeof(SyncCacheItem)); } return data->syncCache; } void _destroySyncCache(struct SyncCache *cache) { if (cache) free(cache); } static SyncData* id2data(id object, enum usage why) { spinlock_t *lockp = &LOCK_FOR_OBJ(object); SyncData **listp = &LIST_FOR_OBJ(object); SyncData* result = NULL; #if SUPPORT_DIRECT_THREAD_KEYS // Check per-thread single-entry fast cache for matching object bool fastCacheOccupied = NO; SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY); if (data) { fastCacheOccupied = YES; if (data->object == object) { // Found a match in fast cache. uintptr_t lockCount; result = data; lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY); if (result->threadCount <= 0 || lockCount <= 0) { _objc_fatal("id2data fastcache is buggy"); } switch(why) { case ACQUIRE: { lockCount++; tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount); break; } case RELEASE: lockCount--; tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount); if (lockCount == 0) { // remove from fast cache tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL); // atomic because may collide with concurrent ACQUIRE OSAtomicDecrement32Barrier(&result->threadCount); } break; case CHECK: // do nothing break; } return result; } } #endif // Check per-thread cache of already-owned locks for matching object SyncCache *cache = fetch_cache(NO); if (cache) { unsigned int i; for (i = 0; i < cache->used; i++) { SyncCacheItem *item = &cache->list[i]; if (item->data->object != object) continue; // Found a match. result = item->data; if (result->threadCount <= 0 || item->lockCount <= 0) { _objc_fatal("id2data cache is buggy"); } switch(why) { case ACQUIRE: item->lockCount++; break; case RELEASE: item->lockCount--; if (item->lockCount == 0) { // remove from per-thread cache cache->list[i] = cache->list[--cache->used]; // atomic because may collide with concurrent ACQUIRE OSAtomicDecrement32Barrier(&result->threadCount); } break; case CHECK: // do nothing break; } return result; } } // Thread cache didn't find anything. // Walk in-use list looking for matching object // Spinlock prevents multiple threads from creating multiple // locks for the same new object. // We could keep the nodes in some hash table if we find that there are // more than 20 or so distinct locks active, but we don't do that now. lockp->lock(); { SyncData* p; SyncData* firstUnused = NULL; for (p = *listp; p != NULL; p = p->nextData) { if ( p->object == object ) { result = p; // atomic because may collide with concurrent RELEASE OSAtomicIncrement32Barrier(&result->threadCount); goto done; } if ( (firstUnused == NULL) && (p->threadCount == 0) ) firstUnused = p; } // no SyncData currently associated with object if ( (why == RELEASE) || (why == CHECK) ) goto done; // an unused one was found, use it if ( firstUnused != NULL ) { result = firstUnused; result->object = (objc_object *)object; result->threadCount = 1; goto done; } } // Allocate a new SyncData and add to list. // XXX allocating memory with a global lock held is bad practice, // might be worth releasing the lock, allocating, and searching again. // But since we never free these guys we won't be stuck in allocation very often. posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData)); result->object = (objc_object *)object; result->threadCount = 1; new (&result->mutex) recursive_mutex_t(fork_unsafe_lock); result->nextData = *listp; *listp = result; done: lockp->unlock(); if (result) { // Only new ACQUIRE should get here. // All RELEASE and CHECK and recursive ACQUIRE are // handled by the per-thread caches above. if (why == RELEASE) { // Probably some thread is incorrectly exiting // while the object is held by another thread. return nil; } if (why != ACQUIRE) _objc_fatal("id2data is buggy"); if (result->object != object) _objc_fatal("id2data is buggy"); #if SUPPORT_DIRECT_THREAD_KEYS if (!fastCacheOccupied) { // Save in fast thread cache tls_set_direct(SYNC_DATA_DIRECT_KEY, result); tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1); } else #endif { // Save in thread cache if (!cache) cache = fetch_cache(YES); cache->list[cache->used].data = result; cache->list[cache->used].lockCount = 1; cache->used++; } } return result; } BREAKPOINT_FUNCTION( void objc_sync_nil(void) ); // Begin synchronizing on 'obj'. // Allocates recursive mutex associated with 'obj' if needed. // Returns OBJC_SYNC_SUCCESS once lock is acquired. int objc_sync_enter(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, ACQUIRE); assert(data); data->mutex.lock(); } else { // @synchronized(nil) does nothing if (DebugNilSync) { _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); } objc_sync_nil(); } return result; } // End synchronizing on 'obj'. // Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR int objc_sync_exit(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, RELEASE); if (!data) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; } else { bool okay = data->mutex.tryUnlock(); if (!okay) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; } } } else { // @synchronized(nil) does nothing } return result; } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-typeencoding.mm ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*********************************************************************** * objc-typeencoding.m * Parsing of old-style type strings. **********************************************************************/ #include "objc-private.h" /*********************************************************************** * SubtypeUntil. * * Delegation. **********************************************************************/ static int SubtypeUntil (const char * type, char end) { int level = 0; const char * head = type; // while (*type) { if (!*type || (!level && (*type == end))) return (int)(type - head); switch (*type) { case ']': case '}': case ')': level--; break; case '[': case '{': case '(': level += 1; break; } type += 1; } _objc_fatal ("Object: SubtypeUntil: end of type encountered prematurely\n"); return 0; } /*********************************************************************** * SkipFirstType. **********************************************************************/ static const char * SkipFirstType (const char * type) { while (1) { switch (*type++) { case 'O': /* bycopy */ case 'n': /* in */ case 'o': /* out */ case 'N': /* inout */ case 'r': /* const */ case 'V': /* oneway */ case '^': /* pointers */ break; case '@': /* objects */ if (type[0] == '?') type++; /* Blocks */ return type; /* arrays */ case '[': while ((*type >= '0') && (*type <= '9')) type += 1; return type + SubtypeUntil (type, ']') + 1; /* structures */ case '{': return type + SubtypeUntil (type, '}') + 1; /* unions */ case '(': return type + SubtypeUntil (type, ')') + 1; /* basic types */ default: return type; } } } /*********************************************************************** * encoding_getNumberOfArguments. **********************************************************************/ unsigned int encoding_getNumberOfArguments(const char *typedesc) { unsigned nargs; // First, skip the return type typedesc = SkipFirstType (typedesc); // Next, skip stack size while ((*typedesc >= '0') && (*typedesc <= '9')) typedesc += 1; // Now, we have the arguments - count how many nargs = 0; while (*typedesc) { // Traverse argument type typedesc = SkipFirstType (typedesc); // Skip GNU runtime's register parameter hint if (*typedesc == '+') typedesc++; // Traverse (possibly negative) argument offset if (*typedesc == '-') typedesc += 1; while ((*typedesc >= '0') && (*typedesc <= '9')) typedesc += 1; // Made it past an argument nargs += 1; } return nargs; } /*********************************************************************** * encoding_getSizeOfArguments. **********************************************************************/ unsigned encoding_getSizeOfArguments(const char *typedesc) { unsigned stack_size; // Get our starting points stack_size = 0; // Skip the return type typedesc = SkipFirstType (typedesc); // Convert ASCII number string to integer while ((*typedesc >= '0') && (*typedesc <= '9')) stack_size = (stack_size * 10) + (*typedesc++ - '0'); return stack_size; } /*********************************************************************** * encoding_getArgumentInfo. **********************************************************************/ unsigned int encoding_getArgumentInfo(const char *typedesc, unsigned int arg, const char **type, int *offset) { unsigned nargs = 0; int self_offset = 0; bool offset_is_negative = NO; // First, skip the return type typedesc = SkipFirstType (typedesc); // Next, skip stack size while ((*typedesc >= '0') && (*typedesc <= '9')) typedesc += 1; // Now, we have the arguments - position typedesc to the appropriate argument while (*typedesc && nargs != arg) { // Skip argument type typedesc = SkipFirstType (typedesc); if (nargs == 0) { // Skip GNU runtime's register parameter hint if (*typedesc == '+') typedesc++; // Skip negative sign in offset if (*typedesc == '-') { offset_is_negative = YES; typedesc += 1; } else offset_is_negative = NO; while ((*typedesc >= '0') && (*typedesc <= '9')) self_offset = self_offset * 10 + (*typedesc++ - '0'); if (offset_is_negative) self_offset = -(self_offset); } else { // Skip GNU runtime's register parameter hint if (*typedesc == '+') typedesc++; // Skip (possibly negative) argument offset if (*typedesc == '-') typedesc += 1; while ((*typedesc >= '0') && (*typedesc <= '9')) typedesc += 1; } nargs += 1; } if (*typedesc) { int arg_offset = 0; *type = typedesc; typedesc = SkipFirstType (typedesc); if (arg == 0) { *offset = 0; } else { // Skip GNU register parameter hint if (*typedesc == '+') typedesc++; // Pick up (possibly negative) argument offset if (*typedesc == '-') { offset_is_negative = YES; typedesc += 1; } else offset_is_negative = NO; while ((*typedesc >= '0') && (*typedesc <= '9')) arg_offset = arg_offset * 10 + (*typedesc++ - '0'); if (offset_is_negative) arg_offset = - arg_offset; *offset = arg_offset - self_offset; } } else { *type = 0; *offset = 0; } return nargs; } void encoding_getReturnType(const char *t, char *dst, size_t dst_len) { size_t len; const char *end; if (!dst) return; if (!t) { strncpy(dst, "", dst_len); return; } end = SkipFirstType(t); len = end - t; strncpy(dst, t, MIN(len, dst_len)); if (len < dst_len) memset(dst+len, 0, dst_len - len); } /*********************************************************************** * encoding_copyReturnType. Returns the method's return type string * on the heap. **********************************************************************/ char * encoding_copyReturnType(const char *t) { size_t len; const char *end; char *result; if (!t) return NULL; end = SkipFirstType(t); len = end - t; result = (char *)malloc(len + 1); strncpy(result, t, len); result[len] = '\0'; return result; } void encoding_getArgumentType(const char *t, unsigned int index, char *dst, size_t dst_len) { size_t len; const char *end; int offset; if (!dst) return; if (!t) { strncpy(dst, "", dst_len); return; } encoding_getArgumentInfo(t, index, &t, &offset); if (!t) { strncpy(dst, "", dst_len); return; } end = SkipFirstType(t); len = end - t; strncpy(dst, t, MIN(len, dst_len)); if (len < dst_len) memset(dst+len, 0, dst_len - len); } /*********************************************************************** * encoding_copyArgumentType. Returns a single argument's type string * on the heap. Argument 0 is `self`; argument 1 is `_cmd`. **********************************************************************/ char * encoding_copyArgumentType(const char *t, unsigned int index) { size_t len; const char *end; char *result; int offset; if (!t) return NULL; encoding_getArgumentInfo(t, index, &t, &offset); if (!t) return NULL; end = SkipFirstType(t); len = end - t; result = (char *)malloc(len + 1); strncpy(result, t, len); result[len] = '\0'; return result; } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-weak.h ================================================ /* * Copyright (c) 2010-2011 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_WEAK_H_ #define _OBJC_WEAK_H_ #include #include "objc-config.h" __BEGIN_DECLS /* The weak table is a hash table governed by a single spin lock. An allocated blob of memory, most often an object, but under GC any such allocation, may have its address stored in a __weak marked storage location through use of compiler generated write-barriers or hand coded uses of the register weak primitive. Associated with the registration can be a callback block for the case when one of the allocated chunks of memory is reclaimed. The table is hashed on the address of the allocated memory. When __weak marked memory changes its reference, we count on the fact that we can still see its previous reference. So, in the hash table, indexed by the weakly referenced item, is a list of all locations where this address is currently being stored. For ARC, we also keep track of whether an arbitrary object is being deallocated by briefly placing it in the table just prior to invoking dealloc, and removing it via objc_clear_deallocating just prior to memory reclamation. */ // The address of a __weak variable. // These pointers are stored disguised so memory analysis tools // don't see lots of interior pointers from the weak table into objects. typedef DisguisedPtr weak_referrer_t; #if __LP64__ #define PTR_MINUS_2 62 #else #define PTR_MINUS_2 30 #endif /** * The internal structure stored in the weak references table. * It maintains and stores * a hash set of weak references pointing to an object. * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set * is instead a small inline array. */ #define WEAK_INLINE_COUNT 4 // out_of_line_ness field overlaps with the low two bits of inline_referrers[1]. // inline_referrers[1] is a DisguisedPtr of a pointer-aligned address. // The low two bits of a pointer-aligned DisguisedPtr will always be 0b00 // (disguised nil or 0x80..00) or 0b11 (any other address). // Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state. #define REFERRERS_OUT_OF_LINE 2 struct weak_entry_t { DisguisedPtr referent; union { struct { weak_referrer_t *referrers; uintptr_t out_of_line_ness : 2; uintptr_t num_refs : PTR_MINUS_2; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; bool out_of_line() { return (out_of_line_ness == REFERRERS_OUT_OF_LINE); } weak_entry_t& operator=(const weak_entry_t& other) { memcpy(this, &other, sizeof(other)); return *this; } weak_entry_t(objc_object *newReferent, objc_object **newReferrer) : referent(newReferent) { inline_referrers[0] = newReferrer; for (int i = 1; i < WEAK_INLINE_COUNT; i++) { inline_referrers[i] = nil; } } }; /** * The global weak references table. Stores object ids as keys, * and weak_entry_t structs as their values. */ struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; }; /// Adds an (object, weak pointer) pair to the weak table. id weak_register_no_lock(weak_table_t *weak_table, id referent, id *referrer, bool crashIfDeallocating); /// Removes an (object, weak pointer) pair from the weak table. void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer); #if DEBUG /// Returns true if an object is weakly referenced somewhere. bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent); #endif /// Called on object destruction. Sets all remaining weak pointers to nil. void weak_clear_no_lock(weak_table_t *weak_table, id referent); __END_DECLS #endif /* _OBJC_WEAK_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc-weak.mm ================================================ /* * Copyright (c) 2010-2011 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "objc-private.h" #include "objc-weak.h" #include #include #include #include #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0) static void append_referrer(weak_entry_t *entry, objc_object **new_referrer); BREAKPOINT_FUNCTION( void objc_weak_error(void) ); static void bad_weak_table(weak_entry_t *entries) { _objc_fatal("bad weak table at %p. This may be a runtime bug or a " "memory error somewhere else.", entries); } /** * Unique hash function for object pointers only. * * @param key The object pointer * * @return Size unrestricted hash of pointer. */ static inline uintptr_t hash_pointer(objc_object *key) { return ptr_hash((uintptr_t)key); } /** * Unique hash function for weak object pointers only. * * @param key The weak object pointer. * * @return Size unrestricted hash of pointer. */ static inline uintptr_t w_hash_pointer(objc_object **key) { return ptr_hash((uintptr_t)key); } /** * Grow the entry's hash table of referrers. Rehashes each * of the referrers. * * @param entry Weak pointer hash set for a particular object. */ __attribute__((noinline, used)) static void grow_refs_and_insert(weak_entry_t *entry, objc_object **new_referrer) { assert(entry->out_of_line()); size_t old_size = TABLE_SIZE(entry); size_t new_size = old_size ? old_size * 2 : 8; size_t num_refs = entry->num_refs; weak_referrer_t *old_refs = entry->referrers; entry->mask = new_size - 1; entry->referrers = (weak_referrer_t *) calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t)); entry->num_refs = 0; entry->max_hash_displacement = 0; for (size_t i = 0; i < old_size && num_refs > 0; i++) { if (old_refs[i] != nil) { append_referrer(entry, old_refs[i]); num_refs--; } } // Insert append_referrer(entry, new_referrer); if (old_refs) free(old_refs); } /** * Add the given referrer to set of weak pointers in this entry. * Does not perform duplicate checking (b/c weak pointers are never * added to a set twice). * * @param entry The entry holding the set of weak pointers. * @param new_referrer The new weak pointer to be added. */ static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) { if (! entry->out_of_line()) { // Try to insert inline. for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil) { entry->inline_referrers[i] = new_referrer; return; } } // Couldn't insert inline. Allocate out of line. weak_referrer_t *new_referrers = (weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); // This constructed table is invalid, but grow_refs_and_insert // will fix it and rehash it. for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { new_referrers[i] = entry->inline_referrers[i]; } entry->referrers = new_referrers; entry->num_refs = WEAK_INLINE_COUNT; entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; entry->mask = WEAK_INLINE_COUNT-1; entry->max_hash_displacement = 0; } assert(entry->out_of_line()); if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { return grow_refs_and_insert(entry, new_referrer); } size_t begin = w_hash_pointer(new_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0; while (entry->referrers[index] != nil) { hash_displacement++; index = (index+1) & entry->mask; if (index == begin) bad_weak_table(entry); } if (hash_displacement > entry->max_hash_displacement) { entry->max_hash_displacement = hash_displacement; } weak_referrer_t &ref = entry->referrers[index]; ref = new_referrer; entry->num_refs++; } /** * Remove old_referrer from set of referrers, if it's present. * Does not remove duplicates, because duplicates should not exist. * * @todo this is slow if old_referrer is not present. Is this ever the case? * * @param entry The entry holding the referrers. * @param old_referrer The referrer to remove. */ static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) { if (! entry->out_of_line()) { for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == old_referrer) { entry->inline_referrers[i] = nil; return; } } _objc_inform("Attempted to unregister unknown __weak variable " "at %p. This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", old_referrer); objc_weak_error(); return; } size_t begin = w_hash_pointer(old_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0; while (entry->referrers[index] != old_referrer) { index = (index+1) & entry->mask; if (index == begin) bad_weak_table(entry); hash_displacement++; if (hash_displacement > entry->max_hash_displacement) { _objc_inform("Attempted to unregister unknown __weak variable " "at %p. This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", old_referrer); objc_weak_error(); return; } } entry->referrers[index] = nil; entry->num_refs--; } /** * Add new_entry to the object's table of weak references. * Does not check whether the referent is already in the table. */ static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) { weak_entry_t *weak_entries = weak_table->weak_entries; assert(weak_entries != nil); size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); size_t index = begin; size_t hash_displacement = 0; while (weak_entries[index].referent != nil) { index = (index+1) & weak_table->mask; if (index == begin) bad_weak_table(weak_entries); hash_displacement++; } weak_entries[index] = *new_entry; weak_table->num_entries++; if (hash_displacement > weak_table->max_hash_displacement) { weak_table->max_hash_displacement = hash_displacement; } } static void weak_resize(weak_table_t *weak_table, size_t new_size) { size_t old_size = TABLE_SIZE(weak_table); weak_entry_t *old_entries = weak_table->weak_entries; weak_entry_t *new_entries = (weak_entry_t *) calloc(new_size, sizeof(weak_entry_t)); weak_table->mask = new_size - 1; weak_table->weak_entries = new_entries; weak_table->max_hash_displacement = 0; weak_table->num_entries = 0; // restored by weak_entry_insert below if (old_entries) { weak_entry_t *entry; weak_entry_t *end = old_entries + old_size; for (entry = old_entries; entry < end; entry++) { if (entry->referent) { weak_entry_insert(weak_table, entry); } } free(old_entries); } } // Grow the given zone's table of weak references if it is full. static void weak_grow_maybe(weak_table_t *weak_table) { size_t old_size = TABLE_SIZE(weak_table); // Grow if at least 3/4 full. if (weak_table->num_entries >= old_size * 3 / 4) { weak_resize(weak_table, old_size ? old_size*2 : 64); } } // Shrink the table if it is mostly empty. static void weak_compact_maybe(weak_table_t *weak_table) { size_t old_size = TABLE_SIZE(weak_table); // Shrink if larger than 1024 buckets and at most 1/16 full. if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) { weak_resize(weak_table, old_size / 8); // leaves new table no more than 1/2 full } } /** * Remove entry from the zone's table of weak references. */ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) { // remove entry if (entry->out_of_line()) free(entry->referrers); bzero(entry, sizeof(*entry)); weak_table->num_entries--; weak_compact_maybe(weak_table); } /** * Return the weak reference table entry for the given referent. * If there is no entry for referent, return NULL. * Performs a lookup. * * @param weak_table * @param referent The object. Must not be nil. * * @return The table of weak referrers to this object. */ static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) { assert(referent); weak_entry_t *weak_entries = weak_table->weak_entries; if (!weak_entries) return nil; size_t begin = hash_pointer(referent) & weak_table->mask; size_t index = begin; size_t hash_displacement = 0; while (weak_table->weak_entries[index].referent != referent) { index = (index+1) & weak_table->mask; if (index == begin) bad_weak_table(weak_table->weak_entries); hash_displacement++; if (hash_displacement > weak_table->max_hash_displacement) { return nil; } } return &weak_table->weak_entries[index]; } /** * Unregister an already-registered weak reference. * This is used when referrer's storage is about to go away, but referent * isn't dead yet. (Otherwise, zeroing referrer later would be a * bad memory access.) * Does nothing if referent/referrer is not a currently active weak reference. * Does not zero referrer. * * FIXME currently requires old referent value to be passed in (lame) * FIXME unregistration should be automatic if referrer is collected * * @param weak_table The global weak table. * @param referent The object. * @param referrer The weak reference. */ void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; weak_entry_t *entry; if (!referent) return; if ((entry = weak_entry_for_referent(weak_table, referent))) { remove_referrer(entry, referrer); bool empty = true; if (entry->out_of_line() && entry->num_refs != 0) { empty = false; } else { for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i]) { empty = false; break; } } } if (empty) { weak_entry_remove(weak_table, entry); } } // Do not set *referrer = nil. objc_storeWeak() requires that the // value not change. } /** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table. * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */ id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; if (!referent || referent->isTaggedPointer()) return referent_id; // ensure that the referenced object is viable bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) object_getMethodImplementation((id)referent, SEL_allowsWeakReference); if ((IMP)allowsWeakReference == _objc_msgForward) { return nil; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); } if (deallocating) { if (crashIfDeallocating) { _objc_fatal("Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.", (void*)referent, object_getClassName((id)referent)); } else { return nil; } } // now remember it and where it is being stored weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, referent))) { append_referrer(entry, referrer); } else { weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); } // Do not set *referrer. objc_storeWeak() requires that the // value not change. return referent_id; } #if DEBUG bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id) { return weak_entry_for_referent(weak_table, (objc_object *)referent_id); } #endif /** * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * @param weak_table * @param referent The object being deallocated. */ void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); if (entry == nil) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); return; } // zero out references weak_referrer_t *referrers; size_t count; if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil; } else if (*referrer) { _objc_inform("__weak variable at %p holds %p instead of %p. " "This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", referrer, (void*)*referrer, (void*)referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry); } ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objc.h ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc.h * Copyright 1988-1996, NeXT Software, Inc. */ #ifndef _OBJC_OBJC_H_ #define _OBJC_OBJC_H_ #include // for __DARWIN_NULL #include #include #include #if !OBJC_TYPES_DEFINED /// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; /// Represents an instance of a class. struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; /// A pointer to an instance of a class. typedef struct objc_object *id; #endif /// An opaque type that represents a method selector. typedef struct objc_selector *SEL; /// A pointer to the function of a method implementation. #if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif /// Type to represent a boolean value. #if defined(__OBJC_BOOL_IS_BOOL) // Honor __OBJC_BOOL_IS_BOOL when available. # if __OBJC_BOOL_IS_BOOL # define OBJC_BOOL_IS_BOOL 1 # else # define OBJC_BOOL_IS_BOOL 0 # endif #else // __OBJC_BOOL_IS_BOOL not set. # if TARGET_OS_OSX || TARGET_OS_IOSMAC || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K) # define OBJC_BOOL_IS_BOOL 0 # else # define OBJC_BOOL_IS_BOOL 1 # endif #endif #if OBJC_BOOL_IS_BOOL typedef bool BOOL; #else # define OBJC_BOOL_IS_CHAR 1 typedef signed char BOOL; // BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C" // even if -funsigned-char is used. #endif #define OBJC_BOOL_DEFINED #if __has_feature(objc_bool) #define YES __objc_yes #define NO __objc_no #else #define YES ((BOOL)1) #define NO ((BOOL)0) #endif #ifndef Nil # if __has_feature(cxx_nullptr) # define Nil nullptr # else # define Nil __DARWIN_NULL # endif #endif #ifndef nil # if __has_feature(cxx_nullptr) # define nil nullptr # else # define nil __DARWIN_NULL # endif #endif #ifndef __strong # if !__has_feature(objc_arc) # define __strong /* empty */ # endif #endif #ifndef __unsafe_unretained # if !__has_feature(objc_arc) # define __unsafe_unretained /* empty */ # endif #endif #ifndef __autoreleasing # if !__has_feature(objc_arc) # define __autoreleasing /* empty */ # endif #endif /** * Returns the name of the method specified by a given selector. * * @param sel A pointer of type \c SEL. Pass the selector whose name you wish to determine. * * @return A C string indicating the name of the selector. */ OBJC_EXPORT const char * _Nonnull sel_getName(SEL _Nonnull sel) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Registers a method with the Objective-C runtime system, maps the method * name to a selector, and returns the selector value. * * @param str A pointer to a C string. Pass the name of the method you wish to register. * * @return A pointer of type SEL specifying the selector for the named method. * * @note You must register a method name with the Objective-C runtime system to obtain the * method’s selector before you can add the method to a class definition. If the method name * has already been registered, this function simply returns the selector. */ OBJC_EXPORT SEL _Nonnull sel_registerName(const char * _Nonnull str) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the class name of a given object. * * @param obj An Objective-C object. * * @return The name of the class of which \e obj is an instance. */ OBJC_EXPORT const char * _Nonnull object_getClassName(id _Nullable obj) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns a pointer to any extra bytes allocated with an instance given object. * * @param obj An Objective-C object. * * @return A pointer to any extra bytes allocated with \e obj. If \e obj was * not allocated with any extra bytes, then dereferencing the returned pointer is undefined. * * @note This function returns a pointer to any extra bytes allocated with the instance * (as specified by \c class_createInstance with extraBytes>0). This memory follows the * object's ordinary ivars, but may not be adjacent to the last ivar. * @note The returned pointer is guaranteed to be pointer-size aligned, even if the area following * the object's last ivar is less aligned than that. Alignment greater than pointer-size is never * guaranteed, even if the area following the object's last ivar is more aligned than that. * @note In a garbage-collected environment, the memory is scanned conservatively. */ OBJC_EXPORT void * _Nullable object_getIndexedIvars(id _Nullable obj) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** * Identifies a selector as being valid or invalid. * * @param sel The selector you want to identify. * * @return YES if selector is valid and has a function implementation, NO otherwise. * * @warning On some platforms, an invalid reference (to invalid memory addresses) can cause * a crash. */ OBJC_EXPORT BOOL sel_isMapped(SEL _Nonnull sel) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Registers a method name with the Objective-C runtime system. * * @param str A pointer to a C string. Pass the name of the method you wish to register. * * @return A pointer of type SEL specifying the selector for the named method. * * @note The implementation of this method is identical to the implementation of \c sel_registerName. * @note Prior to OS X version 10.0, this method tried to find the selector mapped to the given name * and returned \c NULL if the selector was not found. This was changed for safety, because it was * observed that many of the callers of this function did not check the return value for \c NULL. */ OBJC_EXPORT SEL _Nonnull sel_getUid(const char * _Nonnull str) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); typedef const void* objc_objectptr_t; // Obsolete ARC conversions. OBJC_EXPORT id _Nullable objc_retainedObject(objc_objectptr_t _Nullable obj) #if !OBJC_DECLARE_SYMBOLS OBJC_UNAVAILABLE("use CFBridgingRelease() or a (__bridge_transfer id) cast instead") #endif ; OBJC_EXPORT id _Nullable objc_unretainedObject(objc_objectptr_t _Nullable obj) #if !OBJC_DECLARE_SYMBOLS OBJC_UNAVAILABLE("use a (__bridge id) cast instead") #endif ; OBJC_EXPORT objc_objectptr_t _Nullable objc_unretainedPointer(id _Nullable obj) #if !OBJC_DECLARE_SYMBOLS OBJC_UNAVAILABLE("use a __bridge cast instead") #endif ; #if !__OBJC2__ // The following declarations are provided here for source compatibility. #if defined(__LP64__) typedef long arith_t; typedef unsigned long uarith_t; # define ARITH_SHIFT 32 #else typedef int arith_t; typedef unsigned uarith_t; # define ARITH_SHIFT 16 #endif typedef char *STR; #define ISSELECTOR(sel) sel_isMapped(sel) #define SELNAME(sel) sel_getName(sel) #define SELUID(str) sel_getUid(str) #define NAMEOF(obj) object_getClassName(obj) #define IV(obj) object_getIndexedIvars(obj) #endif #endif /* _OBJC_OBJC_H_ */ ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objcrt.c ================================================ #define WIN32_LEAN_AND_MEAN #include #include #include #include "objcrt.h" // Boundary symbols for metadata sections #pragma section(".objc_module_info$A",long,read,write) #pragma data_seg(".objc_module_info$A") static uintptr_t __objc_modStart = 0; #pragma section(".objc_module_info$C",long,read,write) #pragma data_seg(".objc_module_info$C") static uintptr_t __objc_modEnd = 0; #pragma section(".objc_protocol$A",long,read,write) #pragma data_seg(".objc_protocol$A") static uintptr_t __objc_protoStart = 0; #pragma section(".objc_protocol$C",long,read,write) #pragma data_seg(".objc_protocol$C") static uintptr_t __objc_protoEnd = 0; #pragma section(".objc_image_info$A",long,read,write) #pragma data_seg(".objc_image_info$A") static uintptr_t __objc_iiStart = 0; #pragma section(".objc_image_info$C",long,read,write) #pragma data_seg(".objc_image_info$C") static uintptr_t __objc_iiEnd = 0; #pragma section(".objc_message_refs$A",long,read,write) #pragma data_seg(".objc_message_refs$A") static uintptr_t __objc_selrefsStart = 0; #pragma section(".objc_message_refs$C",long,read,write) #pragma data_seg(".objc_message_refs$C") static uintptr_t __objc_selrefsEnd = 0; #pragma section(".objc_class_refs$A",long,read,write) #pragma data_seg(".objc_class_refs$A") static uintptr_t __objc_clsrefsStart = 0; #pragma section(".objc_class_refs$C",long,read,write) #pragma data_seg(".objc_class_refs$C") static uintptr_t __objc_clsrefsEnd = 0; #pragma data_seg() // Merge all metadata into .data // fixme order these by usage? #pragma comment(linker, "/MERGE:.objc_module_info=.data") #pragma comment(linker, "/MERGE:.objc_protocol=.data") #pragma comment(linker, "/MERGE:.objc_image_info=.data") #pragma comment(linker, "/MERGE:.objc_message_refs=.data") #pragma comment(linker, "/MERGE:.objc_class_refs=.data") // Image initializers static void *__hinfo = NULL; // cookie from runtime extern IMAGE_DOS_HEADER __ImageBase; // this image's header static int __objc_init(void) { objc_sections sections = { 5, &__objc_modStart, &__objc_modEnd, &__objc_protoStart, &__objc_protoEnd, &__objc_iiStart, &__objc_iiEnd, &__objc_selrefsStart, &__objc_selrefsEnd, &__objc_clsrefsStart, &__objc_clsrefsEnd, }; __hinfo = _objc_init_image((HMODULE)&__ImageBase, §ions); return 0; } static void __objc_unload(void) { _objc_unload_image((HMODULE)&__ImageBase, __hinfo); } static int __objc_load(void) { _objc_load_image((HMODULE)&__ImageBase, __hinfo); return 0; } // run _objc_init_image ASAP #pragma section(".CRT$XIAA",long,read,write) #pragma data_seg(".CRT$XIAA") static void *__objc_init_fn = &__objc_init; // run _objc_load_image (+load methods) after all other initializers; // otherwise constant NSStrings are not initialized yet #pragma section(".CRT$XCUO",long,read,write) #pragma data_seg(".CRT$XCUO") static void *__objc_load_fn = &__objc_load; // _objc_unload_image is called by atexit(), not by an image terminator #pragma data_seg() ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/objcrt.h ================================================ #ifndef _OBJC_RT_H_ #define _OBJC_RT_H_ #include typedef struct { int count; // number of pointer pairs that follow void *modStart; void *modEnd; void *protoStart; void *protoEnd; void *iiStart; void *iiEnd; void *selrefsStart; void *selrefsEnd; void *clsrefsStart; void *clsrefsEnd; } objc_sections; OBJC_EXPORT void *_objc_init_image(HMODULE image, const objc_sections *sects); OBJC_EXPORT void _objc_load_image(HMODULE image, void *hinfo); OBJC_EXPORT void _objc_unload_image(HMODULE image, void *hinfo); #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/runtime/runtime.h ================================================ /* * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _OBJC_RUNTIME_H #define _OBJC_RUNTIME_H #include #include #include #include #include #include #if TARGET_OS_MAC #include #endif /* Types */ #if !OBJC_TYPES_DEFINED /// An opaque type that represents a method in a class definition. typedef struct objc_method *Method; /// An opaque type that represents an instance variable. typedef struct objc_ivar *Ivar; /// An opaque type that represents a category. typedef struct objc_category *Category; /// An opaque type that represents an Objective-C declared property. typedef struct objc_property *objc_property_t; struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */ #endif #ifdef __OBJC__ @class Protocol; #else typedef struct objc_object Protocol; #endif /// Defines a method struct objc_method_description { SEL _Nullable name; /**< The name of the method */ char * _Nullable types; /**< The types of the method arguments */ }; /// Defines a property attribute typedef struct { const char * _Nonnull name; /**< The name of the attribute */ const char * _Nonnull value; /**< The value of the attribute (usually empty) */ } objc_property_attribute_t; /* Functions */ /* Working with Instances */ /** * Returns a copy of a given object. * * @param obj An Objective-C object. * @param size The size of the object \e obj. * * @return A copy of \e obj. */ OBJC_EXPORT id _Nullable object_copy(id _Nullable obj, size_t size) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** * Frees the memory occupied by a given object. * * @param obj An Objective-C object. * * @return nil */ OBJC_EXPORT id _Nullable object_dispose(id _Nullable obj) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** * Returns the class of an object. * * @param obj The object you want to inspect. * * @return The class object of which \e object is an instance, * or \c Nil if \e object is \c nil. */ OBJC_EXPORT Class _Nullable object_getClass(id _Nullable obj) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the class of an object. * * @param obj The object to modify. * @param cls A class object. * * @return The previous value of \e object's class, or \c Nil if \e object is \c nil. */ OBJC_EXPORT Class _Nullable object_setClass(id _Nullable obj, Class _Nonnull cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns whether an object is a class object. * * @param obj An Objective-C object. * * @return true if the object is a class or metaclass, false otherwise. */ OBJC_EXPORT BOOL object_isClass(id _Nullable obj) OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0, 2.0); /** * Reads the value of an instance variable in an object. * * @param obj The object containing the instance variable whose value you want to read. * @param ivar The Ivar describing the instance variable whose value you want to read. * * @return The value of the instance variable specified by \e ivar, or \c nil if \e object is \c nil. * * @note \c object_getIvar is faster than \c object_getInstanceVariable if the Ivar * for the instance variable is already known. */ OBJC_EXPORT id _Nullable object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the value of an instance variable in an object. * * @param obj The object containing the instance variable whose value you want to set. * @param ivar The Ivar describing the instance variable whose value you want to set. * @param value The new value for the instance variable. * * @note Instance variables with known memory management (such as ARC strong and weak) * use that memory management. Instance variables with unknown memory management * are assigned as if they were unsafe_unretained. * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar * for the instance variable is already known. */ OBJC_EXPORT void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the value of an instance variable in an object. * * @param obj The object containing the instance variable whose value you want to set. * @param ivar The Ivar describing the instance variable whose value you want to set. * @param value The new value for the instance variable. * * @note Instance variables with known memory management (such as ARC strong and weak) * use that memory management. Instance variables with unknown memory management * are assigned as if they were strong. * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar * for the instance variable is already known. */ OBJC_EXPORT void object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); /** * Changes the value of an instance variable of a class instance. * * @param obj A pointer to an instance of a class. Pass the object containing * the instance variable whose value you wish to modify. * @param name A C string. Pass the name of the instance variable whose value you wish to modify. * @param value The new value for the instance variable. * * @return A pointer to the \c Ivar data structure that defines the type and * name of the instance variable specified by \e name. * * @note Instance variables with known memory management (such as ARC strong and weak) * use that memory management. Instance variables with unknown memory management * are assigned as if they were unsafe_unretained. */ OBJC_EXPORT Ivar _Nullable object_setInstanceVariable(id _Nullable obj, const char * _Nonnull name, void * _Nullable value) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** * Changes the value of an instance variable of a class instance. * * @param obj A pointer to an instance of a class. Pass the object containing * the instance variable whose value you wish to modify. * @param name A C string. Pass the name of the instance variable whose value you wish to modify. * @param value The new value for the instance variable. * * @return A pointer to the \c Ivar data structure that defines the type and * name of the instance variable specified by \e name. * * @note Instance variables with known memory management (such as ARC strong and weak) * use that memory management. Instance variables with unknown memory management * are assigned as if they were strong. */ OBJC_EXPORT Ivar _Nullable object_setInstanceVariableWithStrongDefault(id _Nullable obj, const char * _Nonnull name, void * _Nullable value) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0) OBJC_ARC_UNAVAILABLE; /** * Obtains the value of an instance variable of a class instance. * * @param obj A pointer to an instance of a class. Pass the object containing * the instance variable whose value you wish to obtain. * @param name A C string. Pass the name of the instance variable whose value you wish to obtain. * @param outValue On return, contains a pointer to the value of the instance variable. * * @return A pointer to the \c Ivar data structure that defines the type and name of * the instance variable specified by \e name. */ OBJC_EXPORT Ivar _Nullable object_getInstanceVariable(id _Nullable obj, const char * _Nonnull name, void * _Nullable * _Nullable outValue) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /* Obtaining Class Definitions */ /** * Returns the class definition of a specified class. * * @param name The name of the class to look up. * * @return The Class object for the named class, or \c nil * if the class is not registered with the Objective-C runtime. * * @note \c objc_getClass is different from \c objc_lookUpClass in that if the class * is not registered, \c objc_getClass calls the class handler callback and then checks * a second time to see whether the class is registered. \c objc_lookUpClass does * not call the class handler callback. * * @warning Earlier implementations of this function (prior to OS X v10.0) * terminate the program if the class does not exist. */ OBJC_EXPORT Class _Nullable objc_getClass(const char * _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the metaclass definition of a specified class. * * @param name The name of the class to look up. * * @return The \c Class object for the metaclass of the named class, or \c nil if the class * is not registered with the Objective-C runtime. * * @note If the definition for the named class is not registered, this function calls the class handler * callback and then checks a second time to see if the class is registered. However, every class * definition must have a valid metaclass definition, and so the metaclass definition is always returned, * whether it’s valid or not. */ OBJC_EXPORT Class _Nullable objc_getMetaClass(const char * _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the class definition of a specified class. * * @param name The name of the class to look up. * * @return The Class object for the named class, or \c nil if the class * is not registered with the Objective-C runtime. * * @note \c objc_getClass is different from this function in that if the class is not * registered, \c objc_getClass calls the class handler callback and then checks a second * time to see whether the class is registered. This function does not call the class handler callback. */ OBJC_EXPORT Class _Nullable objc_lookUpClass(const char * _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the class definition of a specified class. * * @param name The name of the class to look up. * * @return The Class object for the named class. * * @note This function is the same as \c objc_getClass, but kills the process if the class is not found. * @note This function is used by ZeroLink, where failing to find a class would be a compile-time link error without ZeroLink. */ OBJC_EXPORT Class _Nonnull objc_getRequiredClass(const char * _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Obtains the list of registered class definitions. * * @param buffer An array of \c Class values. On output, each \c Class value points to * one class definition, up to either \e bufferCount or the total number of registered classes, * whichever is less. You can pass \c NULL to obtain the total number of registered class * definitions without actually retrieving any class definitions. * @param bufferCount An integer value. Pass the number of pointers for which you have allocated space * in \e buffer. On return, this function fills in only this number of elements. If this number is less * than the number of registered classes, this function returns an arbitrary subset of the registered classes. * * @return An integer value indicating the total number of registered classes. * * @note The Objective-C runtime library automatically registers all the classes defined in your source code. * You can create class definitions at runtime and register them with the \c objc_addClass function. * * @warning You cannot assume that class objects you get from this function are classes that inherit from \c NSObject, * so you cannot safely call any methods on such classes without detecting that the method is implemented first. */ OBJC_EXPORT int objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Creates and returns a list of pointers to all registered class definitions. * * @param outCount An integer pointer used to store the number of classes returned by * this function in the list. It can be \c nil. * * @return A nil terminated array of classes. It must be freed with \c free(). * * @see objc_getClassList */ OBJC_EXPORT Class _Nonnull * _Nullable objc_copyClassList(unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0, 2.0); /* Working with Classes */ /** * Returns the name of a class. * * @param cls A class object. * * @return The name of the class, or the empty string if \e cls is \c Nil. */ OBJC_EXPORT const char * _Nonnull class_getName(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether a class object is a metaclass. * * @param cls A class object. * * @return \c YES if \e cls is a metaclass, \c NO if \e cls is a non-meta class, * \c NO if \e cls is \c Nil. */ OBJC_EXPORT BOOL class_isMetaClass(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the superclass of a class. * * @param cls A class object. * * @return The superclass of the class, or \c Nil if * \e cls is a root class, or \c Nil if \e cls is \c Nil. * * @note You should usually use \c NSObject's \c superclass method instead of this function. */ OBJC_EXPORT Class _Nullable class_getSuperclass(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the superclass of a given class. * * @param cls The class whose superclass you want to set. * @param newSuper The new superclass for cls. * * @return The old superclass for cls. * * @warning You should not use this function. */ OBJC_EXPORT Class _Nonnull class_setSuperclass(Class _Nonnull cls, Class _Nonnull newSuper) __OSX_DEPRECATED(10.5, 10.5, "not recommended") __IOS_DEPRECATED(2.0, 2.0, "not recommended") __TVOS_DEPRECATED(9.0, 9.0, "not recommended") __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended") __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended"); /** * Returns the version number of a class definition. * * @param cls A pointer to a \c Class data structure. Pass * the class definition for which you wish to obtain the version. * * @return An integer indicating the version number of the class definition. * * @see class_setVersion */ OBJC_EXPORT int class_getVersion(Class _Nullable cls) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Sets the version number of a class definition. * * @param cls A pointer to an Class data structure. * Pass the class definition for which you wish to set the version. * @param version An integer. Pass the new version number of the class definition. * * @note You can use the version number of the class definition to provide versioning of the * interface that your class represents to other classes. This is especially useful for object * serialization (that is, archiving of the object in a flattened form), where it is important to * recognize changes to the layout of the instance variables in different class-definition versions. * @note Classes derived from the Foundation framework \c NSObject class can set the class-definition * version number using the \c setVersion: class method, which is implemented using the \c class_setVersion function. */ OBJC_EXPORT void class_setVersion(Class _Nullable cls, int version) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the size of instances of a class. * * @param cls A class object. * * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil. */ OBJC_EXPORT size_t class_getInstanceSize(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the \c Ivar for a specified instance variable of a given class. * * @param cls The class whose instance variable you wish to obtain. * @param name The name of the instance variable definition to obtain. * * @return A pointer to an \c Ivar data structure containing information about * the instance variable specified by \e name. */ OBJC_EXPORT Ivar _Nullable class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the Ivar for a specified class variable of a given class. * * @param cls The class definition whose class variable you wish to obtain. * @param name The name of the class variable definition to obtain. * * @return A pointer to an \c Ivar data structure containing information about the class variable specified by \e name. */ OBJC_EXPORT Ivar _Nullable class_getClassVariable(Class _Nullable cls, const char * _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Describes the instance variables declared by a class. * * @param cls The class to inspect. * @param outCount On return, contains the length of the returned array. * If outCount is NULL, the length is not returned. * * @return An array of pointers of type Ivar describing the instance variables declared by the class. * Any instance variables declared by superclasses are not included. The array contains *outCount * pointers followed by a NULL terminator. You must free the array with free(). * * If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0. */ OBJC_EXPORT Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a specified instance method for a given class. * * @param cls The class you want to inspect. * @param name The selector of the method you want to retrieve. * * @return The method that corresponds to the implementation of the selector specified by * \e name for the class specified by \e cls, or \c NULL if the specified class or its * superclasses do not contain an instance method with the specified selector. * * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not. */ OBJC_EXPORT Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns a pointer to the data structure describing a given class method for a given class. * * @param cls A pointer to a class definition. Pass the class that contains the method you want to retrieve. * @param name A pointer of type \c SEL. Pass the selector of the method you want to retrieve. * * @return A pointer to the \c Method data structure that corresponds to the implementation of the * selector specified by aSelector for the class specified by aClass, or NULL if the specified * class or its superclasses do not contain an instance method with the specified selector. * * @note Note that this function searches superclasses for implementations, * whereas \c class_copyMethodList does not. */ OBJC_EXPORT Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the function pointer that would be called if a * particular message were sent to an instance of a class. * * @param cls The class you want to inspect. * @param name A selector. * * @return The function pointer that would be called if \c [object name] were called * with an instance of the class, or \c NULL if \e cls is \c Nil. * * @note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)). * @note The function pointer returned may be a function internal to the runtime instead of * an actual method implementation. For example, if instances of the class do not respond to * the selector, the function pointer returned will be part of the runtime's message forwarding machinery. */ OBJC_EXPORT IMP _Nullable class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the function pointer that would be called if a particular * message were sent to an instance of a class. * * @param cls The class you want to inspect. * @param name A selector. * * @return The function pointer that would be called if \c [object name] were called * with an instance of the class, or \c NULL if \e cls is \c Nil. */ OBJC_EXPORT IMP _Nullable class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; /** * Returns a Boolean value that indicates whether instances of a class respond to a particular selector. * * @param cls The class you want to inspect. * @param sel A selector. * * @return \c YES if instances of the class respond to the selector, otherwise \c NO. * * @note You should usually use \c NSObject's \c respondsToSelector: or \c instancesRespondToSelector: * methods instead of this function. */ OBJC_EXPORT BOOL class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Describes the instance methods implemented by a class. * * @param cls The class you want to inspect. * @param outCount On return, contains the length of the returned array. * If outCount is NULL, the length is not returned. * * @return An array of pointers of type Method describing the instance methods * implemented by the class—any instance methods implemented by superclasses are not included. * The array contains *outCount pointers followed by a NULL terminator. You must free the array with free(). * * If cls implements no instance methods, or cls is Nil, returns NULL and *outCount is 0. * * @note To get the class methods of a class, use \c class_copyMethodList(object_getClass(cls), &count). * @note To get the implementations of methods that may be implemented by superclasses, * use \c class_getInstanceMethod or \c class_getClassMethod. */ OBJC_EXPORT Method _Nonnull * _Nullable class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether a class conforms to a given protocol. * * @param cls The class you want to inspect. * @param protocol A protocol. * * @return YES if cls conforms to protocol, otherwise NO. * * @note You should usually use NSObject's conformsToProtocol: method instead of this function. */ OBJC_EXPORT BOOL class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Describes the protocols adopted by a class. * * @param cls The class you want to inspect. * @param outCount On return, contains the length of the returned array. * If outCount is NULL, the length is not returned. * * @return An array of pointers of type Protocol* describing the protocols adopted * by the class. Any protocols adopted by superclasses or other protocols are not included. * The array contains *outCount pointers followed by a NULL terminator. You must free the array with free(). * * If cls adopts no protocols, or cls is Nil, returns NULL and *outCount is 0. */ OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a property with a given name of a given class. * * @param cls The class you want to inspect. * @param name The name of the property you want to inspect. * * @return A pointer of type \c objc_property_t describing the property, or * \c NULL if the class does not declare a property with that name, * or \c NULL if \e cls is \c Nil. */ OBJC_EXPORT objc_property_t _Nullable class_getProperty(Class _Nullable cls, const char * _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Describes the properties declared by a class. * * @param cls The class you want to inspect. * @param outCount On return, contains the length of the returned array. * If \e outCount is \c NULL, the length is not returned. * * @return An array of pointers of type \c objc_property_t describing the properties * declared by the class. Any properties declared by superclasses are not included. * The array contains \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free(). * * If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0. */ OBJC_EXPORT objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a description of the \c Ivar layout for a given class. * * @param cls The class to inspect. * * @return A description of the \c Ivar layout for \e cls. */ OBJC_EXPORT const uint8_t * _Nullable class_getIvarLayout(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a description of the layout of weak Ivars for a given class. * * @param cls The class to inspect. * * @return A description of the layout of the weak \c Ivars for \e cls. */ OBJC_EXPORT const uint8_t * _Nullable class_getWeakIvarLayout(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Adds a new method to a class with a given name and implementation. * * @param cls The class to which to add a method. * @param name A selector that specifies the name of the method being added. * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd. * @param types An array of characters that describe the types of the arguments to the method. * * @return YES if the method was added successfully, otherwise NO * (for example, the class already contains a method implementation with that name). * * @note class_addMethod will add an override of a superclass's implementation, * but will not replace an existing implementation in this class. * To change an existing implementation, use method_setImplementation. */ OBJC_EXPORT BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Replaces the implementation of a method for a given class. * * @param cls The class you want to modify. * @param name A selector that identifies the method whose implementation you want to replace. * @param imp The new implementation for the method identified by name for the class identified by cls. * @param types An array of characters that describe the types of the arguments to the method. * Since the function must take at least two arguments—self and _cmd, the second and third characters * must be “@:” (the first character is the return type). * * @return The previous implementation of the method identified by \e name for the class identified by \e cls. * * @note This function behaves in two different ways: * - If the method identified by \e name does not yet exist, it is added as if \c class_addMethod were called. * The type encoding specified by \e types is used as given. * - If the method identified by \e name does exist, its \c IMP is replaced as if \c method_setImplementation were called. * The type encoding specified by \e types is ignored. */ OBJC_EXPORT IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Adds a new instance variable to a class. * * @return YES if the instance variable was added successfully, otherwise NO * (for example, the class already contains an instance variable with that name). * * @note This function may only be called after objc_allocateClassPair and before objc_registerClassPair. * Adding an instance variable to an existing class is not supported. * @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported. * @note The instance variable's minimum alignment in bytes is 1< Type Encodings. */ OBJC_EXPORT const char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the offset of an instance variable. * * @param v The instance variable you want to enquire about. * * @return The offset of \e v. * * @note For instance variables of type \c id or other object types, call \c object_getIvar * and \c object_setIvar instead of using this offset to access the instance variable data directly. */ OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar _Nonnull v) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /* Working with Properties */ /** * Returns the name of a property. * * @param property The property you want to inquire about. * * @return A C string containing the property's name. */ OBJC_EXPORT const char * _Nonnull property_getName(objc_property_t _Nonnull property) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the attribute string of a property. * * @param property A property. * * @return A C string containing the property's attributes. * * @note The format of the attribute string is described in Declared Properties in Objective-C Runtime Programming Guide. */ OBJC_EXPORT const char * _Nullable property_getAttributes(objc_property_t _Nonnull property) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of property attributes for a property. * * @param property The property whose attributes you want copied. * @param outCount The number of attributes returned in the array. * * @return An array of property attributes; must be free'd() by the caller. */ OBJC_EXPORT objc_property_attribute_t * _Nullable property_copyAttributeList(objc_property_t _Nonnull property, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Returns the value of a property attribute given the attribute name. * * @param property The property whose attribute value you are interested in. * @param attributeName C string representing the attribute name. * * @return The value string of the attribute \e attributeName if it exists in * \e property, \c nil otherwise. */ OBJC_EXPORT char * _Nullable property_copyAttributeValue(objc_property_t _Nonnull property, const char * _Nonnull attributeName) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /* Working with Protocols */ /** * Returns a specified protocol. * * @param name The name of a protocol. * * @return The protocol named \e name, or \c NULL if no protocol named \e name could be found. * * @note This function acquires the runtime lock. */ OBJC_EXPORT Protocol * _Nullable objc_getProtocol(const char * _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of all the protocols known to the runtime. * * @param outCount Upon return, contains the number of protocols in the returned array. * * @return A C array of all the protocols known to the runtime. The array contains \c *outCount * pointers followed by a \c NULL terminator. You must free the list with \c free(). * * @note This function acquires the runtime lock. */ OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable objc_copyProtocolList(unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether one protocol conforms to another protocol. * * @param proto A protocol. * @param other A protocol. * * @return \c YES if \e proto conforms to \e other, otherwise \c NO. * * @note One protocol can incorporate other protocols using the same syntax * that classes use to adopt a protocol: * \code * @protocol ProtocolName < protocol list > * \endcode * All the protocols listed between angle brackets are considered part of the ProtocolName protocol. */ OBJC_EXPORT BOOL protocol_conformsToProtocol(Protocol * _Nullable proto, Protocol * _Nullable other) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether two protocols are equal. * * @param proto A protocol. * @param other A protocol. * * @return \c YES if \e proto is the same as \e other, otherwise \c NO. */ OBJC_EXPORT BOOL protocol_isEqual(Protocol * _Nullable proto, Protocol * _Nullable other) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the name of a protocol. * * @param proto A protocol. * * @return The name of the protocol \e p as a C string. */ OBJC_EXPORT const char * _Nonnull protocol_getName(Protocol * _Nonnull proto) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a method description structure for a specified method of a given protocol. * * @param proto A protocol. * @param aSel A selector. * @param isRequiredMethod A Boolean value that indicates whether aSel is a required method. * @param isInstanceMethod A Boolean value that indicates whether aSel is an instance method. * * @return An \c objc_method_description structure that describes the method specified by \e aSel, * \e isRequiredMethod, and \e isInstanceMethod for the protocol \e p. * If the protocol does not contain the specified method, returns an \c objc_method_description structure * with the value \c {NULL, \c NULL}. * * @note This function recursively searches any protocols that this protocol conforms to. */ OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel, BOOL isRequiredMethod, BOOL isInstanceMethod) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of method descriptions of methods meeting a given specification for a given protocol. * * @param proto A protocol. * @param isRequiredMethod A Boolean value that indicates whether returned methods should * be required methods (pass YES to specify required methods). * @param isInstanceMethod A Boolean value that indicates whether returned methods should * be instance methods (pass YES to specify instance methods). * @param outCount Upon return, contains the number of method description structures in the returned array. * * @return A C array of \c objc_method_description structures containing the names and types of \e p's methods * specified by \e isRequiredMethod and \e isInstanceMethod. The array contains \c *outCount pointers followed * by a \c NULL terminator. You must free the list with \c free(). * If the protocol declares no methods that meet the specification, \c NULL is returned and \c *outCount is 0. * * @note Methods in other protocols adopted by this protocol are not included. */ OBJC_EXPORT struct objc_method_description * _Nullable protocol_copyMethodDescriptionList(Protocol * _Nonnull proto, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the specified property of a given protocol. * * @param proto A protocol. * @param name The name of a property. * @param isRequiredProperty \c YES searches for a required property, \c NO searches for an optional property. * @param isInstanceProperty \c YES searches for an instance property, \c NO searches for a class property. * * @return The property specified by \e name, \e isRequiredProperty, and \e isInstanceProperty for \e proto, * or \c NULL if none of \e proto's properties meets the specification. */ OBJC_EXPORT objc_property_t _Nullable protocol_getProperty(Protocol * _Nonnull proto, const char * _Nonnull name, BOOL isRequiredProperty, BOOL isInstanceProperty) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of the required instance properties declared by a protocol. * * @note Identical to * \code * protocol_copyPropertyList2(proto, outCount, YES, YES); * \endcode */ OBJC_EXPORT objc_property_t _Nonnull * _Nullable protocol_copyPropertyList(Protocol * _Nonnull proto, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of properties declared by a protocol. * * @param proto A protocol. * @param outCount Upon return, contains the number of elements in the returned array. * @param isRequiredProperty \c YES returns required properties, \c NO returns optional properties. * @param isInstanceProperty \c YES returns instance properties, \c NO returns class properties. * * @return A C array of pointers of type \c objc_property_t describing the properties declared by \e proto. * Any properties declared by other protocols adopted by this protocol are not included. The array contains * \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free(). * If the protocol declares no matching properties, \c NULL is returned and \c *outCount is \c 0. */ OBJC_EXPORT objc_property_t _Nonnull * _Nullable protocol_copyPropertyList2(Protocol * _Nonnull proto, unsigned int * _Nullable outCount, BOOL isRequiredProperty, BOOL isInstanceProperty) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); /** * Returns an array of the protocols adopted by a protocol. * * @param proto A protocol. * @param outCount Upon return, contains the number of elements in the returned array. * * @return A C array of protocols adopted by \e proto. The array contains \e *outCount pointers * followed by a \c NULL terminator. You must free the array with \c free(). * If the protocol declares no properties, \c NULL is returned and \c *outCount is \c 0. */ OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable protocol_copyProtocolList(Protocol * _Nonnull proto, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Creates a new protocol instance that cannot be used until registered with * \c objc_registerProtocol() * * @param name The name of the protocol to create. * * @return The Protocol instance on success, \c nil if a protocol * with the same name already exists. * @note There is no dispose method for this. */ OBJC_EXPORT Protocol * _Nullable objc_allocateProtocol(const char * _Nonnull name) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Registers a newly constructed protocol with the runtime. The protocol * will be ready for use and is immutable after this. * * @param proto The protocol you want to register. */ OBJC_EXPORT void objc_registerProtocol(Protocol * _Nonnull proto) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Adds a method to a protocol. The protocol must be under construction. * * @param proto The protocol to add a method to. * @param name The name of the method to add. * @param types A C string that represents the method signature. * @param isRequiredMethod YES if the method is not an optional method. * @param isInstanceMethod YES if the method is an instance method. */ OBJC_EXPORT void protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name, const char * _Nullable types, BOOL isRequiredMethod, BOOL isInstanceMethod) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Adds an incorporated protocol to another protocol. The protocol being * added to must still be under construction, while the additional protocol * must be already constructed. * * @param proto The protocol you want to add to, it must be under construction. * @param addition The protocol you want to incorporate into \e proto, it must be registered. */ OBJC_EXPORT void protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Adds a property to a protocol. The protocol must be under construction. * * @param proto The protocol to add a property to. * @param name The name of the property. * @param attributes An array of property attributes. * @param attributeCount The number of attributes in \e attributes. * @param isRequiredProperty YES if the property (accessor methods) is not optional. * @param isInstanceProperty YES if the property (accessor methods) are instance methods. * This is the only case allowed fo a property, as a result, setting this to NO will * not add the property to the protocol at all. */ OBJC_EXPORT void protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /* Working with Libraries */ /** * Returns the names of all the loaded Objective-C frameworks and dynamic * libraries. * * @param outCount The number of names returned. * * @return An array of C strings of names. Must be free()'d by caller. */ OBJC_EXPORT const char * _Nonnull * _Nonnull objc_copyImageNames(unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the dynamic library name a class originated from. * * @param cls The class you are inquiring about. * * @return The name of the library containing this class. */ OBJC_EXPORT const char * _Nullable class_getImageName(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the names of all the classes within a library. * * @param image The library or framework you are inquiring about. * @param outCount The number of class names returned. * * @return An array of C strings representing the class names. */ OBJC_EXPORT const char * _Nonnull * _Nullable objc_copyClassNamesForImage(const char * _Nonnull image, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /* Working with Selectors */ /** * Returns the name of the method specified by a given selector. * * @param sel A pointer of type \c SEL. Pass the selector whose name you wish to determine. * * @return A C string indicating the name of the selector. */ OBJC_EXPORT const char * _Nonnull sel_getName(SEL _Nonnull sel) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Registers a method with the Objective-C runtime system, maps the method * name to a selector, and returns the selector value. * * @param str A pointer to a C string. Pass the name of the method you wish to register. * * @return A pointer of type SEL specifying the selector for the named method. * * @note You must register a method name with the Objective-C runtime system to obtain the * method’s selector before you can add the method to a class definition. If the method name * has already been registered, this function simply returns the selector. */ OBJC_EXPORT SEL _Nonnull sel_registerName(const char * _Nonnull str) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether two selectors are equal. * * @param lhs The selector to compare with rhs. * @param rhs The selector to compare with lhs. * * @return \c YES if \e lhs and \e rhs are equal, otherwise \c NO. * * @note sel_isEqual is equivalent to ==. */ OBJC_EXPORT BOOL sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /* Objective-C Language Features */ /** * This function is inserted by the compiler when a mutation * is detected during a foreach iteration. It gets called * when a mutation occurs, and the enumerationMutationHandler * is enacted if it is set up. A fatal error occurs if a handler is not set up. * * @param obj The object being mutated. * */ OBJC_EXPORT void objc_enumerationMutation(id _Nonnull obj) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the current mutation handler. * * @param handler Function pointer to the new mutation handler. */ OBJC_EXPORT void objc_setEnumerationMutationHandler(void (*_Nullable handler)(id _Nonnull )) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Set the function to be called by objc_msgForward. * * @param fwd Function to be jumped to by objc_msgForward. * @param fwd_stret Function to be jumped to by objc_msgForward_stret. * * @see message.h::_objc_msgForward */ OBJC_EXPORT void objc_setForwardHandler(void * _Nonnull fwd, void * _Nonnull fwd_stret) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Creates a pointer to a function that will call the block * when the method is called. * * @param block The block that implements this method. Its signature should * be: method_return_type ^(id self, method_args...). * The selector is not available as a parameter to this block. * The block is copied with \c Block_copy(). * * @return The IMP that calls this block. Must be disposed of with * \c imp_removeBlock. */ OBJC_EXPORT IMP _Nonnull imp_implementationWithBlock(id _Nonnull block) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Return the block associated with an IMP that was created using * \c imp_implementationWithBlock. * * @param anImp The IMP that calls this block. * * @return The block called by \e anImp. */ OBJC_EXPORT id _Nullable imp_getBlock(IMP _Nonnull anImp) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Disassociates a block from an IMP that was created using * \c imp_implementationWithBlock and releases the copy of the * block that was created. * * @param anImp An IMP that was created using \c imp_implementationWithBlock. * * @return YES if the block was released successfully, NO otherwise. * (For example, the block might not have been used to create an IMP previously). */ OBJC_EXPORT BOOL imp_removeBlock(IMP _Nonnull anImp) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * This loads the object referenced by a weak pointer and returns it, after * retaining and autoreleasing the object to ensure that it stays alive * long enough for the caller to use it. This function would be used * anywhere a __weak variable is used in an expression. * * @param location The weak pointer address * * @return The object pointed to by \e location, or \c nil if \e *location is \c nil. */ OBJC_EXPORT id _Nullable objc_loadWeak(id _Nullable * _Nonnull location) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); /** * This function stores a new value into a __weak variable. It would * be used anywhere a __weak variable is the target of an assignment. * * @param location The address of the weak pointer itself * @param obj The new object this weak ptr should now point to * * @return The value stored into \e location, i.e. \e obj */ OBJC_EXPORT id _Nullable objc_storeWeak(id _Nullable * _Nonnull location, id _Nullable obj) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); /* Associative References */ /** * Policies related to associative references. * These are options to objc_setAssociatedObject() */ typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */ OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. * The association is not made atomically. */ OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. * The association is made atomically. */ OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */ }; /** * Sets an associated value for a given object using a given key and association policy. * * @param object The source object for the association. * @param key The key for the association. * @param value The value to associate with the key key for object. Pass nil to clear an existing association. * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.” * * @see objc_setAssociatedObject * @see objc_removeAssociatedObjects */ OBJC_EXPORT void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); /** * Returns the value associated with a given object for a given key. * * @param object The source object for the association. * @param key The key for the association. * * @return The value associated with the key \e key for \e object. * * @see objc_setAssociatedObject */ OBJC_EXPORT id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); /** * Removes all associations for a given object. * * @param object An object that maintains associated objects. * * @note The main purpose of this function is to make it easy to return an object * to a "pristine state”. You should not use this function for general removal of * associations from objects, since it also removes associations that other clients * may have added to the object. Typically you should use \c objc_setAssociatedObject * with a nil value to clear an association. * * @see objc_setAssociatedObject * @see objc_getAssociatedObject */ OBJC_EXPORT void objc_removeAssociatedObjects(id _Nonnull object) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); /* Hooks for Swift */ /** * Function type for a hook that intercepts class_getImageName(). * * @param cls The class whose image name is being looked up. * @param outImageName On return, the result of the image name lookup. * @return YES if an image name for this class was found, NO otherwise. * * @see class_getImageName * @see objc_setHook_getImageName */ typedef BOOL (*objc_hook_getImageName)(Class _Nonnull cls, const char * _Nullable * _Nonnull outImageName); /** * Install a hook for class_getImageName(). * * @param newValue The hook function to install. * @param outOldValue The address of a function pointer variable. On return, * the old hook function is stored in the variable. * * @note The store to *outOldValue is thread-safe: the variable will be * updated before class_getImageName() calls your new hook to read it, * even if your new hook is called from another thread before this * setter completes. * @note The first hook in the chain is the native implementation of * class_getImageName(). Your hook should call the previous hook for * classes that you do not recognize. * * @see class_getImageName * @see objc_hook_getImageName */ OBJC_EXPORT void objc_setHook_getImageName(objc_hook_getImageName _Nonnull newValue, objc_hook_getImageName _Nullable * _Nonnull outOldValue) OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); #define _C_ID '@' #define _C_CLASS '#' #define _C_SEL ':' #define _C_CHR 'c' #define _C_UCHR 'C' #define _C_SHT 's' #define _C_USHT 'S' #define _C_INT 'i' #define _C_UINT 'I' #define _C_LNG 'l' #define _C_ULNG 'L' #define _C_LNG_LNG 'q' #define _C_ULNG_LNG 'Q' #define _C_FLT 'f' #define _C_DBL 'd' #define _C_BFLD 'b' #define _C_BOOL 'B' #define _C_VOID 'v' #define _C_UNDEF '?' #define _C_PTR '^' #define _C_CHARPTR '*' #define _C_ATOM '%' #define _C_ARY_B '[' #define _C_ARY_E ']' #define _C_UNION_B '(' #define _C_UNION_E ')' #define _C_STRUCT_B '{' #define _C_STRUCT_E '}' #define _C_VECTOR '!' #define _C_CONST 'r' /* Obsolete types */ #if !__OBJC2__ #define CLS_GETINFO(cls,infomask) ((cls)->info & (infomask)) #define CLS_SETINFO(cls,infomask) ((cls)->info |= (infomask)) // class is not a metaclass #define CLS_CLASS 0x1 // class is a metaclass #define CLS_META 0x2 // class's +initialize method has completed #define CLS_INITIALIZED 0x4 // class is posing #define CLS_POSING 0x8 // unused #define CLS_MAPPED 0x10 // class and subclasses need cache flush during image loading #define CLS_FLUSH_CACHE 0x20 // method cache should grow when full #define CLS_GROW_CACHE 0x40 // unused #define CLS_NEED_BIND 0x80 // methodLists is array of method lists #define CLS_METHOD_ARRAY 0x100 // the JavaBridge constructs classes with these markers #define CLS_JAVA_HYBRID 0x200 #define CLS_JAVA_CLASS 0x400 // thread-safe +initialize #define CLS_INITIALIZING 0x800 // bundle unloading #define CLS_FROM_BUNDLE 0x1000 // C++ ivar support #define CLS_HAS_CXX_STRUCTORS 0x2000 // Lazy method list arrays #define CLS_NO_METHOD_ARRAY 0x4000 // +load implementation #define CLS_HAS_LOAD_METHOD 0x8000 // objc_allocateClassPair API #define CLS_CONSTRUCTING 0x10000 // class compiled with bigger class structure #define CLS_EXT 0x20000 struct objc_method_description_list { int count; struct objc_method_description list[1]; }; struct objc_protocol_list { struct objc_protocol_list * _Nullable next; long count; __unsafe_unretained Protocol * _Nullable list[1]; }; struct objc_category { char * _Nonnull category_name OBJC2_UNAVAILABLE; char * _Nonnull class_name OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_ivar { char * _Nullable ivar_name OBJC2_UNAVAILABLE; char * _Nullable ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; struct objc_ivar_list { int ivar_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_method { SEL _Nonnull method_name OBJC2_UNAVAILABLE; char * _Nullable method_types OBJC2_UNAVAILABLE; IMP _Nonnull method_imp OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_method_list { struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; typedef struct objc_symtab *Symtab OBJC2_UNAVAILABLE; struct objc_symtab { unsigned long sel_ref_cnt OBJC2_UNAVAILABLE; SEL _Nonnull * _Nullable refs OBJC2_UNAVAILABLE; unsigned short cls_def_cnt OBJC2_UNAVAILABLE; unsigned short cat_def_cnt OBJC2_UNAVAILABLE; void * _Nullable defs[1] /* variable size */ OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; typedef struct objc_cache *Cache OBJC2_UNAVAILABLE; #define CACHE_BUCKET_NAME(B) ((B)->method_name) #define CACHE_BUCKET_IMP(B) ((B)->method_imp) #define CACHE_BUCKET_VALID(B) (B) #ifndef __LP64__ #define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask)) #else #define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask)) #endif struct objc_cache { unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; unsigned int occupied OBJC2_UNAVAILABLE; Method _Nullable buckets[1] OBJC2_UNAVAILABLE; }; typedef struct objc_module *Module OBJC2_UNAVAILABLE; struct objc_module { unsigned long version OBJC2_UNAVAILABLE; unsigned long size OBJC2_UNAVAILABLE; const char * _Nullable name OBJC2_UNAVAILABLE; Symtab _Nullable symtab OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; #else struct objc_method_list; #endif /* Obsolete functions */ OBJC_EXPORT IMP _Nullable class_lookupMethod(Class _Nullable cls, SEL _Nonnull sel) __OSX_DEPRECATED(10.0, 10.5, "use class_getMethodImplementation instead") __IOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead") __TVOS_DEPRECATED(9.0, 9.0, "use class_getMethodImplementation instead") __WATCHOS_DEPRECATED(1.0, 1.0, "use class_getMethodImplementation instead") __BRIDGEOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead"); OBJC_EXPORT BOOL class_respondsToMethod(Class _Nullable cls, SEL _Nonnull sel) __OSX_DEPRECATED(10.0, 10.5, "use class_respondsToSelector instead") __IOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead") __TVOS_DEPRECATED(9.0, 9.0, "use class_respondsToSelector instead") __WATCHOS_DEPRECATED(1.0, 1.0, "use class_respondsToSelector instead") __BRIDGEOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead"); OBJC_EXPORT void _objc_flush_caches(Class _Nullable cls) __OSX_DEPRECATED(10.0, 10.5, "not recommended") __IOS_DEPRECATED(2.0, 2.0, "not recommended") __TVOS_DEPRECATED(9.0, 9.0, "not recommended") __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended") __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended"); OBJC_EXPORT id _Nullable object_copyFromZone(id _Nullable anObject, size_t nBytes, void * _Nullable z) __OSX_DEPRECATED(10.0, 10.5, "use object_copy instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT id _Nullable object_realloc(id _Nullable anObject, size_t nBytes) OBJC2_UNAVAILABLE; OBJC_EXPORT id _Nullable object_reallocFromZone(id _Nullable anObject, size_t nBytes, void * _Nullable z) OBJC2_UNAVAILABLE; #define OBSOLETE_OBJC_GETCLASSES 1 OBJC_EXPORT void * _Nonnull objc_getClasses(void) OBJC2_UNAVAILABLE; OBJC_EXPORT void objc_addClass(Class _Nonnull myClass) OBJC2_UNAVAILABLE; OBJC_EXPORT void objc_setClassHandler(int (* _Nullable )(const char * _Nonnull)) OBJC2_UNAVAILABLE; OBJC_EXPORT void objc_setMultithreaded(BOOL flag) OBJC2_UNAVAILABLE; OBJC_EXPORT id _Nullable class_createInstanceFromZone(Class _Nullable, size_t idxIvars, void * _Nullable z) __OSX_DEPRECATED(10.0, 10.5, "use class_createInstance instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT void class_addMethods(Class _Nullable, struct objc_method_list * _Nonnull) OBJC2_UNAVAILABLE; OBJC_EXPORT void class_removeMethods(Class _Nullable, struct objc_method_list * _Nonnull) OBJC2_UNAVAILABLE; OBJC_EXPORT void _objc_resolve_categories_for_class(Class _Nonnull cls) OBJC2_UNAVAILABLE; OBJC_EXPORT Class _Nonnull class_poseAs(Class _Nonnull imposter, Class _Nonnull original) OBJC2_UNAVAILABLE; OBJC_EXPORT unsigned int method_getSizeOfArguments(Method _Nonnull m) OBJC2_UNAVAILABLE; OBJC_EXPORT unsigned method_getArgumentInfo(struct objc_method * _Nonnull m, int arg, const char * _Nullable * _Nonnull type, int * _Nonnull offset) UNAVAILABLE_ATTRIBUTE // This function was accidentally deleted in 10.9. OBJC2_UNAVAILABLE; OBJC_EXPORT Class _Nullable objc_getOrigClass(const char * _Nonnull name) OBJC2_UNAVAILABLE; #define OBJC_NEXT_METHOD_LIST 1 OBJC_EXPORT struct objc_method_list * _Nullable class_nextMethodList(Class _Nullable, void * _Nullable * _Nullable) OBJC2_UNAVAILABLE; // usage for nextMethodList // // void *iterator = 0; // struct objc_method_list *mlist; // while ( mlist = class_nextMethodList( cls, &iterator ) ) // ; OBJC_EXPORT id _Nullable (* _Nonnull _alloc)(Class _Nullable, size_t) OBJC2_UNAVAILABLE; OBJC_EXPORT id _Nullable (* _Nonnull _copy)(id _Nullable, size_t) OBJC2_UNAVAILABLE; OBJC_EXPORT id _Nullable (* _Nonnull _realloc)(id _Nullable, size_t) OBJC2_UNAVAILABLE; OBJC_EXPORT id _Nullable (* _Nonnull _dealloc)(id _Nullable) OBJC2_UNAVAILABLE; OBJC_EXPORT id _Nullable (* _Nonnull _zoneAlloc)(Class _Nullable, size_t, void * _Nullable) OBJC2_UNAVAILABLE; OBJC_EXPORT id _Nullable (* _Nonnull _zoneRealloc)(id _Nullable, size_t, void * _Nullable) OBJC2_UNAVAILABLE; OBJC_EXPORT id _Nullable (* _Nonnull _zoneCopy)(id _Nullable, size_t, void * _Nullable) OBJC2_UNAVAILABLE; OBJC_EXPORT void (* _Nonnull _error)(id _Nullable, const char * _Nonnull, va_list) OBJC2_UNAVAILABLE; #endif ================================================ FILE: InterView-obj-isa-class/objc4-750/unexported_symbols ================================================ .objc_class_name___IncompleteProtocol __Znam __ZnamRKSt9nothrow_t __Znwm __ZnwmRKSt9nothrow_t __ZdaPv __ZdaPvRKSt9nothrow_t __ZdlPv __ZdlPvRKSt9nothrow_t __ZTISt9bad_alloc __ZTISt9exception __ZTISt11logic_error __ZTISt12length_error __ZTSSt9bad_alloc __ZTSSt9exception __ZTSSt11logic_error __ZTSSt12length_error ================================================ FILE: InterView-obj-isa-class/objc4-750/version.bat ================================================ :: version.bat :: Writes version numbers from B&I into version.h for use by version.rc. @ECHO OFF :: Set default values for environment variables if not set by B&I IF "%OBJROOT%"=="" SET OBJROOT=. IF "%RC_PROJECTSOURCEVERSION%"=="" SET RC_PROJECTSOURCEVERSION=0.0 IF "%RC_PROJECTBUILDVERSION%"=="" SET RC_PROJECTBUILDVERSION=0 :: Get version numbers from environment variables SET major=1 SET patch=0 FOR /F "tokens=1* eol= delims=." %%i IN ("%RC_PROJECTSOURCEVERSION%") DO ( SET minor=%%i IF NOT "%%j"=="" SET patch=%%j ) SET build=%RC_PROJECTBUILDVERSION% ECHO version %major% . %minor% . %patch% . %build% :: Write version.h ECHO // This file is automatically generated by version.bat. > "%OBJROOT%\version.h" ECHO // DO NOT EDIT >> "%OBJROOT%\version.h" ECHO #define major %major% >> "%OBJROOT%\version.h" ECHO #define minor %minor% >> "%OBJROOT%\version.h" ECHO #define patch %patch% >> "%OBJROOT%\version.h" ECHO #define build %build% >> "%OBJROOT%\version.h" ECHO #define string "%major%,%minor%,%patch%,%build%" >> "%OBJROOT%\version.h" ================================================ FILE: InterView-obj-isa-class/objc4-750/version.rc ================================================ #include "Winver.h" // built by version.bat; sets variables major, minor, patch, build, string #include "version.h" VS_VERSION_INFO VERSIONINFO FILEVERSION major,minor,patch,build PRODUCTVERSION major,minor,patch,build FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Apple Inc." VALUE "FileDescription", "Objective-C Runtime Library" VALUE "FileVersion", string VALUE "ProductVersion", string VALUE "ProductName", "objc4" VALUE "InternalName", "objc4" VALUE "LegalCopyright", "Copyright (C) 2007-2009, Apple Inc." VALUE "OriginalFilename", "objc.dll" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ ![伪装成首页.jpg](https://upload-images.jianshu.io/upload_images/4563271-78c96d2cb4ee43c7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) # iOS初级到中级的进阶之路~ - 一个NSObject 对象,占用多少内存 - 对象方法 与 类方法的存放在哪 - 什么是isa指针 - 什么是meta-class - megsend 是如何找到方法的 ``` @implementation MNSubclass - (void)compareSelfWithSuperclass{ NSLog(@"self class = %@",[self class]); NSLog(@"super class = %@",[super class]); } @end ``` - 输出的结果是什么 - 。。。
### *友情tips:如果上诉问题你都知道答案,或者没有兴趣知道,就可以不用继续往下看了,兴趣是最好的老师,如果没有兴趣知道这些,往下很难读得进去~*
## 底层探究专栏 ### [isa&&Class&&meta-class 初步认识](https://minilv.github.io/2018/07/01/ias-class-metaClass) ### [isa与class详解](https://minilv.github.io/2019/03/18/isa%E8%AF%A6%E8%A7%A3-&&-class%E5%86%85%E9%83%A8%E7%BB%93%E6%9E%84/) ### [Category && 关联对象](https://minilv.github.io/2019/02/27/category/) ### [KVO && KVC 常考点](https://minilv.github.io/2018/03/27/KVO&KVC/) ### [Block看我就够了](https://minilv.github.io/2019/02/27/BlockFile/) ### [runtime消息机制](https://minilv.github.io/2019/03/17/Runtime-%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6%E5%9C%9F%E5%91%B3%E8%AE%B2%E8%A7%A3/) ### [一道高级iOS面试题(runtime方向)](https://minilv.github.io/2019/03/27/%E4%B8%80%E9%81%93%E9%AB%98%E7%BA%A7iOS%E9%9D%A2%E8%AF%95%E9%A2%98(runtime%E6%96%B9%E5%90%91)/)
## 性能优化专栏 ### [UITableView的性能优化 - 中级篇](https://minilv.github.io/2018/06/29/TableViewCellOptimization/) ## 架构专栏 ### [iOS架构入门 - MVC模式实例演示](https://minilv.github.io/2018/05/29/MVC/)
## 面试专栏 ### [iOS 初中级工程师简历指北](https://minilv.github.io/2019/05/05/iOS%E5%88%9D%E4%B8%AD%E7%BA%A7%E5%BC%80%E5%8F%91%E7%AE%80%E5%8E%86%E6%8C%87%E5%8C%97/) ### [萌新iOS面试官迷你厂第一视角](https://minilv.github.io/2019/12/29/interviewer/) ================================================ FILE: category分析.md ================================================ # 面试驱动技术 - Category 相关考点 > 面试驱动技术合集(初中级iOS开发),关注仓库,及时获取更新 [Interview-series](https://github.com/miniLV/Interview-series) ![](https://user-gold-cdn.xitu.io/2019/2/28/1693261e7f1da012?w=1050&h=700&f=jpeg&s=130669)
## I. Category
### Category相关面试题 - Category实现原理? - 实际开发中,你用Category做了哪些事? - Category能否添加成员变量,如果可以,如何添加? - load 、initialize方法的区别是什么,他们在category中的调用顺序?以及出现继承时他们之间的调用过程? - Category 和 Class Extension的区别是什么? - 为什么分类会“覆盖”宿主类的方法?

--- #### 1.Category的特点 - 运行时决议 - 通过 `runtime` 动态将分类的方法合并到类对象、元类对象中 - 实例方法合并到类对象中,类方法合并到元类对象中 - 可以为系统类添加分类
#### 2.分类中可以添加哪些内容 - 实例方法 - 类方法 - 协议 - 属性
### 分类中原理解析 使用 `xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc MNPerson+Test.m` 函数,生产一个cpp文件,窥探其底层结构(编译状态) ``` struct _category_t { //宿主类名称 - 这里的MNPerson const char *name; //宿主类对象,里面有isa struct _class_t *cls; //实例方法列表 const struct _method_list_t *instance_methods; //类方法列表 const struct _method_list_t *class_methods; //协议列表 const struct _protocol_list_t *protocols; //属性列表 const struct _prop_list_t *properties; }; //_class_t 结构 struct _class_t { struct _class_t *isa; struct _class_t *superclass; void *cache; void *vtable; struct _class_ro_t *ro; }; ``` - 每个分类都是独立的 - 每个分类的结构都一致,都是`category_t` #### 函数转换 ``` @implementation MNPerson (Test) - (void)test{ NSLog(@"test - rua~"); } @end ``` ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a2b47788e5c7) ``` static void attachCategories(Class cls, category_list *cats, bool flush_caches) { if (!cats) return; if (PrintReplacedMethods) printReplacements(cls, cats); bool isMeta = cls->isMetaClass(); // fixme rearrange to remove these intermediate allocations /* 二维数组( **mlists => 两颗星星,一个) [ [method_t,], [method_t,method_t], [method_t,method_t,method_t], ] */ method_list_t **mlists = (method_list_t **) malloc(cats->count * sizeof(*mlists)); property_list_t **proplists = (property_list_t **) malloc(cats->count * sizeof(*proplists)); protocol_list_t **protolists = (protocol_list_t **) malloc(cats->count * sizeof(*protolists)); // Count backwards through cats to get newest categories first int mcount = 0; int propcount = 0; int protocount = 0; int i = cats->count;//宿主类,分类的总数 bool fromBundle = NO; while (i--) {//倒序遍历,最先访问最后编译的分类 // 获取某一个分类 auto& entry = cats->list[i]; // 分类的方法列表 method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { //最后编译的分类,最先添加到分类数组中 mlists[mcount++] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { proplists[propcount++] = proplist; } protocol_list_t *protolist = entry.cat->protocols; if (protolist) { protolists[protocount++] = protolist; } } auto rw = cls->data(); prepareMethodLists(cls, mlists, mcount, NO, fromBundle); // 核心:将所有分类的对象方法,附加到类对象的方法列表中 rw->methods.attachLists(mlists, mcount); free(mlists); if (flush_caches && mcount > 0) flushCaches(cls); rw->properties.attachLists(proplists, propcount); free(proplists); rw->protocols.attachLists(protolists, protocount); free(protolists); } ``` ``` void attachLists(List* const * addedLists, uint32_t addedCount) { if (addedCount == 0) return; if (hasArray()) { // many lists -> many lists uint32_t oldCount = array()->count; uint32_t newCount = oldCount + addedCount; //realloc - 重新分配内存 - 扩容了 setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); array()->count = newCount; //memmove,内存挪动 //array()->lists 原来的方法列表 memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); //memcpy - 将分类的方法列表 copy 到原来的方法列表中 memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } ... } ``` 画图分析就是 ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a68348190846?w=1534&h=554&f=png&s=1437034) ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a687869587d1?w=1590&h=762&f=png&s=2050449) ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a688d74c9d22?w=1600&h=742&f=png&s=1982328) ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a68b11df0d39?w=1670&h=1224&f=png&s=3393364) ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a68d560570e4?w=1668&h=1150&f=png&s=3209517) #### 3.实际开发中,你用Category做了哪些事? - 声明私有方法 - 分解体积庞大的类文件 - - 把`Framework`的私有方法公开 - 。。。 #### 4.Category实现原理? - Category编译之后,底层结构是category_t,里面存储着分类的各种信息,包括 对象方法、类方法、属性、协议信息 - 分类的在编译后,方法并不会直接添加到类信息中,而是要在程序运行的时候,通过 `runtime`, 讲Category的数据, #### 5.为什么分类会“覆盖”宿主类的方法? - 其实不是真正的“覆盖”,宿主类的同名方法还是存在 - 分类将附加到类对象的方法列表中,整合的时候,分类的方法优先放到前面 - OC的函数调用底层走的是msg_send() 函数,它做的是方法查找,因为分类的方法优先放在前面,所以通过选择器查找到分类的方法之后直接调用,宿主类的方法看上去就像被“覆盖”而没有生效 #### 6.Category 和 Class Extension的区别是什么? #### *Class Extension(扩展)* - 声明私有属性 - 声明私有方法 - 声明私有成员变量 - 编译时决议,Category 运行时决议 - 不能为系统类添加扩展 - 只能以声明的形式存在,多数情况下,寄生于宿主类的.m文件中

## II. load 、initialize ### load实现原理 > - 类第一次加载进内存的时候,会调用 `+ load` 方法,无需导入,无需使用 > - 每个类、分类的 `+ load` 在程序运行过程中只会执行一次 > - `+ load` 走的不是消息发送的 `objc_msgSend` 调用,而是找到 `+ load` 函数的地址,直接调用 ``` void call_load_methods(void) { static bool loading = NO; bool more_categories; loadMethodLock.assertLocked(); // Re-entrant calls do nothing; the outermost call will finish the job. if (loading) return; loading = YES; void *pool = objc_autoreleasePoolPush(); do { // 1. Repeatedly call class +loads until there aren’t any more while (loadable_classes_used > 0) { //先加载宿主类的load方法(按照编译顺序,调用load方法) call_class_loads(); } // 2. Call category +loads ONCE more_categories = call_category_loads(); // 3. Run more +loads if there are classes OR more untried categories } while (loadable_classes_used > 0 || more_categories); objc_autoreleasePoolPop(pool); loading = NO; } ``` ``` static void schedule_class_load(Class cls) { if (!cls) return; assert(cls->isRealized()); // _read_images should realize if (cls->data()->flags & RW_LOADED) return; // Ensure superclass-first ordering // 递归调用,先将父类添加到load方法列表中,再将自己加进去 schedule_class_load(cls->superclass); add_class_to_loadable_list(cls); cls->setInfo(RW_LOADED); } ```
---
#### 调用顺序 1. 先调用宿主类的`+ load` 函数 - 按照编译先后顺序调用(先编译,先调用) - 调用子类的+load之前会先调用父类的+load 2. 再调用分类的的`+ load` 函数 - 按照编译先后顺序调用(先编译,先调用) 实验证明:宿主类先调用,分类再调用 ``` 2019-02-27 17:28:00.519862+0800 load-Initialize-Demo[91107:2281575] MNPerson + load 2019-02-27 17:28:00.520032+0800 load-Initialize-Demo[91107:2281575] MNPerson (Play) + load 2019-02-27 17:28:00.520047+0800 load-Initialize-Demo[91107:2281575] MNPerson (Eat) + load ``` ![](https://user-gold-cdn.xitu.io/2019/2/27/1692e5511b580ce9?w=716&h=194&f=png&s=37322) --- ``` 2019-02-27 17:39:10.354050+0800 load-Initialize-Demo[91308:2303030] MNDog + load (宿主类1) 2019-02-27 17:39:10.354237+0800 load-Initialize-Demo[91308:2303030] MNPerson + load (宿主类2) 2019-02-27 17:39:10.354252+0800 load-Initialize-Demo[91308:2303030] MNDog (Rua) + load (分类1) 2019-02-27 17:39:10.354263+0800 load-Initialize-Demo[91308:2303030] MNPerson (Play) + load(分类2) 2019-02-27 17:39:10.354274+0800 load-Initialize-Demo[91308:2303030] MNPerson (Eat) + load(分类3) 2019-02-27 17:39:10.354285+0800 load-Initialize-Demo[91308:2303030] MNDog (Run) + load(分类4) ```
#### Initialize实现原理 > - 类第一次接收到消息的时候,会调用该方法,需导入,并使用 > - `+ Initialize` 走的是消息发送的 `objc_msgSend` 调用 #### Initialize题目出现 ``` /*父类*/ @interface MNPerson : NSObject @end @implementation MNPerson + (void)initialize{ NSLog(@"MNPerson + initialize"); } @end /*子类1*/ @interface MNTeacher : MNPerson @end @implementation MNTeacher @end /*子类2*/ @interface MNStudent : MNPerson @end @implementation MNStudent @end --------------------------------------------- 问题出现:以下会输出什么结果 int main(int argc, const char * argv[]) { @autoreleasepool { [MNTeacher alloc]; [MNStudent alloc]; } return 0; } ```
---
结果如下: ``` 2019-02-27 17:57:33.305655+0800 load-Initialize-Demo[91661:2331296] MNPerson + initialize 2019-02-27 17:57:33.305950+0800 load-Initialize-Demo[91661:2331296] MNPerson + initialize 2019-02-27 17:57:33.306476+0800 load-Initialize-Demo[91661:2331296] MNPerson + initialize ``` **exo me? 为啥打印三次呢** ![](https://user-gold-cdn.xitu.io/2019/2/28/169321d806839d28?w=225&h=225&f=jpeg&s=5766) 原理分析: 1. `initialize` 在类第一次接收消息的时候会调用,OC里面的 `[ xxx ]` 调用都可以看成 `objc_msgSend`,所以这时候,`[MNTeacher alloc]` 其实内部会调用 `[MNTeacher initialize]` 2. `initialize` 调用的时候,要先实现自己父类的 `initialize` 方法,第一次调用的时候,`MNPerson` 没被使用过,所以未被初始化,要先调用一下父类的 `[MNPerson initialize]`,输出第一个`MNPerson + initialize` 3. `MNPerson` 调用了 `initialize` 之后,轮到`MNTeacher` 类自己了,由于他内部没有实现 `initialize`方法,所以调用父类的`initialize`, 输出第二个`MNPerson + initialize` 4. 然后轮到`[MNStudent alloc]`,内部也是调用 `[MNStudent initialize]`, 然后判断得知 父类`MNPerson`类调用过`initialize`了,因此调用自身的就够了,由于他和`MNTeacher` 一样,也没实现`initialize` 方法,所以同理调用父类的`[MNPerson initialize]`,输出第3个`MNPerson + initialize` ---
### initialize 与 load 的区别 - load 是类第一次加载的时候调用,initialize 是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能被调用多次) - load 和 initialize,加载or调用的时候,都会先调用父类对应的 `load` or `initialize` 方法,再调用自己本身的; - load 和 initialize 都是系统自动调用的话,都只会调用一次 - 调用方式也不一样,load 是根据函数地址直接调用,initialize 是通过`objc_msgSend` - 调用时刻,load是runtime加载类、分类的时候调用(只会调用一次) - 调用顺序: - load: - 先调用类的load - 先编译的类,优先调用load - 调用子类的load之前,会先调用父类的load - 在调用分类的load - initialize: - 先初始化父列 - 再初始化子类(可能最终调用的是父类的初始化方法) ``` /*父类*/ @interface MNPerson : NSObject @end @implementation MNPerson + (void)initialize{ NSLog(@"MNPerson + initialize"); } + (void)load{ NSLog(@"MNPerson + load"); } /*子类1*/ @interface MNTeacher : MNPerson @end @implementation MNTeacher + (void)load{ NSLog(@"MNTeacher + load"); } /*子类2*/ @interface MNStudent : MNPerson @end @implementation MNStudent + (void)load{ NSLog(@"MNStudent + load"); } ------------------------------------ 问题出现:以下会输出什么结果? int main(int argc, const char * argv[]) { @autoreleasepool { [MNTeacher load]; } return 0; } ``` #### 答案出现!!! ``` 2019-02-27 18:17:12.034392+0800 load-Initialize-Demo[92064:2370496] MNPerson + load 2019-02-27 18:17:12.034555+0800 load-Initialize-Demo[92064:2370496] MNStudent + load 2019-02-27 18:17:12.034569+0800 load-Initialize-Demo[92064:2370496] MNTeacher + load 2019-02-27 18:17:12.034627+0800 load-Initialize-Demo[92064:2370496] MNPerson + initialize 2019-02-27 18:17:12.034645+0800 load-Initialize-Demo[92064:2370496] MNPerson + initialize 2019-02-27 18:17:12.034658+0800 load-Initialize-Demo[92064:2370496] MNTeacher + load ``` exo me again!怎么这么多!连load 也有了? ![](https://user-gold-cdn.xitu.io/2019/2/28/169321e995812c15?w=225&h=225&f=jpeg&s=12623) 解释: 1. 前三个load不多bb了吧,程序一运行,runtime直接将全部的类加载到内存中,肯定最先输出; 2. 第一个 `MNPerson + initialize`,因为是`MNTeacher`的调用,所以会先让父类`MNPerson` 调用一次`initialize`,输出第一个 `MNPerson + initialize` 3. 第二个 `MNPerson + initialize`, `MNTeacher` 自身调用,由于他自己没有实现 `initialize`, 调用父类的`initialize`, 输出第二个 `MNPerson + initialize` 4. 最后一个`MNTeacher + load`可能其实有点奇怪,不是说 `load`只会加载一次吗,而且他还不走 `objc_msgSend` 吗,怎么还能调用这个方法? - 因为!当类第一次加载进内存的时候,调用的 `load` 方法是系统调的,这时候不走 `objc_msgSend` - 但是,你现在是`[MNTeacher load]`啊,这个就是objc_msgSend(MNTeacher,@selector(MNTeacher)),这就跑到`MNTeacher + load`里了! - 只是一般没人手动调用`load` 函数,但是,还是可以调用的!
## III. 关联对象AssociatedObject ### Category能否添加成员变量,如果可以,如何添加? > 这道题实际上考的就是关联对象
如果是普通类声明生命属性的话 ``` @interface MNPerson : NSObject @property (nonatomic, copy)NSString *property; @end ``` 上述代码系统内部会自动三件事: 1. 帮我们生成一个生成变量_property 2. 生成一个 `get` 方法 `- (NSString *)property` 3. 生成一个 `set` 方法 `- (void)setProperty:(NSString *)property` ``` @implementation MNPerson{ NSString *_property; } - (void)setProperty:(NSString *)property{ _property = property; } - (NSString *)property{ return _property; } @end ```
分类也是可以添加属性的 - 类结构里面,有个`properties` 列表,里面就是 存放属性的; 分类里面,生成属性,只会生成方法的声明,不会生成成员变量 && 方法实现! ![](https://user-gold-cdn.xitu.io/2019/2/27/1692f73fd3b74f8d?w=1938&h=222&f=png&s=39210) >人工智障翻译:实例变量不能放在分类中 所以: **不能直接给category 添加成员变量,但是可以间接实现分类有成员变量的效果(效果上感觉像成员变量)** ``` @interface MNPerson (Test) @property (nonatomic, assign) NSInteger age; @end @implementation MNPerson (Test) @end ``` ![](https://user-gold-cdn.xitu.io/2019/2/27/1692f88838824ba5?w=1944&h=940&f=png&s=274895) `person.age = 10`等价于 `[person setAge:10]`,所以证明了,给分类声明属性之后,并没有添加其对应的实现!
### 关联对象 objc_setAssociatedObject Api ``` objc_setAssociatedObject( <#id _Nonnull object#>, (对象) <#const void * _Nonnull key#>,(key) <#id _Nullable value#>,(关联的值) <#objc_AssociationPolicy policy#>)(关联策略) ``` 关联策略,等价于属性声明 ``` typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 }; ``` ![](https://user-gold-cdn.xitu.io/2019/2/27/1692f9182dcd9a6f?w=1614&h=998&f=png&s=2116741) 比如这里的age属性,默认声明是`@property (nonatomic, assign) NSInteger age;`,就是 assign,所以这里选择`OBJC_ASSOCIATION_ASSIGN`
取值 ``` objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>) ``` ### 面试题 - 以下代码输出的结果是啥 ``` int main(int argc, const char * argv[]) { @autoreleasepool { MNPerson *person = [[MNPerson alloc]init]; { MNPerson *test = [[MNPerson alloc]init]; objc_setAssociatedObject(person, @"test", test, OBJC_ASSOCIATION_ASSIGN); } NSLog(@"%@",objc_getAssociatedObject(person, @"test")); } return 0; } ``` ![](https://user-gold-cdn.xitu.io/2019/2/28/1692fb5b60a63547?w=2004&h=644&f=png&s=115737) >原因,关联的对象是person,关联的value是 test,test变量 出了他们的`{}` 作用域之后,就会销毁; >此时通过key 找到 对应的对象,访问对象内部的value,因为test变量已经销毁了,所以程序崩溃了,这也说明了 => **内部 test 对 value是强引用!** ### 关联对象的本质 > 在分类中,因为类的实例变量的布局已经固定,使用 @property 已经无法向固定的布局中添加新的实例变量(这样做可能会覆盖子类的实例变量),所以我们需要使用关联对象以及两个方法来模拟构成属性的三个要素。 引用自 [关联对象 AssociatedObject 完全解析](https://draveness.me/ao) --- ### 关联对象的原理 实现关联对象技术的核心对象有 - AssociationsManager - AssociationsHashMap - ObjectAssociationMap - ObjcAssociation ``` class AssociationsManager { static spinlock_t _lock;//自旋锁,保证线程安全 static AssociationsHashMap *_map; } ``` ``` class AssociationsHashMap : public unordered_map ``` ``` class ObjectAssociationMap : public std::map ``` ``` class ObjcAssociation { uintptr_t _policy; id _value; } ``` 以关联对象代码为例: ``` objc_setAssociatedObject(obj, @selector(key), @"hello world", OBJC_ASSOCIATION_COPY_NONATOMIC); ``` ![](https://user-gold-cdn.xitu.io/2019/2/28/16931f33935797e0?w=1000&h=696&f=png&s=1223486) - 关联对象并不是存储在被关联对象本身的内存中的 - 关联对象,存储在全局的一个统一的`AssociationsManager`中 - 关联对象其实就是 `ObjcAssociation` 对象,关联的 `value` 就放在 `ObjcAssociation` 内 - 关联对象由 `AssociationsManager` 管理并在 `AssociationsHashMap` 存储 - 对象的指针以及其对应 `ObjectAssociationMap` 以键值对的形式存储在 `AssociationsHashMap` 中 - `ObjectAssociationMap` 则是用于存储关联对象的数据结构 - 每一个对象都有一个标记位 `has_assoc` 指示对象是否含有关联对象 - 存储在全局的一个统一的`AssociationsManager` 内部有一持有一个`_lock`,他其实是一个spinlock_t(自旋锁),用来保证`AssociationsHashMap`操作的时候,是线程安全的
`Category` 相关的问题一般初中级问的比较多,一般最深的就问到`关联对象`,上面的问题以及解答已经把比较常见的 `Category` 的问题都罗列解决了一下,如果还有其他常见的 `Category` 的试题欢迎补充~
传言的互联网寒冬貌似真的来临了,在这种环境下,无法得知公司是否不裁员,还是让自己💪起来!19年的 **铜三铁四** 从明天就要开始拉开帷幕了,也希望近期找工作的iOS们能找到一份满意的工作,看下寒冬下,iOS开发是不是叕没人要了~

--- *本文基于 [MJ老师](https://github.com/CoderMJLee) 的基础知识之上,结合了包括 [draveness](https://github.com/draveness) 在内的一系列大神的文章总结的,如果不当之处,欢迎讨论~*
友情演出:[小马哥MJ](https://github.com/CoderMJLee) 参考资料: [关联对象 AssociatedObject 完全解析](https://draveness.me/ao) [associated-objects](https://nshipster.com/associated-objects/) ================================================ FILE: isa&&Class&&meta-class.md ================================================ ## OC对象的分类 - 实例对象(instance对象) - 类对象(class对象) - 元类对象(meta-class对象)
### instance 对象 - 通过类 `alloc` 出来的对象 - 每次 `alloc` 都会产生新的`instance` 对象(内存不相同) - `instance` 对象存储的信息 - isa 指针 - 其他成员变量
### class 对象 - 是创建对象的蓝图,描述了所创建的对象共同的属性和方法(*made in 维基百科*) - 类在内存中只有一份,每个类在内存中都有且只有一个 `class` 对象 - `class`对象在内存中存储的信息 - isa 指针 - superclass 指针 - 类的对象方法 && 协议 - 类的属性 && 成员变量信息 - 。。。
### meta-class ` Class metaClass = object_getClass([NSObject class]);` - `metaclss` 是 `NSObject`的`meta-class`对象 - `meta-class` 在内存中只有一份,每个类都有且只有一个 `meta-class` 对象 - `meta-class` 也是类,与`class`的对象结构一样,但是内部的数据不一样(用途不同) - `meta-clas` 包括: - isa指针 - superclass - 类方法 - 。。。
![image](http://upload-images.jianshu.io/upload_images/4563271-1b7e14a38f761d54?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 提问:`object_getClass` 与 `objc_getClass`的区别 ``` Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; } ``` - object_getClass : 传入的是可以是任意对象(id类型),返回的是类 or 元类 - 如果传入 `instance` 对象,返回 `class ` - 如果传入 `class`, 返回的是 `meta-class` 对象 - 如果传入的是 `meta-class`,返回的是 `root-meta-class` 对象
``` Class objc_getClass(const char *aClassName) { if (!aClassName) return Nil; // NO unconnected, YES class handler return look_up_class(aClassName, NO, YES); } ``` - 传入的是类名字符串,返回的是该类名对应的类 - 不能返回元类 ![指向图.png](http://upload-images.jianshu.io/upload_images/4563271-eb09268ab87a4671?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) (图片来自于 http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html) > 看懂这张图 - 就等价于看懂 `isa` && `superclass`了 探究流程: ``` @interface MNSuperclass : NSObject - (void)superclassInstanceMethod; + (void)superClassMethod; @end @implementation MNSuperclass - (void)superclassInstanceMethod{ NSLog(@"superclass-InstanceMethod - %p",self); } + (void)superClassMethod{ NSLog(@"+ superClass-classMethod- %p",self); } @end @interface MNSubclass : MNSuperclass - (void)subclassInstanceMethod; @end @implementation MNSubclass - (void)subclassInstanceMethod{ NSLog(@"subclassInstanceMethod- %p",self); } @end ```
#### 问: 子类调用父类的对象方法,执行的流程是如何的? ``` MNSubclass *subclass = [[MNSubclass alloc]init]; [subclass superclassInstanceMethod]; ``` - 思路: - `subclass` 调用对象方法,对象方法存在 `class` 中 - 第一步,先找到 `subclass`对象,通过 `isa` 指针,找到其对应的 `MNSubclass` 类 - 看`MNSubclass` 是否有 `superclassInstanceMethod` 方法的实现,发现没有,`MNSubclass` 沿着 `superclass` 指针找到他的父类 - `MNSuperclass` - 此时,`MNSuperclass` 中找到 `superclassInstanceMethod` 的实现,调用它,整个流程结束 ![image](http://upload-images.jianshu.io/upload_images/4563271-08adc97b80c3923e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) `[MNSubclass superClassMethod];`
#### 问: 子类调用父类的类方法,执行的流程是如何的? - 思路: - 类方法存在`meta-class`中 - 第一步,找到对应的`MNSubclass`,沿着`isa`指针,找到其对应的`meta-class` - 看`MNSubclass` 的 `meta-class` 中是否有 `superClassMethod` 方法的实现,发现没有,沿着 `superclass` 指针找到 `MNSuperclass` 的 `meta-class` - 发现 `MNSuperclass` 的 `meta-class` 有`superClassMethod` 方法实现,调用,流程结束 ![image](http://upload-images.jianshu.io/upload_images/4563271-baf53d692b8ac300?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#### 图中比较难理解的一根线 ![image](http://upload-images.jianshu.io/upload_images/4563271-5554fa08fb461169?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 探究 : 元类对象的superclass 指针是否指向 rootclass - 分析: - `meta-class` 对象存储的是类方法,`class` 存储的是 对象方法 - 从面向对象的角度来讲,一个类调用一个类方法,不应该最后调用到 对象方法 - 这里的`Root class` 就是 `NSObject`, 要给 `NSObject` 添加方法就要用到 `分类` - 验证 `NSObject` 的对象方法是否会被调用
``` //"NSObject+MNTest"类的声明 && 实现 @interface NSObject (MNTest) + (void)checkSuperclass; @end @implementation NSObject (MNTest) + (void)checkSuperclass{ NSLog(@"+NSObject checkSuperclass - %p",self); } @end //main函数中调用 int main(int argc, char * argv[]) { @autoreleasepool { [MNSubclass checkSuperclass]; NSLog(@"MNSubclass = %p",[MNSubclass class]); } return 0; } -------------------------------------------------------- 控制台输出: +NSObject checkSuperclass - 0x105817040 InterView-obj-isa-class[36303:7016608] MNSubclass = 0x105817040 ``` >1. 发现,调用`checkSuperclass` 类方法的,是`MNSubclass`类 >2. 这时候要验证上面那条 `meta-class` 指向 `root-class`的线, 这里的`root-class` 即等于 `NSObject` >3. `root-class`中只存对象方法,这里,只要验证,`NSObject` 中同名的类方法实现取消,变成同名的对象方法测试,即可得出结论 >4. 声明的还是`+ (void)checkSuperclass`,实现的方法用 `- (void)checkSuperclass`对象方法替换
``` @interface NSObject (MNTest) + (void)checkSuperclass; @end @implementation NSObject (MNTest) //+ (void)checkSuperclass{ // NSLog(@"+NSObject checkSuperclass - %p",self); //} - (void)checkSuperclass{ NSLog(@"-NSObject checkSuperclass - %p",self); } @end //main函数中调用 int main(int argc, char * argv[]) { @autoreleasepool { [MNSubclass checkSuperclass]; NSLog(@"MNSubclass = %p",[MNSubclass class]); } return 0; } -------------------------------------------------------- 控制台输出: -NSObject checkSuperclass - 0x101239040 InterView-obj-isa-class[36391:7022301] MNSubclass = 0x101239040 ``` 发现 - 调用的还是类方法 `+ (void)checkSuperclass` ,但是最终实现的,却是对象方法 `- (void)checkSuperclass` - 原因: - `[MNSubclass checkSuperclass]` 其实本质上,调用的是发送消息方法,函数类似是`objc_msgsend([MNSubclass class], @selector(checkSuperclass))` - 这里的`@selector(checkSuperclass)` 并未说明是 类方法 or 对象方法 - 所以最终走流程图的话,`root-meta-class`通过`isa`找到了`root-class` (NSObject), - `NSObject` 类不是元类,存储的是对象方法,所以 最终调用了`NSObject -checkSuperclass`这个对象方法 ![image](http://upload-images.jianshu.io/upload_images/4563271-8d5a450e2e10afd4?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 叮叮叮!循循循序渐进之面试题叒来了!! ``` @implementation MNSubclass - (void)compareSelfWithSuperclass{ NSLog(@"self class = %@",[self class]); NSLog(@"super class = %@",[super class]); } @end 调用: MNSubclass *subclass = [[MNSubclass alloc]init]; [subclass subclassInstanceMethod]; ``` - 问: `[self class]` && `[super class]` 分别输出什么 ``` @protocol NSObject - (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead"); ``` - 思路: - `class` 方法 是`NSObject` 的一个对象方法,对方方法存在 `class` 中 - 第一步,先找到 `subclass`对象,通过 `isa` 指针,找到其对应的 `MNSubclass` 类 - 看`MNSubclass` 是否有 `class` 方法的实现,发现没有,`MNSubclass` 沿着 `superclass` 指针找到他的父类 - `MNSuperclass` - 查询`MNSuperclass` 中是否有 `class` 方法的实现,发现没有,`MNSuperclass` 沿着 `superclass` 指针找到他的父类 - `NSObject` - 最终在 `NSObject` 中找到 `class` 的实现 - 而调用方都是都是当前对象,所以最后输出都是 - `MNSubclass`
#### 验证: ``` NSLog(@"self class = %@",[self class]); NSLog(@"super class = %@",[super class]); ---------------------------------------------------------------------- 控制台输出: InterView-obj-isa-class[36796:7048007] self class = MNSubclass InterView-obj-isa-class[36796:7048007] super class = MNSubclass ```

感谢[小码哥](https://github.com/CoderMJLee) 的精彩演出,文章中如果有说错的地方,欢迎提出,一起学习~

---
友情客串: [小码哥](https://github.com/CoderMJLee) [神经病院runtime入学考试](https://blog.sunnyxx.com/2014/11/06/runtime-nuts/) [gun](https://www.gnu.org/software/libc/) [www.sealiesoftware.com](http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html) ================================================ FILE: 什么是NSObject.md ================================================ # OC对象的本质 --- 我们平时编写的Objetcive-C,底层实现都是C/C++实现的 ![image](http://upload-images.jianshu.io/upload_images/4563271-21c7bda6f74b2c42?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 问 : Objetcive-C 基于 C/C++ 实现的话,Objetcive-C 对象相当于C/C++ 中的什么数据结构呢? ``` @interface MNPerson : NSObject { int _age; double _height; NSString *name; } ``` 以`MNPerson`为例,里面的成员变量有不同类型是,比如`int`、`double`、`NSString` 类型,假如在C/C++ 中用`数组`存储,显然是不太合理的 - 答: C/C++中用 `结构体` 的数据格式,表示oc对象。 ``` // 转成c/c++ 代码后,MNPerson 的结构如下 struct NSObject_IMPL { Class isa; }; struct MNPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; int _age; double _height; NSString *name; }; ``` > 使用指令 `xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc oc源文件 -o 输出的c++文件` 将 `oc` 代码转成 `c++` 代码之后,发现内部确实是结构体
--- ## 面试题来袭!前方请做好准备!! - 一个NSObject 对象,占用多少内存 - 思路: - 1. 由上面可知,`NSObject`的本质是结构体,通过`NSObject.m` 可以发现,`NSObject` 只有一个 `isa` 成员,`isa` 的本质是 `class`, `struct objc_object *` 类型,所以应该占据 8 字节 - 2.`NSLog(@"%zu",class_getInstanceSize([NSObject class]));` 输出 - size = 8 - 注意!实际上, ``` { //获得 - NSObject 一个实例对象的成员变量所占用的大小 >> 8 NSLog(@"%zu",class_getInstanceSize([NSObject class])); NSObject *obj = [[NSObject alloc]init]; // 获取 obj 指针,指向的内存大小 >> 16 NSLog(@"%zu",malloc_size((__bridge const void *)obj)); } ``` ``` //基于 `objc-class.m` 文件 750 版本 size_t class_getInstanceSize(Class cls) { if (!cls) return 0; return cls->alignedInstanceSize(); } // Class‘s ivar size rounded up to a pointer-size boundary. // 点击一下 - 智能翻译 ==> (返回类的成员变量所占据的大小) uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } ``` [opensource 源码](https://opensource.apple.com/tarballs/) ### 对象创建 - `alloc init`, 查找alloc底层实现 ``` size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; } ``` > - `CoreFoundation` 硬性规定,一个对象,至少有 16 字节 > - 以 `NSObject`为例,这里传入的 `size` 是 `alignedInstanceSize`, 而`alignedInstanceSize` 已经知道 = 8, 8 < 16,retun 16, 最终 NSObject 创建的对象,占据的内存大小是 16
### lldb 调试下,使用 `memory read` 查看对象内存 ``` (lldb) p obj (NSObject *) $0 = 0x000060000000eb90 (lldb) memory read 0x000060000000eb90 0x60000000eb90: a8 6e 3a 0b 01 00 00 00 00 00 00 00 00 00 00 00 ``` 也能发现,前8 位存储 `isa` 指针,后 8 位都是0,但是整个对象还是占据了 16 个字节
### 一个NSObject内存分配示意图 ![一个NSObject内存分配示意图](http://upload-images.jianshu.io/upload_images/4563271-0fd026ab7f8f05b3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 总结: - 问 :一个NSObject 对象,占用多少内存? - 答 : - 系统在alloc的时候,分配了16个字节给 `NSObject` 对象(`malloc_size`函数获得) - 但是实际上 `NSObject` 只使用了 8个字节的存储空间(64bit系统下) - 可以通过`class_getInstanceSize()`
### 循序渐进之面试题又来了!! ``` @interface MNStudent : NSObject { int _age; int _no; } @end ``` - 问:一个MNStudent 对象,占用多少内存 - 答: - 由上面 `NSObject`占据16个字节可知,base = 16 - 一个int占4字节,age = 4, no = 4 - 最终结果, 16 + 4 + 4 = 24! ![image](http://upload-images.jianshu.io/upload_images/4563271-03d4237d5e54005f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) *哈哈!中计了!* ![image](http://upload-images.jianshu.io/upload_images/4563271-7978dbb47c0a4286?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 原理解释: ![image](http://upload-images.jianshu.io/upload_images/4563271-4ffee21e94d8ad99?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > 1. 之前 `NSObject` 创建一个对象,确实是分配了 16 个字节的空间 > 2. 但是,他还有未使用的空间8个字节,还是可以存储的 > 3. 这里的`age` && `no` 存进去,正好 `16`,之前分配的空间正好够用!所以答案是 16!
### 循循序渐进之面试题双来了!! (大哥别打了,这次保证不挖坑了,别打,疼,别打脸别打脸。。。) ``` @interface MNPerson : NSObject { int _age; int _height; NSString *name; } ``` - 问: 一个`MNPerson`对象,占用多少内存 - 答: 这题我真的会了!上面的坑老夫已经知道了! - 默认创建的时候,分配的内容是16 - `isa` = 8, `int age` = 4, `int height` = 4, `NSString` = char * = 8 - 最终分配: 8 + 4 + 4 + 8 = 24 ![image](http://upload-images.jianshu.io/upload_images/4563271-ccfa1407c65170cc?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 哈哈哈哈! 又中计了! ![image](http://upload-images.jianshu.io/upload_images/4563271-2984c712cdb346c7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这时候你肯定好奇了 ``` uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; } ``` > - `extraBytes` 一般都是 0,这里可以理解为 `size = alignedInstanceSize()`; > - `alignedInstanceSize = class_getInstanceSize`, `class_getInstanceSize` 由上图的log信息也可以知道 = `24` > - 内心os: who tm fucking 32? ![image](http://upload-images.jianshu.io/upload_images/4563271-c7bddeced8471c44?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) [ios内存分配源码](https://opensource.apple.com/tarballs/libmalloc/) ``` 下载`libmalloc`,找到`calloc`的实现 void * calloc(size_t num_items, size_t size) { void *retval; retval = malloc_zone_calloc(default_zone, num_items, size); if (retval == NULL) { errno = ENOMEM; } return retval; } 发现这个函数,就是我们调用的`calloc`函数的底层 void * malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size) { MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0); void *ptr; if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } ptr = zone->calloc(zone, num_items, size); if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone, (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0); } MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr); return ptr; } ``` 内心os: exo me? 这传入的 `size_t size = 24`,怎么返回32的?? #### 涉及到 - 内存对齐 检索`Buckets` - (libmalloc 源码) ``` #define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */ ``` > 1. 发现,iOS 系统分配的时候,有自己的分配规则, 不是说你需要的size多大,就创建多大 > 2. 操作系统内部有自己的一套规则,这里的都是 16 的倍数,而操作系统在此基础之上,操作系统的操作访问最快 > 3. 所以,在`MNPerson` 对象需要 24 的size的时候,操作系统根据他的规则,直接创建了 32 的size的空间,所以这里的答案是 32! ![image](http://upload-images.jianshu.io/upload_images/4563271-85cdba9dd850a245?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
补充说明: `sizeof` 运算符 ``` (lldb) po [obj class] MNPerson (lldb) po sizeof(obj) 8 (lldb) po sizeof(int) 4 ``` > - `sizeof`是运算符,不是函数,编译时即知道,不是函数 > - 这里的 `obj` 是对象, *obj - 指针指向,编译器知道他是指针类型,所以 sizeof = 8(指针占据8个字节) - 特别注意,这里传入的不是对象!是指针 > - `sizeof`是告诉你传入的类型,占多少存储空间
--- *欢迎点赞fork~*
友情客串: [小码哥](https://github.com/CoderMJLee) [gun](https://www.gnu.org/software/libc/) [libmalloc](https://opensource.apple.com/tarballs/libmalloc/) [objc4](https://opensource.apple.com/tarballs/objc4/)