├── .gitignore ├── README.md ├── libclang experiments.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── vishnu.xcuserdatad │ └── xcschemes │ └── libclang experiments.xcscheme └── libclang experiments └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | llvm/ 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Reposted from my NUS Hacker [digest entry](http://digest.nushackers.org/2014/11/02/libclang-ast-parsing/) 2 | This post references [this](https://github.com/burnflare/libclang-experiments/) Github project. 3 | # Auto Code Injection with libclang 4 | ## Motivation 5 | I've always been fascinated by IDEs. Long have I wondered how do they what they do: syntax highlighting, code completion, method refactoring and so much more. Recently, I had a bunch of time on my hands and I decided to figure out how an IDE works its magic. I chose to play around with Xcode because that's my favourite IDE. 6 | 7 | Here's the challenge I presented to myself: given any typical modern iOS project, use the IDE's AST (Abstract Syntax Tree) parsing tools to insert a bunch of code into a predetermined method. In this example, we'll add code to an iOS app's `application:didFinishLaunchingWithOptions` since we can almost always guarantee that this method would exist. So I would like to turn this: 8 | ```Objective-C 9 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 10 | // Override point for customization after application launch. 11 | return YES; 12 | } 13 | ``` 14 | into: 15 | ```Objective-C 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 17 | // Override point for customization after application launch. 18 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 19 | if (![defaults objectForKey:@"firstRun"]) 20 | [defaults setObject:[NSDate date] forKey:@"firstRun"]; 21 | // First run! 22 | } else { 23 | // Not first run! 24 | } 25 | } 26 | ``` 27 | First things first, I was pretty confident that Xcode was relying on some extra framework/tool to get its magic done but I was not sure what it was. I tried `spindump` and `iosnoop` on the Xcode process but that didn't reveal anything interesting. Then I tried to sample the Xcode process by running `sample Xcode` in the Terminal. On top of showing all current call stacks of the specified process, `sample` also lists out all the binary images (Frameworks, Static and dynamic libraries) that Xcode has loaded or linked to. Most of the images here were uninteresting but one of them caught my attention: 28 | 29 | 0x103002000 - 0x103a94fff +libclang.dylib (600.0.54) <21EB2141-3192-33E4-8641-8CD0F9DA0B20> /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib 30 | 31 | Further googling revealed that libclang was exactly what I was looking for. The LLVM project trivially describes [libclang](http://clang.llvm.org/doxygen/group__CINDEX.html) as "a stable high level C interface to clang". If you don't already know, Clang is modern compiler for C, C++ and Objective-C that uses LLVM as it's backend. The Clang project was originally started in Apple as a modern replacement to the 25 year old, very-much-hacked, recursively named, GNU Compiler Collection (GCC). Clang is also now matured enough to be the primary compiler used for all iOS/Mac apps for the past few years. And libclang seemed like a way to 'talk' to Clang. Perfect, exactly what I wanted. 32 | 33 | Unfortunately, libclang isn't very easy to use for someone who has no experience with Clang APIs. Its website is just a simple doxygen page with no instructions or sample code. Unable to find sample code anywhere on the internet, it was a painful, frustrating process and I made a lot of mistakes all over the place trying to get libclang working. This post aims to save you time and a bunch of mistakes I made while trying to tame down libclang. And I'll try to explain some stuff along the way. 34 | 35 | Alright, let's begin the tutorial! 36 | 37 | ## Clone repo 38 | Let's clone the repo 39 | ``` 40 | git clone https://github.com/burnflare/libclang-experiments.git 41 | cd libclang-experiments 42 | ``` 43 | Although Xcode comes with a precompiled version of libclang built-in, we still need to get our headers from the Clang project (Try to make sure you're following the same directory structure as I am here) 44 | ``` 45 | git clone http://llvm.org/git/llvm.git 46 | cd llvm/tools 47 | git clone http://llvm.org/git/clang.git 48 | ``` 49 | ## Configure Xcode 50 | Now, let's verify that the `libclang-experiments` Xcode project is in a valid state, ensuring that it's linked to all the right binaries and header paths. If you're trying to get libclang working on your own project, you should reproduce the steps mentioned in this section. 51 | 52 | In the project navigator, click on your project, then click on *Build Phases* in the main window. Expand the *Link Binary with Libraries* disclosure, click on the *+* and choose *Add Other...*. Thankfully, we don't have to build our own version of libclang.dylib (I've spend hours doing that) as Xcode comes bundled with one. We can link directly against that! Hit ⌘⇧G and paste this in and click *Open* 53 | `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib` 54 | 55 | Next, move on to the *Build Settings* section and do the following: 56 | 57 | - Add a new Runpath Search Paths: `$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib` 58 | - `libclang.dylib` relies heavily on other libraries and its complete paths are not known on compile time. It relies on the runtime's dynamic loader to find these libraries so we'll have to provide it with an additional path to search through. 59 | - `$(DEVELOPER_DIR)` is an Xcode variable that points to `/Applications/Xcode.app/Contents/Developer` or wherever Xcode is installed. 60 | - Add a new Header Search Paths: `$(SRCROOT)/llvm/tools/clang/include` (Resursive) 61 | - We checked-out LLVM&Clang so that we could use some of its headers, so let's point to the ones we care about 62 | - `$(SRCROOT)` is a Xcode variable that points to the root of this project. For me, that's `/Users/vishnu/dev/libclang-experiments`. Obviously, Your Roots May Vary (YRMV). 63 | - Add a new Library Search Paths: `$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib` 64 | - Even though we've 'added' `libclang.dylib` into our Xcode's project navigator, we still need to tell the compiler to look for dynamic libraries in that search path or else it won't find it. 65 | - Enable Modules (C and Objective-C) - Set this to No. 66 | 67 | ## Explaining source code 68 | The original draft of this project was written in minimal C and mostly Objective-C. I have an allergy to C, the language (it gives me the shivers). But after some deliberation, I decided to refactor the entire app in C as going back and forth between C and Obj-C types just added more muck to the code for little benefit. And C's not ***that*** bad :P 69 | 70 | ```C 71 | // 72 | // main.c 73 | // libclang experiments 74 | // 75 | // Created by Vishnu Prem on 3/11/14. 76 | // Copyright (c) 2014 Vishnu Prem. All rights reserved. 77 | // 78 | 79 | #include 80 | #include "string.h" 81 | #include "clang-c/Index.h" 82 | ``` 83 | 84 | Importing the header `clang-c/Index.h` that lives in our LLVM project that we checked out. This header recursively includes everything else we would need to play with libclang 85 | ``` 86 | const char * args[] = { "-c", "-arch", "i386", 87 | "-isysroot", "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk", 88 | "-I", "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/6.0/include", 89 | "-Wno-objc-property-implementation"}; 90 | ``` 91 | Clang loves to eat all the arguments for breakfast, lunch and dinner. If you want to have fun, [take a look](https://www.dropbox.com/s/zls6pdfhrsuxiqa/Screenshot%202014-11-03%2011.27.09.png?dl=0) at the default list of arguments Xcode sends Clang whenever it tries to build. Have fun understanding that! 92 | 93 | I tried to be as minimal as possible with my Clang arguments. Tried a bunch of permutations with all kinds of stuff and and this is what I ended up with: 94 | 95 | - `-c`: Expect C, the language. 96 | - `-arch i386`: Expect architecture x86. 97 | - `isysroot `: Root directory for the compiler. Usually you want this to point to the root SDK you want to link against. I'm using the iPhoneSumulator SDK here since we're running this on a x86 CPU. 98 | - `-I `: Add the path to the compiler's include search path. 99 | - `-Wno-objc-property-implementation`: Suppressing a frequent warning that shows up while compiling some of Apple's iOS8 headers. 100 | 101 | Fun, right? Ok moving on! 102 | ```C++ 103 | CXTranslationUnit translationUnit; 104 | ``` 105 | 106 | A CXIndex consists of multiple Translation Units. A single translation unit is typically used to represent a single source file. I'm defining the translation unit globally here so that methods outside `main()` can use it. 107 | ```C++ 108 | void m_indexDeclaration(CXClientData client_data, const CXIdxDeclInfo *declaration); 109 | static IndexerCallbacks indexerCallbacks = { 110 | .indexDeclaration = m_indexDeclaration, 111 | }; 112 | ``` 113 | When we get libclang to parse through our source file, we can implement various callbacks that will be triggered. In this project, we only care about the `IndexerCallbacks.indexDeclaration` callback. It's also possible to implement a callback whenever the preprocessor includes a file, more on the other IndexerCallbacks callbacks [here](http://clang.llvm.org/doxygen/structIndexerCallbacks.html) 114 | ```C++ 115 | const char *methodToFind = "application:didFinishLaunchingWithOptions:"; 116 | const char *injectCode = "NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];\n\tif (![defaults objectForKey:@\"firstRun\"])\n\t\t[defaults setObject:[NSDate date] forKey:@\"firstRun\"];\n\t\t// First run!\n\t} else {\n\t\t// Not first run!\n\t}\n\t"; 117 | ``` 118 | `methodToFind` is the signature of the method we're looking for. 119 | `injectCode` is the escaped, nicely formatted code snippet we're trying to inject into our `AppDelegate.m`. 120 | ```C++ 121 | int main(int argc, const char * argv[]) { 122 | CXIndex index = clang_createIndex(1, 1); 123 | 124 | const char *sourceFile = "/Users/vishnu/Desktop/FlappyCode/FlappyCode/AppDelegate.m"; 125 | 126 | if (!index) { 127 | printf("Couldn't create CXIndex"); 128 | return 0; 129 | } 130 | ``` 131 | Initialize an empty CXIndex, to get things started. You will notice here that I've decided to hardcode the path to my `AppDelegate.m`, but a better programmer would choose to retrieve this from `argv[]` or user input. 132 | ```C++ 133 | translationUnit = clang_parseTranslationUnit(index, 134 | sourceFile, 135 | args, 136 | sizeof(args) / sizeof(args[0]), 137 | NULL, 138 | 0, 139 | CXTranslationUnit_None); 140 | 141 | if (!translationUnit) { 142 | printf("Couldn't create CXTranslationUnit of %s", sourceFile); 143 | return 0; 144 | } 145 | ``` 146 | Here, we are initialising the single translation unit we'll be using for our project. The first four arguments passed in are the CXIndex, path to source file, Clang arguments array and size of that array respectively. The fifth & sixth argument is used to send files to libclang that have not been saved to disk yet. I'm guessing IDEs (like Xcode) use this to get syntax highighting for code as you're typing on the fly. The last parameter is used to send in [special options](http://clang.llvm.org/doxygen/group__CINDEX__TRANSLATION__UNIT.html#gab1e4965c1ebe8e41d71e90203a723fe9) for the parser. An interesting option I found here was `CXTranslationUnit_Incomplete` which would tell the parser that we're working with an intentionally incomplete translation unit here. Proceed proudly! 147 | ```C++ 148 | CXIndexAction action = clang_IndexAction_create(index); 149 | clang_indexTranslationUnit(action, NULL, &indexerCallbacks, sizeof(indexerCallbacks), CXIndexOpt_SuppressWarnings, translationUnit); 150 | 151 | clang_disposeIndex(index); 152 | clang_disposeTranslationUnit(translationUnit); 153 | clang_IndexAction_dispose(action); 154 | return 0; 155 | } 156 | ``` 157 | Now that we have a translation unit representing our source code, we'll be getting libclang to run an index through it, triggering our above-mentioned callback methods when necessary. CXIndexAction is used to represent an indexing session. 158 | 159 | libclang comes with plenty of convenience methods to clean up memory after ourselves, so you can go ahead and destroy those CXStuff when you're doing using them. 160 | ```C++ 161 | void m_indexDeclaration(CXClientData client_data, const CXIdxDeclInfo *declaration) { 162 | if (declaration->cursor.kind == CXCursor_ObjCInstanceMethodDecl) { 163 | if (strcmp(declaration->entityInfo->name, methodToFind) == 0) { 164 | ``` 165 | From my (weak) understanding, libclang's indexer calls `m_indexDeclaration()` whenever a new declaration has been discovered serially in the build process. This includes all of the languages' primitive declarations, Darwin related declarations, Obj-C language declarations, CoreGraphics declarations, SDK declarations and more. Running a counter shows that this method is invoked over 21,000 times. 166 | 167 | Thankfully, there's an easy way to filter to only the kinds of declarations we care about. In this case, we only care about Objective-C instance method so let's filter that out. You can see the entire list of declaration types [here](http://clang.llvm.org/doxygen/group__CINDEX.html#gaaccc432245b4cd9f2d470913f9ef0013) 168 | 169 | Once we know that it's a Obj-C instance method, we can do a simple `strcmp()` to confirm that its method signature is the one that we care about. 170 | ```C++ 171 | CXToken *tokens; 172 | unsigned int numTokens; 173 | CXCursor *cursors = 0; 174 | 175 | CXSourceRange range = clang_getCursorExtent(declaration->cursor); 176 | clang_tokenize(translationUnit, range, &tokens, &numTokens); 177 | cursors = (CXCursor *)malloc(numTokens * sizeof(CXCursor)); 178 | clang_annotateTokens(translationUnit, tokens, numTokens, cursors); 179 | ``` 180 | `CXToken`: A token is used to hold a single *token* of source code in its simplest form. For example, the following code sequence: 181 | ```Objective-C 182 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 183 | // Override point for customization after application launch. 184 | return YES; 185 | } 186 | ``` 187 | would be tokenized into the array: 188 | 189 | [01] - - 190 | [02] - ( 191 | [03] - BOOL 192 | [04] - ) 193 | [05] - application 194 | [06] - : 195 | [07] - ( 196 | [08] - UIApplication 197 | [09] - * 198 | [10] - ) 199 | [11] - application 200 | [12] - didFinishLaunchingWithOptions 201 | [13] - : 202 | [14] - ( 203 | [15] - NSDictionary 204 | [16] - * 205 | [17] - ) 206 | [18] - launchOptions 207 | [19] - { 208 | [20] - // Override point for customization after application launch. 209 | [21] - return 210 | [22] - YES 211 | [23] - ; 212 | [24] - } 213 | 214 | There are 5 possible kinds of CXTokens: Punctuation, Keyword, Identifier, Literal and Comment. You can retrieve their kinds using `clang_getTokenKind()`. If you want to print out the exact code the token represents, use `clang_getTokenSpelling()` 215 | 216 | `CXCursor`: A CXCursor contains a cursor representation of an element in the AST. A cursor can be used for many uses, but we will be using it specifically to retrieve a CXToken's exact position (line & offset) in a source file. We will be using `clang_annotateTokens()` to map each CXToken in the tokens array to its respective cursors arrays for future cursor manipulation. 217 | 218 | `clang_getCursorExtent` returns the physical boundaries of the source represented by that cursor. In this example, it returns a CXSourceRange that represents the entire `application:didFinishLaunchingWithOptions:` method from the first `'` character to the last `}`. 219 | ```C++ 220 | int next = 0; 221 | for(int i=0; i < numTokens; i++) { 222 | if (next == 0) { 223 | CXTokenKind tKind = clang_getTokenKind(tokens[i]); 224 | CXString tString = clang_getTokenSpelling(translationUnit, tokens[i]); 225 | const char *cString = clang_getCString(tString); 226 | if (tKind == CXToken_Punctuation && strcmp(cString, "{") == 0) { 227 | next = 1; 228 | continue; 229 | } 230 | } 231 | ``` 232 | Using a loop to run through all the initial tokens in the range until we meet a `{`. We want to insert our injection code into the token right after the `{` token, so I'm using a `next` bool here to keep state of that. 233 | ```C++ 234 | else { 235 | CXFile file; 236 | unsigned line; 237 | unsigned offset; 238 | 239 | clang_getSpellingLocation(clang_getCursorLocation(cursors[i+1]), 240 | &file, 241 | &line, 242 | NULL, 243 | &offset); 244 | 245 | const char* filename = clang_getCString(clang_getFileName(file)); 246 | printf("\n\nMethod found in %s in line %d, offset %d\n", clang_getCString(clang_getFileName(file)), line, offset); 247 | ``` 248 | Now we know where we want to insert our code at. Let's extract out the file name and offset out from the cursor. 249 | ```C++ 250 | // File reader 251 | FILE *fr = fopen(filename, "r"); 252 | fseek(fr, 0, SEEK_END); 253 | long fsize = ftell(fr); 254 | fseek(fr, 0, SEEK_SET); 255 | 256 | // Reading file to string 257 | char *input = malloc(fsize); 258 | fread(input, fsize, 1, fr); 259 | fclose(fr); 260 | 261 | // Making an output that is input(start till offset) + code injection + input(offset till end) 262 | FILE *fw = fopen(filename, "w"); 263 | char *output = malloc(fsize); 264 | strncpy(output, input, offset); 265 | strcat(output, injectCode); 266 | strcat(output, input+offset); 267 | 268 | // Rewrite the whole file with output string 269 | fwrite(output, fsize, sizeof(output), fw); 270 | fclose(fw); 271 | ``` 272 | Code to rewrite the source file with the new code in between the cursor point. 273 | ```C++ 274 | clang_disposeTokens(translationUnit, tokens, numTokens); 275 | break; 276 | } 277 | } 278 | } 279 | } 280 | } 281 | ``` 282 | ## Flaws & Improvements 283 | This is just my very first attempt in trying to demystify libclang. I've probably just covered 2% of libclang's API and there's so much more it can do. And I've probably made a lot of trivial mistakes in my methodology too. 284 | 285 | - Right now, this project requires you to manually point to an `AppDelegate.m` file. It would be much cooler if we could just point to a project folder and this tool would do the rest. Pretty sure it's quite doable by parsing through Xcode's .xcproject file and looking for a main.m file then discovering an `AppDelegate` file from there. 286 | - Right now, after finding out which token I want to insert myself into, I'm using C's ugly `fopen` and `fwrite` APIs to actual do the code insertion for me. I'm pretty sure a competent AST parser like libclang would have the ability for me to programatically create CXTokens and append them into my CXTranslationUnit and get the parser to generate the source file for me. I'm sure this is possible, but I've not discovered it yet, so please tell me if you do! 287 | - I think Xcode's code completion functionality works [through libclang](http://clang.llvm.org/doxygen/group__CINDEX__CODE__COMPLET.html) too, it might be interested to work with those set of APIs next. 288 | 289 | ## Conclusion 290 | It was very exciting trying to pry open Xcode and look at how its refactoring and code completion tools work. Given some time, I might be able to build my own IDE too, wrapped around libclang. 291 | 292 | If you have any thoughts, comments or improvements, feel free to shout at me on [Twitter](http://twitter.com/burnflare), email me at vishnu [at] nushackers [dot] org or create an [issue](https://github.com/burnflare/libclang-experiments/issues/new) on the [Github](https://github.com/burnflare/libclang-experiments/) repo. 293 | 294 | We live in exciting times. 295 | -------------------------------------------------------------------------------- /libclang experiments.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 27E3C7591A06FC3E008EFBF6 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 27E3C7581A06FC3E008EFBF6 /* main.c */; }; 11 | 27E3C7601A06FCA0008EFBF6 /* libclang.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E3C75F1A06FCA0008EFBF6 /* libclang.dylib */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | 27E3C7531A06FC3D008EFBF6 /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = /usr/share/man/man1/; 19 | dstSubfolderSpec = 0; 20 | files = ( 21 | ); 22 | runOnlyForDeploymentPostprocessing = 1; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 27E3C7551A06FC3D008EFBF6 /* libclang experiments */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "libclang experiments"; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 27E3C7581A06FC3E008EFBF6 /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = ""; }; 29 | 27E3C75F1A06FCA0008EFBF6 /* libclang.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libclang.dylib; path = Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib; sourceTree = DEVELOPER_DIR; }; 30 | /* End PBXFileReference section */ 31 | 32 | /* Begin PBXFrameworksBuildPhase section */ 33 | 27E3C7521A06FC3D008EFBF6 /* Frameworks */ = { 34 | isa = PBXFrameworksBuildPhase; 35 | buildActionMask = 2147483647; 36 | files = ( 37 | 27E3C7601A06FCA0008EFBF6 /* libclang.dylib in Frameworks */, 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 27E3C74C1A06FC3D008EFBF6 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 27E3C75F1A06FCA0008EFBF6 /* libclang.dylib */, 48 | 27E3C7571A06FC3D008EFBF6 /* libclang experiments */, 49 | 27E3C7561A06FC3D008EFBF6 /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 27E3C7561A06FC3D008EFBF6 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 27E3C7551A06FC3D008EFBF6 /* libclang experiments */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 27E3C7571A06FC3D008EFBF6 /* libclang experiments */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 27E3C7581A06FC3E008EFBF6 /* main.c */, 65 | ); 66 | path = "libclang experiments"; 67 | sourceTree = ""; 68 | }; 69 | /* End PBXGroup section */ 70 | 71 | /* Begin PBXNativeTarget section */ 72 | 27E3C7541A06FC3D008EFBF6 /* libclang experiments */ = { 73 | isa = PBXNativeTarget; 74 | buildConfigurationList = 27E3C75C1A06FC3E008EFBF6 /* Build configuration list for PBXNativeTarget "libclang experiments" */; 75 | buildPhases = ( 76 | 27E3C7511A06FC3D008EFBF6 /* Sources */, 77 | 27E3C7521A06FC3D008EFBF6 /* Frameworks */, 78 | 27E3C7531A06FC3D008EFBF6 /* CopyFiles */, 79 | ); 80 | buildRules = ( 81 | ); 82 | dependencies = ( 83 | ); 84 | name = "libclang experiments"; 85 | productName = "libclang experiments"; 86 | productReference = 27E3C7551A06FC3D008EFBF6 /* libclang experiments */; 87 | productType = "com.apple.product-type.tool"; 88 | }; 89 | /* End PBXNativeTarget section */ 90 | 91 | /* Begin PBXProject section */ 92 | 27E3C74D1A06FC3D008EFBF6 /* Project object */ = { 93 | isa = PBXProject; 94 | attributes = { 95 | LastUpgradeCheck = 0610; 96 | ORGANIZATIONNAME = "Vishnu Prem"; 97 | TargetAttributes = { 98 | 27E3C7541A06FC3D008EFBF6 = { 99 | CreatedOnToolsVersion = 6.1; 100 | }; 101 | }; 102 | }; 103 | buildConfigurationList = 27E3C7501A06FC3D008EFBF6 /* Build configuration list for PBXProject "libclang experiments" */; 104 | compatibilityVersion = "Xcode 3.2"; 105 | developmentRegion = English; 106 | hasScannedForEncodings = 0; 107 | knownRegions = ( 108 | en, 109 | ); 110 | mainGroup = 27E3C74C1A06FC3D008EFBF6; 111 | productRefGroup = 27E3C7561A06FC3D008EFBF6 /* Products */; 112 | projectDirPath = ""; 113 | projectRoot = ""; 114 | targets = ( 115 | 27E3C7541A06FC3D008EFBF6 /* libclang experiments */, 116 | ); 117 | }; 118 | /* End PBXProject section */ 119 | 120 | /* Begin PBXSourcesBuildPhase section */ 121 | 27E3C7511A06FC3D008EFBF6 /* Sources */ = { 122 | isa = PBXSourcesBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | 27E3C7591A06FC3E008EFBF6 /* main.c in Sources */, 126 | ); 127 | runOnlyForDeploymentPostprocessing = 0; 128 | }; 129 | /* End PBXSourcesBuildPhase section */ 130 | 131 | /* Begin XCBuildConfiguration section */ 132 | 27E3C75A1A06FC3E008EFBF6 /* Debug */ = { 133 | isa = XCBuildConfiguration; 134 | buildSettings = { 135 | ALWAYS_SEARCH_USER_PATHS = NO; 136 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 137 | CLANG_CXX_LIBRARY = "libc++"; 138 | CLANG_ENABLE_MODULES = YES; 139 | CLANG_ENABLE_OBJC_ARC = YES; 140 | CLANG_WARN_BOOL_CONVERSION = YES; 141 | CLANG_WARN_CONSTANT_CONVERSION = YES; 142 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 143 | CLANG_WARN_EMPTY_BODY = YES; 144 | CLANG_WARN_ENUM_CONVERSION = YES; 145 | CLANG_WARN_INT_CONVERSION = YES; 146 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 147 | CLANG_WARN_UNREACHABLE_CODE = YES; 148 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 149 | COPY_PHASE_STRIP = NO; 150 | ENABLE_STRICT_OBJC_MSGSEND = YES; 151 | GCC_C_LANGUAGE_STANDARD = gnu99; 152 | GCC_DYNAMIC_NO_PIC = NO; 153 | GCC_OPTIMIZATION_LEVEL = 0; 154 | GCC_PREPROCESSOR_DEFINITIONS = ( 155 | "DEBUG=1", 156 | "$(inherited)", 157 | ); 158 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 159 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 160 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 161 | GCC_WARN_UNDECLARED_SELECTOR = YES; 162 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 163 | GCC_WARN_UNUSED_FUNCTION = YES; 164 | GCC_WARN_UNUSED_VARIABLE = YES; 165 | MACOSX_DEPLOYMENT_TARGET = 10.10; 166 | MTL_ENABLE_DEBUG_INFO = YES; 167 | ONLY_ACTIVE_ARCH = YES; 168 | SDKROOT = macosx; 169 | }; 170 | name = Debug; 171 | }; 172 | 27E3C75B1A06FC3E008EFBF6 /* Release */ = { 173 | isa = XCBuildConfiguration; 174 | buildSettings = { 175 | ALWAYS_SEARCH_USER_PATHS = NO; 176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 177 | CLANG_CXX_LIBRARY = "libc++"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_CONSTANT_CONVERSION = YES; 182 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 183 | CLANG_WARN_EMPTY_BODY = YES; 184 | CLANG_WARN_ENUM_CONVERSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 187 | CLANG_WARN_UNREACHABLE_CODE = YES; 188 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 189 | COPY_PHASE_STRIP = YES; 190 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 191 | ENABLE_NS_ASSERTIONS = NO; 192 | ENABLE_STRICT_OBJC_MSGSEND = YES; 193 | GCC_C_LANGUAGE_STANDARD = gnu99; 194 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 195 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 196 | GCC_WARN_UNDECLARED_SELECTOR = YES; 197 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 198 | GCC_WARN_UNUSED_FUNCTION = YES; 199 | GCC_WARN_UNUSED_VARIABLE = YES; 200 | MACOSX_DEPLOYMENT_TARGET = 10.10; 201 | MTL_ENABLE_DEBUG_INFO = NO; 202 | SDKROOT = macosx; 203 | }; 204 | name = Release; 205 | }; 206 | 27E3C75D1A06FC3E008EFBF6 /* Debug */ = { 207 | isa = XCBuildConfiguration; 208 | buildSettings = { 209 | CLANG_ENABLE_MODULES = NO; 210 | HEADER_SEARCH_PATHS = ( 211 | "$(inherited)", 212 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 213 | "$(SRCROOT)/llvm/tools/clang/include/**", 214 | ); 215 | LD_RUNPATH_SEARCH_PATHS = "$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib"; 216 | LIBRARY_SEARCH_PATHS = ( 217 | "$(inherited)", 218 | "$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib", 219 | ); 220 | PRODUCT_NAME = "$(TARGET_NAME)"; 221 | }; 222 | name = Debug; 223 | }; 224 | 27E3C75E1A06FC3E008EFBF6 /* Release */ = { 225 | isa = XCBuildConfiguration; 226 | buildSettings = { 227 | CLANG_ENABLE_MODULES = NO; 228 | HEADER_SEARCH_PATHS = ( 229 | "$(inherited)", 230 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 231 | "$(SRCROOT)/llvm/tools/clang/include/**", 232 | ); 233 | LD_RUNPATH_SEARCH_PATHS = "$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib"; 234 | LIBRARY_SEARCH_PATHS = ( 235 | "$(inherited)", 236 | "$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib", 237 | ); 238 | PRODUCT_NAME = "$(TARGET_NAME)"; 239 | }; 240 | name = Release; 241 | }; 242 | /* End XCBuildConfiguration section */ 243 | 244 | /* Begin XCConfigurationList section */ 245 | 27E3C7501A06FC3D008EFBF6 /* Build configuration list for PBXProject "libclang experiments" */ = { 246 | isa = XCConfigurationList; 247 | buildConfigurations = ( 248 | 27E3C75A1A06FC3E008EFBF6 /* Debug */, 249 | 27E3C75B1A06FC3E008EFBF6 /* Release */, 250 | ); 251 | defaultConfigurationIsVisible = 0; 252 | defaultConfigurationName = Release; 253 | }; 254 | 27E3C75C1A06FC3E008EFBF6 /* Build configuration list for PBXNativeTarget "libclang experiments" */ = { 255 | isa = XCConfigurationList; 256 | buildConfigurations = ( 257 | 27E3C75D1A06FC3E008EFBF6 /* Debug */, 258 | 27E3C75E1A06FC3E008EFBF6 /* Release */, 259 | ); 260 | defaultConfigurationIsVisible = 0; 261 | }; 262 | /* End XCConfigurationList section */ 263 | }; 264 | rootObject = 27E3C74D1A06FC3D008EFBF6 /* Project object */; 265 | } 266 | -------------------------------------------------------------------------------- /libclang experiments.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /libclang experiments.xcodeproj/xcuserdata/vishnu.xcuserdatad/xcschemes/libclang experiments.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /libclang experiments/main.c: -------------------------------------------------------------------------------- 1 | // 2 | // main.c 3 | // libclang experiments 4 | // 5 | // Created by Vishnu Prem on 3/11/14. 6 | // Copyright (c) 2014 Vishnu Prem. All rights reserved. 7 | // 8 | 9 | #include 10 | #include "string.h" 11 | #include "stdlib.h" 12 | #include "clang-c/Index.h" 13 | 14 | const char * args[] = { "-c", "-arch", "i386", 15 | "-isysroot", "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk", 16 | "-I", "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/6.0/include", 17 | "-Wno-objc-property-implementation", "-Wno-all"}; 18 | 19 | CXTranslationUnit translationUnit; 20 | 21 | void m_indexDeclaration(CXClientData client_data, const CXIdxDeclInfo *declaration); 22 | 23 | static IndexerCallbacks indexerCallbacks = { 24 | .indexDeclaration = m_indexDeclaration, 25 | }; 26 | 27 | const char *methodToFind = "application:didFinishLaunchingWithOptions:"; 28 | const char *injectCode = "NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];\n\tif (![defaults objectForKey:@\"firstRun\"]){\n\t\t[defaults setObject:[NSDate date] forKey:@\"firstRun\"];\n\t\t// First run!\n\t} else {\n\t\t// Not first run!\n\t}\n\t"; 29 | 30 | int main(int argc, const char * argv[]) { 31 | CXIndex index = clang_createIndex(1, 1); 32 | 33 | const char *sourceFile = "/Users/vishnu/dev/FlappyCode/FlappyCode/AppDelegate.m"; 34 | 35 | if (!index) { 36 | printf("Couldn't create CXIndex"); 37 | return 0; 38 | } 39 | translationUnit = clang_parseTranslationUnit(index, 40 | sourceFile, 41 | args, 42 | sizeof(args) / sizeof(args[0]), 43 | NULL, 44 | 0, 45 | CXTranslationUnit_None); 46 | 47 | if (!translationUnit) { 48 | printf("Couldn't create CXTranslationUnit of %s", sourceFile); 49 | return 0; 50 | } 51 | 52 | CXIndexAction action = clang_IndexAction_create(index); 53 | clang_indexTranslationUnit(action, NULL, &indexerCallbacks, sizeof(indexerCallbacks), CXIndexOpt_SuppressWarnings, translationUnit); 54 | 55 | clang_disposeIndex(index); 56 | clang_disposeTranslationUnit(translationUnit); 57 | clang_IndexAction_dispose(action); 58 | return 0; 59 | } 60 | 61 | void m_indexDeclaration(CXClientData client_data, const CXIdxDeclInfo *declaration) { 62 | if (declaration->cursor.kind == CXCursor_ObjCInstanceMethodDecl) { 63 | if (strcmp(declaration->entityInfo->name, methodToFind) == 0) { 64 | CXToken *tokens; 65 | unsigned int numTokens; 66 | CXCursor *cursors = 0; 67 | 68 | CXSourceRange range = clang_getCursorExtent(declaration->cursor); 69 | clang_tokenize(translationUnit, range, &tokens, &numTokens); 70 | cursors = (CXCursor *)malloc(numTokens * sizeof(CXCursor)); 71 | clang_annotateTokens(translationUnit, tokens, numTokens, cursors); 72 | 73 | int next = 0; 74 | for(int i=0; i < numTokens; i++) { 75 | if (next == 0) { 76 | CXTokenKind tKind = clang_getTokenKind(tokens[i]); 77 | CXString tString = clang_getTokenSpelling(translationUnit, tokens[i]); 78 | const char *cString = clang_getCString(tString); 79 | if (tKind == CXToken_Punctuation && strcmp(cString, "{") == 0) { 80 | next = 1; 81 | continue; 82 | } 83 | clang_disposeString(tString); 84 | } 85 | else { 86 | CXFile file; 87 | unsigned line; 88 | unsigned offset; 89 | 90 | clang_getSpellingLocation(clang_getCursorLocation(cursors[i+1]), 91 | &file, 92 | &line, 93 | NULL, 94 | &offset); 95 | 96 | const char* filename = clang_getCString(clang_getFileName(file)); 97 | printf("\n\nMethod found in %s in line %d, offset %d\n", clang_getCString(clang_getFileName(file)), line, offset); 98 | 99 | // File reader 100 | FILE *fr = fopen(filename, "r"); 101 | fseek(fr, 0, SEEK_END); 102 | long fsize = ftell(fr); 103 | fseek(fr, 0, SEEK_SET); 104 | 105 | // Reading file to string 106 | char *input = malloc(fsize); 107 | fread(input, fsize, 1, fr); 108 | fclose(fr); 109 | 110 | // Making an output that is input(start till offset) + code injection + input(offset till end) 111 | FILE *fw = fopen(filename, "w"); 112 | char *output = malloc(fsize); 113 | strncpy(output, input, offset); 114 | strcat(output, injectCode); 115 | strcat(output, input+offset); 116 | 117 | // Rewrite the whole file with output string 118 | fwrite(output, fsize, sizeof(output), fw); 119 | fclose(fw); 120 | 121 | clang_disposeTokens(translationUnit, tokens, numTokens); 122 | break; 123 | } 124 | } 125 | } 126 | } 127 | } 128 | --------------------------------------------------------------------------------