├── .gitignore ├── CONTRIBUTING ├── LICENSE ├── README.md ├── editor └── vim │ ├── install.sh │ ├── navc.vim │ ├── navc_client.py │ └── navc_vim.py ├── files.go ├── main.go ├── parse.go ├── request-handler.go ├── symbols-db.go └── test └── client.go /.gitignore: -------------------------------------------------------------------------------- 1 | .navc_dbsymbols 2 | .navc_socket 3 | sample 4 | *.pyc 5 | *.swp 6 | cscope.* 7 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) 6 | (CLA), which you can do online. The CLA is necessary mainly because you own the 7 | copyright to your changes, even after your contribution becomes part of our 8 | codebase, so we need your permission to use and distribute your code. We also 9 | need to be sure of various other things—for instance that you'll tell us if you 10 | know that your code infringes on other people's patents. You don't have to sign 11 | the CLA until after you've submitted your code for review and a member has 12 | approved it, but you must do it before we can put your code into our codebase. 13 | Before you start working on a larger contribution, you should get in touch with 14 | us first through the issue tracker with your idea so that we can help out and 15 | possibly guide you. Coordinating up front makes it much easier to avoid 16 | frustration later on. 17 | 18 | ### Code reviews 19 | All submissions, including submissions by project members, require review. We 20 | use Github pull requests for this purpose. 21 | 22 | ### The small print 23 | Contributions made by corporations are covered by a different agreement than 24 | the one above, the Software Grant and Corporate Contributor License Agreement. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *navc* is a daemon to index and navigate your C code. It watches for all file 2 | changes and automatically update the index. It provides a RPC API to ask for 3 | definition, declaration, calls, and uses of some symbol. This API can be used 4 | by any editor plugin to point to the correct location of the looked up symbol. 5 | navc uses clang to parse the file. Having the abstract syntax tree of the code 6 | can be very powerful as it can know with greater exactitude the location of the 7 | declaration or definition being looked up. The project is limited to C only. 8 | 9 | List of Query Capabilities 10 | ========================== 11 | * Uses of a symbol 12 | * Definition of a function 13 | * All declarations of a symbol: functions, variables, structs, typedef, enums, 14 | defines. 15 | 16 | Installation 17 | ============ 18 | You need to have the development headers for clang 3.6. In Ubuntu this is the 19 | package ``libclang-3.6-dev`` and in mac's homebrew ``homebrew/versions/llvm36``. 20 | Once this is installed, you need to simply run: 21 | 22 | ``` 23 | CGO_LDFLAGS="-L`llvm-config-3.6 --libdir`" \ 24 | go get github.com/google/navc 25 | ``` 26 | 27 | The binary will be located in $GOPATH/bin/navc. Make sure to have $GOPATH/bin in 28 | your PATH. 29 | 30 | VIM plugin 31 | ---------- 32 | 33 | The vim plugin is very basic but usable. For the vim plugin to work, you need 34 | vim with python support. I added an installer to make the use of the plugin 35 | easier. It assumes that your vim uses ``~/.vim`` as config directory. You can 36 | install the plugin by running: 37 | 38 | ``` 39 | $ cd $GOPATH/src/github.com/google/navc/editor/vim/ 40 | $ ./install.sh 41 | ``` 42 | 43 | To uninstall the plugin, just run ``./install.sh -u``. 44 | 45 | Using *navc* 46 | ============ 47 | 48 | You should simply *cd* into your project directory and start the daemon: 49 | 50 | ``` 51 | $ cd $HOME/my/project/ 52 | $ navc 53 | ``` 54 | 55 | If you have a non-standard set of compilation flags (usual on large projects), 56 | you probably want to use clang's 57 | [compile_commands.json](http://clang.llvm.org/docs/JSONCompilationDatabase.html) 58 | database. This can be generated with [bear](https://github.com/rizsotto/Bear) or 59 | with cmake if available. Assuming that a project is compiled with make (e.g. 60 | Linux kernel) you simply need to run: 61 | ``` 62 | $ bear make 63 | ``` 64 | 65 | Once *navc* index your project, from vim you simply place the cursor on top of 66 | the symbol to query and issue one of the following commands: 67 | 68 | | Shortcut | Action | 69 | |----------|-----------------------| 70 | | C-z d | Go to definition | 71 | | C-z e | Go to declaration | 72 | | C-z u | List uses | 73 | | C-z b | Go to previous symbol | 74 | 75 | Caveats 76 | ======= 77 | 78 | 1. Since go version 1.6, the go clang library is having issues with pointers. To 79 | bypass this problem for now, navc has to be run disabling the pointer checks: 80 | ``` 81 | $ GODEBUG=cgocheck=0 navc 82 | ``` 83 | 1. Currently, to update the compile\_commands.json file in memory, the daemon 84 | has to be restarted. 85 | 1. For large projects on Mac, the daemon fail due to too many open files. This 86 | is because every file watched counts as an open file. This could maybe be fixed 87 | with recursive watching. 88 | 89 | 90 | TODO 91 | ==== 92 | * Have better logging and not log everything. In particular, it would be nice 93 | to have a progress bar while indexing code at start up. 94 | * Currently, symbols used in macros are ignored. We need to fix this problem. 95 | * Some array initialization are not been reported by clang (or go-clang). Hence, 96 | we are missing some symbol uses. 97 | * Watch all the compile\_commands.json files and update the database in memory 98 | with any change. 99 | 100 | DISCLAIMER 101 | ========== 102 | This is not a official Google project and it is not supported by Google Inc. 103 | -------------------------------------------------------------------------------- /editor/vim/install.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | # Copyright 2016 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This script install/uninstall the navc vim plugin. To install the plugin: 17 | # ./install.sh 18 | # To uninstall the plugin: 19 | # ./install.sh -u 20 | # 21 | # To update the plugin, simply re-install it. 22 | 23 | PLUGIN_DIR=~/.vim/plugin/navc/ 24 | 25 | if [ "$1" = "-u" ]; then 26 | rm -rf $PLUGIN_DIR 27 | exit 0 28 | fi 29 | 30 | mkdir -p $PLUGIN_DIR 31 | cp navc.vim *.py $PLUGIN_DIR 32 | -------------------------------------------------------------------------------- /editor/vim/navc.vim: -------------------------------------------------------------------------------- 1 | " Copyright 2016 Google Inc. All Rights Reserved. 2 | " 3 | " Licensed under the Apache License, Version 2.0 (the "License"); 4 | " you may not use this file except in compliance with the License. 5 | " You may obtain a copy of the License at 6 | " 7 | " http://www.apache.org/licenses/LICENSE-2.0 8 | " 9 | " Unless required by applicable law or agreed to in writing, software 10 | " distributed under the License is distributed on an "AS IS" BASIS, 11 | " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | " See the License for the specific language governing permissions and 13 | " limitations under the License. 14 | 15 | if !has('python') 16 | echo "Error: Required vim compiled with +python" 17 | finish 18 | endif 19 | 20 | python << EOF 21 | import os 22 | import sys 23 | sys.path.append(os.path.expanduser('~/.vim/plugin/navc/')) 24 | import navc_vim as navc 25 | EOF 26 | 27 | " This function will find the declaration of the symbol currently under the 28 | " cursor. 29 | function! FindCursorSymbolDecls() 30 | python navc.find_cursor_decl() 31 | endfunction 32 | 33 | " This function will find all the uses of symbol declaration. This should work 34 | " well to find all symbols use of a declaration in a header file. 35 | function! FindCursorSymbolUses() 36 | python navc.find_cursor_uses() 37 | endfunction 38 | 39 | function! FindCursorSymbolDef() 40 | python navc.find_symbol_def() 41 | endfunction 42 | 43 | function! MoveCursorToPrev() 44 | python navc.move_cursor_to_prev() 45 | endfunction 46 | 47 | nnoremap e :call FindCursorSymbolDecls() 48 | nnoremap b :call MoveCursorToPrev() 49 | nnoremap u :call FindCursorSymbolUses() 50 | nnoremap d :call FindCursorSymbolDef() 51 | -------------------------------------------------------------------------------- /editor/vim/navc_client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | import socket 17 | import sys 18 | 19 | 20 | class RequestError: 21 | 22 | def __init__(self, error): 23 | self.error = error 24 | 25 | def __str__(self): 26 | return self.error 27 | 28 | 29 | def __get_json(method, args): 30 | req = { 31 | "jsonrpc": "1.0", 32 | "method": method, 33 | "id": "navc_request", 34 | "params": [args] 35 | } 36 | 37 | return json.dumps(req) 38 | 39 | 40 | def get_res(method, args): 41 | req = __get_json(method, args) 42 | 43 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 44 | 45 | try: 46 | sock.connect(".navc.sock") 47 | except socket.error, msg: 48 | print >>sys.stderr, msg 49 | sys.exit(1) 50 | 51 | try: 52 | sock.sendall(req) 53 | res_str = sock.recv(1024) 54 | finally: 55 | sock.close() 56 | 57 | res = json.loads(res_str) 58 | 59 | if res["error"]: 60 | raise RequestError(res["error"]) 61 | 62 | return res["result"] 63 | -------------------------------------------------------------------------------- /editor/vim/navc_vim.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import vim 16 | import re 17 | import os 18 | import navc_client as client 19 | 20 | fname_char = re.compile('[a-zA-Z_]') 21 | 22 | prev_locs = [] 23 | 24 | 25 | def __find_start_cur_symbol(): 26 | row, col = vim.current.window.cursor 27 | while col > 0 and fname_char.match(vim.current.buffer[row - 1][col - 1]): 28 | col -= 1 29 | return (row, col + 1) 30 | 31 | 32 | def __get_choice(): 33 | vim.command("call inputsave()") 34 | vim.command("let choice = input('Input Choice: ')") 35 | vim.command("call inputrestore()") 36 | return vim.eval("choice") 37 | 38 | 39 | def __get_choice_int(): 40 | # TODO: we need to make sure that a number was given and that it is 41 | # within boundaries. 42 | ch = __get_choice() 43 | if not ch: 44 | raise ValueError('No choice') 45 | return int(ch) 46 | 47 | 48 | def __get_cursor_input(): 49 | line, col = __find_start_cur_symbol() 50 | fname = os.path.relpath(vim.current.buffer.name) 51 | 52 | args = { 53 | "File": fname, 54 | "Line": line, 55 | "Col": col, 56 | } 57 | 58 | return args 59 | 60 | 61 | def __move_cursor(fname, line, col): 62 | vim.command('edit %s' % fname) 63 | vim.current.window.cursor = (line, col) 64 | 65 | 66 | def __save_and_move_cursor(fname, line, col): 67 | prow, pcol = vim.current.window.cursor 68 | pfname = vim.current.buffer.name 69 | prev_locs.append((pfname, prow, pcol)) 70 | __move_cursor(fname, line, col) 71 | 72 | 73 | def __get_file_line(fname, line): 74 | # TODO: this is less than optimal, but it does the trick. A vim 75 | # implementation may be more efficient. 76 | with open(fname, 'r') as f: 77 | return f.readlines()[line - 1].strip() 78 | 79 | 80 | def __print_error(s): 81 | vim.command(':echohl Error | echo "' + str(s) + '" | echohl None') 82 | 83 | 84 | def __print_warn(s): 85 | vim.command(':echohl WarningMsg | echo "' + str(s) + '" | echohl None') 86 | 87 | 88 | def __get_multi_choice(options): 89 | if len(options) > 1: 90 | num = 1 91 | for op in options: 92 | line = __get_file_line(op['File'], op['Line']) 93 | print "(%2d) %s %d\n %s" % \ 94 | (num, op['File'], op['Line'], line) 95 | num += 1 96 | ch = __get_choice_int() 97 | ch -= 1 98 | else: 99 | ch = 0 100 | 101 | return ch 102 | 103 | 104 | def find_cursor_decl(): 105 | try: 106 | ret = client.get_res( 107 | "RequestHandler.GetSymbolDecls", __get_cursor_input()) 108 | ch = __get_multi_choice(ret) 109 | __save_and_move_cursor(ret[ch]['File'], ret[ch][ 110 | 'Line'], ret[ch]['Col'] - 1) 111 | except client.RequestError as e: 112 | __print_error(e) 113 | 114 | 115 | def find_cursor_uses(): 116 | try: 117 | ret = client.get_res( 118 | "RequestHandler.GetSymbolUses", __get_cursor_input()) 119 | ch = __get_multi_choice(ret) 120 | __save_and_move_cursor(ret[ch]['File'], ret[ch][ 121 | 'Line'], ret[ch]['Col'] - 1) 122 | except client.RequestError as e: 123 | __print_error(e) 124 | except ValueError: 125 | pass 126 | 127 | 128 | def find_symbol_def(): 129 | try: 130 | ret = client.get_res("RequestHandler.GetSymbolDef", 131 | __get_cursor_input()) 132 | ch = __get_multi_choice(ret) 133 | __save_and_move_cursor(ret[ch]['File'], ret[ch][ 134 | 'Line'], ret[ch]['Col'] - 1) 135 | except client.RequestError as e: 136 | __print_error(e) 137 | except ValueError: 138 | pass 139 | 140 | 141 | def move_cursor_to_prev(): 142 | if len(prev_locs) > 0: 143 | pfname, prow, pcol = prev_locs.pop() 144 | __move_cursor(pfname, prow, pcol) 145 | -------------------------------------------------------------------------------- /files.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | /* 20 | * This module handles all file changes and serialize accesses to the DB 21 | * (symbols-db.go). It is event driven. There are events for file discovery, 22 | * file creation, deletion, renaming, and modification. There are also events 23 | * for DB queries, and a timer for DB flushing. 24 | * 25 | * Everything is initialized in startFilesHandler. All the events are handled in 26 | * the handleFiles go routine. The file discovery is run once at daemon start up 27 | * and it is exected by exploreIndexDir function. Function listenRequests 28 | * listens for any new query and sends the requests to handleFiles for 29 | * processing. 30 | * 31 | * For increased parallelism, we have multiple go routines for parsing (function 32 | * parseFiles). By default, there will be as many parseFiles go routines as CPUs 33 | * available. This function will simply take a file name, call the parser, and 34 | * return the symbolsTUDB created by the parser (presumibly for its insertion in 35 | * the DB). Function handleFiles sends files to be parsed according to its needs 36 | * (e.g. a new file was created, a file was changed, etc). It will later get the 37 | * new symbolsTUDB and insert it in the DB. 38 | * 39 | * +-----------------+ 40 | * | exploreIndexDir | 41 | * +-----------------+ 42 | * | 43 | * | 44 | * v 45 | * +-------------+ +----------------------------+ 46 | * | handleFiles | <---------> | (# cpu cores) x parseFiles | 47 | * +-------------+ +----------------------------+ 48 | * ^ 49 | * | 50 | * | 51 | * +----------------+ 52 | * | listenRequests | 53 | * +----------------+ 54 | */ 55 | 56 | /* 57 | * NOTE: There is a potential race if a included header is removed and created 58 | * quickly (this could be the case for vim and its backup files). To exemplofy 59 | * the issue, assume a file a.c that includes a header b.h. The race goes like 60 | * this: 61 | * 1. b.h is removed and navc quickly reparse a.c but does not add it yet to the 62 | * DB. This new TUDB will have b.h as a potential header, but not a real one. 63 | * 2. While parsing, b.h is created again and navc look for potential files 64 | * including a header named b.h. However, it does not find one because in the 65 | * DB, a.c still have b.h as dependency. Hence, it ignores the create event. 66 | * 3. The new TUDB (the one without the b.h dependency) is inserted in the DB. 67 | * 68 | * In practice I havn't seen this occurring, but it might happen. The 69 | * consequence is that any change to b.h will not cause navc to reparse a.c. 70 | * This can be easily fixed in the next navc reboot or by writing to a.c for 71 | * reparsing. 72 | */ 73 | 74 | import ( 75 | "container/list" 76 | "log" 77 | "net" 78 | "os" 79 | "path/filepath" 80 | "regexp" 81 | "strings" 82 | "sync" 83 | "time" 84 | 85 | fsnotify "gopkg.in/fsnotify.v1" 86 | ) 87 | 88 | const validCString string = `^[^\.].*\.c$` 89 | const validHString string = `^[^\.].*\.h$` 90 | const flushTime int = 10 91 | 92 | var sysInclDir = map[string]bool{ 93 | "/usr/include/": true, 94 | "/usr/lib/": true, 95 | } 96 | 97 | var toParseMap map[string]bool 98 | var toParseQueue *list.List 99 | var inFlight map[string]bool 100 | var nIndexingThreads int 101 | var parseFile chan string 102 | var doneFile chan *symbolsTUDB 103 | var foundFile, foundHeader, removeFile chan string 104 | var flush <-chan time.Time 105 | var newConn chan net.Conn 106 | 107 | var wg sync.WaitGroup 108 | var watcher *fsnotify.Watcher 109 | 110 | var db *symbolsDB 111 | var rh *RequestHandler 112 | 113 | func traversePath(path string, visitDir func(string), visitC func(string), visitRest func(string)) { 114 | filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 115 | if err != nil { 116 | log.Println("error opening", path, "igoring", err) 117 | return filepath.SkipDir 118 | } 119 | 120 | // visit file 121 | if info.IsDir() { 122 | if info.Name() != "." && info.Name()[0] == '.' { 123 | return filepath.SkipDir 124 | } 125 | 126 | visitDir(path) 127 | return nil 128 | } 129 | // ignore non-C files 130 | validC, _ := regexp.MatchString(validCString, path) 131 | if validC { 132 | visitC(path) 133 | } else { 134 | visitRest(path) 135 | } 136 | 137 | return nil 138 | }) 139 | } 140 | 141 | func queueFileToParse(filePath string) { 142 | if len(inFlight) < nIndexingThreads && !inFlight[filePath] { 143 | inFlight[filePath] = true 144 | parseFile <- filePath 145 | } else if !toParseMap[filePath] { 146 | toParseMap[filePath] = true 147 | toParseQueue.PushBack(filePath) 148 | } 149 | } 150 | 151 | func queueFilesToParse(files ...string) { 152 | for _, f := range files { 153 | queueFileToParse(f) 154 | } 155 | } 156 | 157 | func doneFileToParse(tudb *symbolsTUDB) { 158 | if !toParseMap[tudb.File] { 159 | db.InsertTUDB(tudb) 160 | } 161 | 162 | delete(inFlight, tudb.File) 163 | 164 | if toParseQueue.Front() == nil { 165 | return 166 | } 167 | 168 | filePath := toParseQueue.Front().Value.(string) 169 | toParseQueue.Remove(toParseQueue.Front()) 170 | delete(toParseMap, filePath) 171 | 172 | inFlight[filePath] = true 173 | parseFile <- filePath 174 | } 175 | 176 | func parseIncluders(headerPath string) { 177 | toParse, err := db.GetIncluders(headerPath) 178 | if err != nil { 179 | log.Panic(err) 180 | } 181 | queueFilesToParse(toParse...) 182 | } 183 | 184 | func handleFileChange(event fsnotify.Event) { 185 | validC, _ := regexp.MatchString(validCString, event.Name) 186 | validH, _ := regexp.MatchString(validHString, event.Name) 187 | 188 | switch { 189 | case validC: 190 | switch { 191 | case event.Op&(fsnotify.Create|fsnotify.Write) != 0: 192 | queueFilesToParse(event.Name) 193 | case event.Op&(fsnotify.Remove|fsnotify.Rename) != 0: 194 | db.RemoveFileReferences(event.Name) 195 | } 196 | case validH: 197 | if event.Op&(fsnotify.Write|fsnotify.Remove|fsnotify.Rename|fsnotify.Create) != 0 { 198 | parseIncluders(event.Name) 199 | } 200 | } 201 | } 202 | 203 | func handleDirChange(event fsnotify.Event) { 204 | switch { 205 | case event.Op&(fsnotify.Create) != 0: 206 | // explore the new dir 207 | visitorDir := func(path string) { 208 | // add watcher to directory 209 | watcher.Add(path) 210 | } 211 | visitorC := func(path string) { 212 | // put file in channel 213 | queueFilesToParse(path) 214 | } 215 | visitorRest := func(path string) { 216 | // nothing to do 217 | } 218 | traversePath(event.Name, visitorDir, visitorC, visitorRest) 219 | case event.Op&(fsnotify.Remove|fsnotify.Rename) != 0: 220 | // remove watcher from dir 221 | watcher.Remove(event.Name) 222 | } 223 | } 224 | 225 | func isDirectory(path string) (bool, error) { 226 | fi, err := os.Stat(path) 227 | if err != nil { 228 | return false, err 229 | } 230 | return fi.IsDir(), nil 231 | } 232 | 233 | func handleChange(event fsnotify.Event) { 234 | 235 | // ignore if hidden 236 | if filepath.Base(event.Name)[0] == '.' { 237 | return 238 | } 239 | 240 | // first, we need to check if the file is a directory or not 241 | isDir, err := isDirectory(event.Name) 242 | if os.IsNotExist(err) { 243 | // we either removed or renamed. If not found in DB, assuming 244 | // dir 245 | isDir = !db.FileExist(event.Name) 246 | } else if err != nil { 247 | // ignoring this event 248 | return 249 | } 250 | 251 | if isDir { 252 | handleDirChange(event) 253 | } else { 254 | handleFileChange(event) 255 | } 256 | } 257 | 258 | func isSysInclDir(path string) bool { 259 | for incl := range sysInclDir { 260 | if strings.HasPrefix(path, incl) { 261 | return true 262 | } 263 | } 264 | 265 | return false 266 | } 267 | 268 | func parseFiles(indexDir []string) { 269 | wg.Add(1) 270 | defer wg.Done() 271 | 272 | pa := newParser(indexDir) 273 | 274 | for file := range parseFile { 275 | log.Println("parsing", file) 276 | doneFile <- pa.Parse(file) 277 | } 278 | } 279 | 280 | func handleFiles(indexDir []string) { 281 | wg.Add(1) 282 | defer wg.Done() 283 | 284 | // start threads to process files 285 | for i := 0; i < nIndexingThreads; i++ { 286 | go parseFiles(indexDir) 287 | } 288 | 289 | for { 290 | select { 291 | // process parsed files 292 | case tudb, ok := <-doneFile: 293 | if !ok { 294 | return 295 | } 296 | doneFileToParse(tudb) 297 | // process changes in files 298 | case event := <-watcher.Events: 299 | handleChange(event) 300 | case err := <-watcher.Errors: 301 | log.Println("watcher error: ", err) 302 | // process explored files 303 | case header := <-foundHeader: 304 | parseIncluders(header) 305 | case file := <-foundFile: 306 | exist, uptodate, err := db.UptodateFile(file) 307 | if err == nil && (!exist || !uptodate) { 308 | queueFilesToParse(file) 309 | } 310 | case file := <-removeFile: 311 | validH, _ := regexp.MatchString(validHString, file) 312 | if validH { 313 | parseIncluders(file) 314 | } else { 315 | db.RemoveFileReferences(file) 316 | } 317 | // flush frequently to disk 318 | case <-flush: 319 | db.FlushDB(time.Now().Add(-time.Duration(flushTime) * time.Second)) 320 | // handle requests 321 | case conn := <-newConn: 322 | rh.handleRequest(conn) 323 | } 324 | } 325 | } 326 | 327 | func exploreIndexDir(indexDir []string) { 328 | wg.Add(1) 329 | defer wg.Done() 330 | 331 | // explore all the paths in indexDir and process all files 332 | notExplored := db.GetSetFilesInDB() 333 | visitorDir := func(path string) { 334 | // add watcher to directory 335 | watcher.Add(path) 336 | } 337 | visitorC := func(path string) { 338 | // update set of removed files 339 | delete(notExplored, path) 340 | // put file in channel 341 | foundFile <- path 342 | } 343 | visitorRest := func(path string) { 344 | if notExplored[path] { 345 | // update set of removed files 346 | delete(notExplored, path) 347 | } 348 | foundHeader <- path 349 | } 350 | for _, path := range indexDir { 351 | traversePath(path, visitorDir, visitorC, visitorRest) 352 | } 353 | 354 | // check files not explored by now 355 | for path := range notExplored { 356 | if isSysInclDir(path) { 357 | // if system include dir, visit normally 358 | visitorRest(path) 359 | } else { 360 | // if not, then delete 361 | removeFile <- path 362 | } 363 | } 364 | } 365 | 366 | func startFilesHandler(indexDir []string, inputIndexThreads int, dbDir string) error { 367 | var err error 368 | 369 | toParseMap = make(map[string]bool) 370 | toParseQueue = list.New() 371 | inFlight = make(map[string]bool) 372 | nIndexingThreads = inputIndexThreads 373 | parseFile = make(chan string) 374 | doneFile = make(chan *symbolsTUDB) 375 | foundFile = make(chan string) 376 | foundHeader = make(chan string) 377 | removeFile = make(chan string) 378 | newConn = make(chan net.Conn) 379 | watcher, err = fsnotify.NewWatcher() 380 | if err != nil { 381 | return err 382 | } 383 | flush = time.Tick(time.Duration(flushTime) * time.Second) 384 | db = newSymbolsDB(dbDir) 385 | rh = newRequestHandler(db) 386 | 387 | go listenRequests(newConn) 388 | go handleFiles(indexDir) 389 | go exploreIndexDir(indexDir) 390 | 391 | return nil 392 | } 393 | 394 | func closeFilesHandler() { 395 | close(parseFile) 396 | close(doneFile) 397 | watcher.Close() 398 | 399 | wg.Wait() 400 | 401 | db.FlushDB(time.Now()) 402 | } 403 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "log" 22 | "os" 23 | "os/signal" 24 | "runtime" 25 | ) 26 | 27 | func main() { 28 | // path to symbols DB 29 | var dbDir string 30 | flag.StringVar(&dbDir, "db", ".navc_dbsymbols", "Path to symbols DB dir") 31 | 32 | // number of parallel indexing threads 33 | var nIndexingThreads int 34 | flag.IntVar(&nIndexingThreads, "numThreads", runtime.NumCPU(), 35 | "Number of indexing threads") 36 | 37 | // reset DB 38 | var resetDB bool 39 | flag.BoolVar(&resetDB, "resetDB", false, 40 | "Reset symbols DB and start over") 41 | 42 | // print file db and exit 43 | var dbFilePrint string 44 | flag.StringVar(&dbFilePrint, "dbFilePrint", "", "DB file to print") 45 | 46 | flag.Parse() 47 | 48 | // list of directores with source to index 49 | var indexDir []string 50 | if len(flag.Args()) > 0 { 51 | indexDir = flag.Args() 52 | for _, path := range indexDir { 53 | fi, err := os.Stat(path) 54 | if err != nil { 55 | log.Println("unable to access ", path, err) 56 | return 57 | } 58 | if !fi.IsDir() { 59 | log.Println("only dir inputs allowed") 60 | return 61 | } 62 | } 63 | } else { 64 | indexDir = []string{"."} 65 | } 66 | 67 | if dbFilePrint != "" { 68 | db := newSymbolsDB(dbDir) 69 | err := db.PrintAndCheckSymbolsTUDB(dbFilePrint) 70 | if err != nil { 71 | log.Println(err) 72 | } 73 | return 74 | } 75 | 76 | // handle interrup and kill signals 77 | intr := make(chan os.Signal, 1) 78 | signal.Notify(intr, os.Interrupt, os.Kill) 79 | defer close(intr) 80 | 81 | // if we need to reset the database, erase the old one 82 | if resetDB { 83 | os.RemoveAll(dbDir) 84 | } 85 | 86 | // start files handler 87 | err := startFilesHandler(indexDir, nIndexingThreads, dbDir) 88 | if err != nil { 89 | log.Println("unable to start daemon", err) 90 | return 91 | } 92 | defer closeFilesHandler() 93 | 94 | // wait until ctl-c is pressed 95 | select { 96 | case <-intr: 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /parse.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "log" 22 | "os" 23 | "path/filepath" 24 | "strings" 25 | 26 | "github.com/go-clang/v3.6/clang" 27 | ) 28 | 29 | type parse struct { 30 | cas map[string][]string 31 | } 32 | 33 | /* 34 | * There is so much path manipulation in the construction of the compilation 35 | * aguments database that I think this deserves a long explanation. Compilation 36 | * database (compile_command.json) provides absolute path of the file with its 37 | * compilation options. We are storing this compilation options/arguments in 38 | * the cas field of the Parser struct to be used during parsing. This is a map 39 | * of file name to list of arguments. The name file should match the one 40 | * returned by the directory traversing in main, i.e., the minimum relative 41 | * path of the file (the path returned by filepath.Clean) or the absolute path 42 | * depending on the input. For each input directory (provided in the command 43 | * line) we try to read the compile command database from disk. For each of the 44 | * file path read, we fix the full path to match the relative or absolute path 45 | * of the input (fixPaths) and clean it with filepath.Clean. 46 | * 47 | * Then, we need to make sure that the directories in the -I options also match 48 | * the relative or absolute path from the input. This is fixed in fixCompDirArg 49 | * right before populating the arguments for some specific file. 50 | */ 51 | 52 | type compArgs struct { 53 | Directory string 54 | Command string 55 | File string 56 | } 57 | 58 | func fixPaths(cas []compArgs, path string) { 59 | // first, find absolute path of @path 60 | if filepath.IsAbs(path) { 61 | return 62 | } 63 | 64 | wd, err := os.Getwd() 65 | if err != nil { 66 | log.Panic("unable to get working directoy: ", err) 67 | } 68 | 69 | // second, replace absolute path with relative path and clean 70 | for i := range cas { 71 | ca := &cas[i] 72 | rel, err := filepath.Rel(wd, ca.File) 73 | if err != nil { 74 | log.Panic("unable to get relative path: ", err) 75 | } 76 | ca.File = filepath.Clean(rel) 77 | } 78 | } 79 | 80 | func fixCompDirArg(argDir, path string) string { 81 | if filepath.IsAbs(path) { 82 | if filepath.IsAbs(argDir) { 83 | return argDir 84 | } 85 | 86 | abs, err := filepath.Abs(argDir) 87 | if err != nil { 88 | log.Panic("unable to get absolute path: ", 89 | err) 90 | } 91 | return filepath.Clean(abs) 92 | } 93 | if filepath.IsAbs(argDir) { 94 | wd, err := os.Getwd() 95 | if err != nil { 96 | log.Panic("unable to get working directoy: ", 97 | err) 98 | } 99 | rel, err := filepath.Rel(wd, argDir) 100 | if err != nil { 101 | log.Panic("unable to get relative path: ", 102 | err) 103 | } 104 | return filepath.Clean(rel) 105 | } 106 | return filepath.Clean(path + "/" + argDir) 107 | } 108 | 109 | func getCompArgs(command, path string) []string { 110 | args := []string{} 111 | 112 | argsList := strings.Fields(command) 113 | 114 | for i, arg := range argsList { 115 | switch { 116 | case arg == "-D": 117 | args = append(args, arg, argsList[i+1]) 118 | case strings.HasPrefix(arg, "-D"): 119 | args = append(args, arg) 120 | case arg == "-I": 121 | argDir := fixCompDirArg(argsList[i+1], path) 122 | args = append(args, "-I", argDir) 123 | case strings.HasPrefix(arg, "-I"): 124 | argDir := fixCompDirArg( 125 | strings.Replace(arg, "-I", "", 1), 126 | path) 127 | args = append(args, "-I", argDir) 128 | } 129 | } 130 | 131 | return args 132 | } 133 | 134 | func newParser(inputDirs []string) *parse { 135 | ret := &parse{make(map[string][]string)} 136 | 137 | // read compilation args db and fix files paths 138 | for _, path := range inputDirs { 139 | f, err := os.Open(path + "/compile_commands.json") 140 | if os.IsPermission(err) { 141 | log.Panic("error opening compile db: ", err) 142 | } else if err != nil { 143 | continue 144 | } 145 | defer f.Close() 146 | 147 | dec := json.NewDecoder(f) 148 | var cas []compArgs 149 | err = dec.Decode(&cas) 150 | if err != nil { 151 | log.Panic(err) 152 | } 153 | 154 | fixPaths(cas, path) 155 | 156 | // index compArgs by file names 157 | for _, ca := range cas { 158 | ret.cas[ca.File] = getCompArgs(ca.Command, path) 159 | } 160 | } 161 | 162 | return ret 163 | } 164 | 165 | func getSymbolFromCursor(cursor *clang.Cursor) *symbolInfo { 166 | if cursor.IsNull() { 167 | return nil 168 | } 169 | 170 | f, line, col, _ := cursor.Location().FileLocation() 171 | fName := filepath.Clean(f.Name()) 172 | return &symbolInfo{ 173 | name: cursor.Spelling(), 174 | usr: cursor.USR(), 175 | loc: SymbolLocReq{ 176 | fName, 177 | int(line), 178 | int(col), 179 | }, 180 | } 181 | } 182 | 183 | func (pa *parse) Parse(file string) *symbolsTUDB { 184 | idx := clang.NewIndex(0, 0) 185 | defer idx.Dispose() 186 | 187 | args, ok := pa.cas[file] 188 | if !ok { 189 | args = []string{} 190 | } 191 | tu := idx.ParseTranslationUnit(file, args, nil, clang.TranslationUnit_DetailedPreprocessingRecord) 192 | defer tu.Dispose() 193 | 194 | db := newSymbolsTUDB(file, tu.File(file).Time()) 195 | defer db.TempSaveDB() 196 | 197 | visitNode := func(cursor, parent clang.Cursor) clang.ChildVisitResult { 198 | if cursor.IsNull() { 199 | return clang.ChildVisit_Continue 200 | } 201 | 202 | cur := getSymbolFromCursor(&cursor) 203 | curFile := cur.loc.File 204 | 205 | if curFile == "" || curFile == "." { 206 | // ignore system code 207 | return clang.ChildVisit_Continue 208 | } 209 | 210 | // TODO: erase! this is not required 211 | if false { 212 | log.Printf("%s: %s (%s)\n", 213 | cursor.Kind().Spelling(), 214 | cursor.Spelling(), 215 | cursor.USR()) 216 | log.Println(curFile, ":", cur.loc.Line, cur.loc.Col) 217 | } 218 | //////////////////////////////////// 219 | switch cursor.Kind() { 220 | case clang.Cursor_FunctionDecl, clang.Cursor_StructDecl, clang.Cursor_FieldDecl, 221 | clang.Cursor_TypedefDecl, clang.Cursor_EnumDecl, clang.Cursor_EnumConstantDecl: 222 | defCursor := cursor.Definition() 223 | if !defCursor.IsNull() { 224 | def := getSymbolFromCursor(&defCursor) 225 | db.InsertSymbolDeclWithDef(cur, def) 226 | } else { 227 | db.InsertSymbolDecl(cur) 228 | } 229 | case clang.Cursor_MacroDefinition: 230 | db.InsertSymbolDeclWithDef(cur, cur) 231 | case clang.Cursor_VarDecl: 232 | db.InsertSymbolDecl(cur) 233 | case clang.Cursor_ParmDecl: 234 | if cursor.Spelling() != "" { 235 | db.InsertSymbolDecl(cur) 236 | } 237 | case clang.Cursor_CallExpr: 238 | decCursor := cursor.Referenced() 239 | dec := getSymbolFromCursor(&decCursor) 240 | db.InsertSymbolUse(cur, dec, true) 241 | case clang.Cursor_DeclRefExpr, clang.Cursor_TypeRef, clang.Cursor_MemberRefExpr, 242 | clang.Cursor_MacroExpansion: 243 | decCursor := cursor.Referenced() 244 | dec := getSymbolFromCursor(&decCursor) 245 | db.InsertSymbolUse(cur, dec, false) 246 | case clang.Cursor_InclusionDirective: 247 | incFile := cursor.IncludedFile() 248 | db.InsertHeader(cursor.Spelling(), incFile) 249 | } 250 | 251 | return clang.ChildVisit_Recurse 252 | } 253 | 254 | tu.TranslationUnitCursor().Visit(visitNode) 255 | 256 | return db 257 | } 258 | -------------------------------------------------------------------------------- /request-handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "log" 21 | "net" 22 | "net/rpc" 23 | "net/rpc/jsonrpc" 24 | "os" 25 | ) 26 | 27 | // RequestHandler is the handler of all quries coming to the daemon. It is 28 | // exported as required by the rpc packade. 29 | type RequestHandler struct { 30 | db *symbolsDB 31 | handler *rpc.Server 32 | } 33 | 34 | // GetSymbolDecls gets a symbol use location and returns the list of 35 | // declarations for that symbol. 36 | func (rh *RequestHandler) GetSymbolDecls(use *SymbolLocReq, res *[]*SymbolLocReq) error { 37 | dec, err := rh.db.GetSymbolDecl(use) 38 | if err != nil { 39 | return err 40 | } 41 | *res = dec 42 | return nil 43 | } 44 | 45 | // GetSymbolUses gets a symbol use location and returns all the uses of that 46 | // symbol. 47 | func (rh *RequestHandler) GetSymbolUses(use *SymbolLocReq, res *[]*SymbolLocReq) error { 48 | uses, err := rh.db.GetSymbolUses(use) 49 | if err != nil { 50 | return err 51 | } 52 | *res = uses 53 | return nil 54 | } 55 | 56 | // GetSymbolDef gets a symbol use location and returns the definition location 57 | // of the symbol. If not available, it returns an error. 58 | func (rh *RequestHandler) GetSymbolDef(use *SymbolLocReq, res *[]*SymbolLocReq) error { 59 | def, err := rh.db.GetSymbolDef(use) 60 | if err != nil { 61 | return err 62 | } 63 | if def == nil { 64 | // find all definitions with the same name 65 | defs, err := rh.db.GetAllSymbolDefs(use) 66 | if err != nil { 67 | return err 68 | } 69 | *res = defs 70 | return nil 71 | } 72 | *res = []*SymbolLocReq{def} 73 | return nil 74 | } 75 | 76 | func newRequestHandler(db *symbolsDB) *RequestHandler { 77 | rh := &RequestHandler{db, rpc.NewServer()} 78 | 79 | rh.handler.Register(rh) 80 | 81 | return rh 82 | } 83 | 84 | func (rh *RequestHandler) handleRequest(conn net.Conn) { 85 | codec := jsonrpc.NewServerCodec(conn) 86 | defer codec.Close() 87 | 88 | err := rh.handler.ServeRequest(codec) 89 | if err != nil { 90 | log.Println("handling request (ignoring):", err) 91 | } 92 | } 93 | 94 | func listenRequests(newConn chan<- net.Conn) { 95 | // socket file for communication with daemon 96 | socketFile := ".navc.sock" 97 | 98 | // start serving requests 99 | os.Remove(socketFile) 100 | lis, err := net.Listen("unix", socketFile) 101 | if err != nil { 102 | log.Panic("error opening socket", err) 103 | } 104 | defer os.Remove(socketFile) 105 | defer lis.Close() 106 | 107 | for { 108 | conn, err := lis.Accept() 109 | if err != nil { 110 | log.Println("accepting connection (breaking):", err) 111 | break 112 | } 113 | 114 | newConn <- conn 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /symbols-db.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/sha1" 21 | "encoding/gob" 22 | "encoding/hex" 23 | "fmt" 24 | "io/ioutil" 25 | "log" 26 | "os" 27 | "path/filepath" 28 | "time" 29 | 30 | "github.com/go-clang/v3.6/clang" 31 | ) 32 | 33 | /* 34 | * The symbols database keeps all the index of the code. We keep one file (or 35 | * database file) per indexed C file. This file will contain the information of 36 | * the code in the C file and all the headers included recursively (the headers 37 | * included in the headers). We call this translation unit or TU (following 38 | * clang's nomenclature). Having a database per translation unit will allow 39 | * greater parallelism and higher performance at indexing time. All these files 40 | * are stored in the symbols directory, .navc_dbsymbols by default, and 41 | * represented by the struct symbolsDB. In the symbols directory, each database 42 | * file uses the sha1 of the original file name as file name. The file will 43 | * simply have a serialized form of some of the fields in the structure 44 | * symbolsTUDB. This structure has the following fields: 45 | * 46 | * - File: Name of the source file indexed. 47 | * 48 | * - Mtime: Modification time of the file when was indexed. 49 | * 50 | * - Headers (fileID -> Time): Contains all the header files included in the 51 | * translation unit and the modification time of each when the translation unit 52 | * was indexed. 53 | * 54 | * - SymLoc (symbolLoc -> symbolID): Contains all the symbols uses in the 55 | * translatio unit. It maps symbol locations to symbol ID. 56 | * 57 | * - SymData (symbolID -> symbolData): Contains the data of all the symbols 58 | * indexed by symbol ID. Given any symbol location, we can find its symbol data 59 | * in the translation unit. The symbol data will have the list of declarations 60 | * of the symbol and the list of uses in the translation unit. If the definition 61 | * of the symbol is available in this translation unit, DefAvail will be true 62 | * and Def will hold the location of the definition. 63 | * 64 | * - Includers: In case the translation unit represent a header file, this list 65 | * will have all the translation units including this file. This is the only 66 | * information necessary for header files. Header files is where two translation 67 | * units meet. For instance, one function declared in a.h and used by a.c can be 68 | * defined in b.c. The meeting point of a.c and b.c is their included header 69 | * file a.h. Keeping this information is not really necessary but it speed up 70 | * lookup of symbols. In theory, these can be recreated from the regular 71 | * translation units. 72 | * 73 | * fileID and symbolID are simply a hash of the name of the file or symbol. In 74 | * this case, it is the sha1 hash of the names. 75 | * 76 | * symbolsDB has a map with an entry for every symbolsTUDB. On each entry, it 77 | * caches some information of the translation unit. Translations units are 78 | * inserted in the InsertTUDB function. This function will also be called to 79 | * replace an old translation unit of a file. Translation units will be 80 | * persisted to disk whenever the symbolsDB is flushed. This is done by calling 81 | * the FlushDB function. 82 | */ 83 | 84 | type symbolID [sha1.Size]byte 85 | type fileID [sha1.Size]byte 86 | 87 | type symbolLoc struct { 88 | File fileID 89 | Line int16 90 | Col int16 91 | } 92 | 93 | type symbolUse struct { 94 | Loc symbolLoc 95 | FuncCall bool 96 | } 97 | 98 | type symbolData struct { 99 | Name string 100 | Uses []symbolUse 101 | Decls []symbolLoc 102 | DefAvail bool 103 | Def symbolLoc 104 | } 105 | 106 | // SymbolLocReq is used as input and output structure for the daemon requests. 107 | type SymbolLocReq struct { 108 | File string 109 | Line int 110 | Col int 111 | } 112 | 113 | type symbolInfo struct { 114 | name string 115 | usr string 116 | loc SymbolLocReq 117 | } 118 | 119 | type symbolsTUDB struct { 120 | File string 121 | 122 | // .c data 123 | Mtime time.Time 124 | SymLoc map[symbolLoc]symbolID 125 | SymData map[symbolID]symbolData 126 | Headers map[fileID]time.Time 127 | 128 | // .h lists 129 | Includers map[fileID]bool 130 | 131 | // used only while parsing 132 | headersTUDB map[string]bool 133 | tmpFile string 134 | } 135 | 136 | type tuSymbolsDBCache struct { 137 | tudb *symbolsTUDB 138 | Mtime time.Time 139 | Path string 140 | 141 | accTime time.Time 142 | dirty bool 143 | } 144 | 145 | type symbolsDB struct { 146 | TUDBs map[fileID]*tuSymbolsDBCache 147 | } 148 | 149 | // db directory path 150 | var dbDirPath string 151 | 152 | // db temp directory = dbDirPath + "/tmp" 153 | var dbDirTmp string 154 | 155 | // db index file = dbDirPath + "/index" 156 | var dbDirIndex string 157 | 158 | ///// Helper functions 159 | 160 | func getStringEncode(str string) [sha1.Size]byte { 161 | return sha1.Sum([]byte(str)) 162 | } 163 | 164 | func nonExistingHeaderName(headPath string) string { 165 | // adding magic to filename to not confuse it with real files 166 | return "IDoNotReallyExist-" + filepath.Base(headPath) 167 | } 168 | 169 | ///// Symbols DB methods 170 | 171 | func newSymbolsDB(dbDirPathIn string) *symbolsDB { 172 | // create index directory if it does not exist 173 | err := os.MkdirAll(dbDirPathIn+"/tmp", 0700) 174 | if err != nil { 175 | log.Panic("unable to create db dir ", err) 176 | } 177 | dbDirPath = dbDirPathIn 178 | dbDirTmp = dbDirPath + "/tmp" 179 | dbDirIndex = dbDirPath + "/index" 180 | 181 | newDB, err := loadSymbolsDBIndex() 182 | if err != nil && os.IsNotExist(err) { 183 | newDB = &symbolsDB{make(map[fileID]*tuSymbolsDBCache)} 184 | } else if err != nil { 185 | return nil 186 | } 187 | 188 | return newDB 189 | } 190 | 191 | func loadSymbolsDBIndex() (*symbolsDB, error) { 192 | var index symbolsDB 193 | 194 | indexFile, err := os.Open(dbDirIndex) 195 | if err != nil { 196 | return nil, err 197 | } 198 | defer indexFile.Close() 199 | 200 | dec := gob.NewDecoder(indexFile) 201 | 202 | err = dec.Decode(&index) 203 | if err != nil { 204 | return nil, err 205 | } 206 | 207 | return &index, nil 208 | } 209 | 210 | func (db *symbolsDB) saveSymbolsDBIndex() error { 211 | flags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC 212 | indexFile, err := os.OpenFile(dbDirIndex, flags, 0644) 213 | if err != nil { 214 | return err 215 | } 216 | defer indexFile.Close() 217 | 218 | enc := gob.NewEncoder(indexFile) 219 | 220 | err = enc.Encode(db) 221 | if err != nil { 222 | return err 223 | } 224 | 225 | return nil 226 | } 227 | 228 | func (db *symbolsDB) FlushDB(saveFrom time.Time) error { 229 | for _, cache := range db.TUDBs { 230 | if cache.tudb == nil { 231 | continue 232 | } 233 | 234 | if cache.accTime.After(saveFrom) { 235 | continue 236 | } 237 | 238 | if cache.dirty { 239 | err := cache.tudb.SaveSymbolsTUDB(getDBFileName(cache.Path)) 240 | if err != nil { 241 | return err 242 | } 243 | } 244 | cache.dirty = false 245 | cache.tudb = nil 246 | } 247 | 248 | db.saveSymbolsDBIndex() 249 | 250 | return nil 251 | } 252 | 253 | func getDBFileNameFromSha1(fid fileID) string { 254 | return dbDirPath + "/" + hex.EncodeToString(fid[:]) 255 | } 256 | 257 | func getDBFileName(file string) string { 258 | return getDBFileNameFromSha1(getStringEncode(file)) 259 | } 260 | 261 | func (db *symbolsDB) FileExist(filePath string) bool { 262 | return db.TUDBs[getStringEncode(filePath)] != nil 263 | } 264 | 265 | func (db *symbolsDB) LoadSymbolsTUDBFromSha1(file fileID) (*symbolsTUDB, error) { 266 | tudb, err := loadSymbolsTUDB(getDBFileNameFromSha1(file)) 267 | if err != nil { 268 | return nil, err 269 | } 270 | 271 | return tudb, nil 272 | } 273 | 274 | func (db *symbolsDB) getListOfFilenames(fileSet map[fileID]bool) []string { 275 | filenames := []string{} 276 | for fid := range fileSet { 277 | filenames = append(filenames, db.TUDBs[fid].Path) 278 | } 279 | 280 | return filenames 281 | } 282 | 283 | func (db *symbolsDB) GetIncluders(headPath string) ([]string, error) { 284 | realHeader := true 285 | hmtime := time.Time{} 286 | headID := getStringEncode(headPath) 287 | 288 | if db.TUDBs[headID] == nil { 289 | // lets try for inexistent but potential headers 290 | headID = getStringEncode(nonExistingHeaderName(headPath)) 291 | if db.TUDBs[headID] == nil { 292 | return []string{}, nil 293 | } 294 | realHeader = false 295 | } 296 | 297 | htudb, err := db.GetSymbolsTUDB(headID) 298 | if err != nil { 299 | return nil, err 300 | } 301 | 302 | if realHeader { 303 | // if the header is a real not a potential one in the DB, check 304 | // if it exits and its mtime 305 | info, err := os.Stat(headPath) 306 | if err != nil { 307 | return db.getListOfFilenames(htudb.Includers), nil 308 | } 309 | hmtime = info.ModTime() 310 | } 311 | 312 | files := []string{} 313 | for includer := range htudb.Includers { 314 | tudb, err := db.GetSymbolsTUDB(includer) 315 | if err != nil { 316 | return nil, err 317 | } 318 | 319 | if hmtime.IsZero() || hmtime.After(tudb.Headers[headID]) { 320 | files = append(files, tudb.File) 321 | } 322 | } 323 | 324 | return files, nil 325 | } 326 | 327 | func (db *symbolsDB) UptodateFile(file string) (bool, bool, error) { 328 | info, err := os.Stat(file) 329 | if err != nil { 330 | return false, false, err 331 | } 332 | 333 | fileSha1 := getStringEncode(file) 334 | cache := db.TUDBs[fileSha1] 335 | if cache == nil { 336 | return false, false, nil 337 | } 338 | 339 | if cache.Mtime.Before(info.ModTime()) { 340 | return true, false, nil 341 | } 342 | 343 | return true, true, nil 344 | } 345 | 346 | func (db *symbolsDB) GetSymbolsTUDB(fid fileID) (*symbolsTUDB, error) { 347 | cache := db.TUDBs[fid] 348 | 349 | if cache == nil { 350 | return nil, fmt.Errorf("File not in DB") 351 | } 352 | 353 | cache.accTime = time.Now() 354 | 355 | if cache.tudb != nil { 356 | return cache.tudb, nil 357 | } 358 | 359 | var err error 360 | cache.tudb, err = db.LoadSymbolsTUDBFromSha1(fid) 361 | if err != nil { 362 | return nil, err 363 | } 364 | 365 | return cache.tudb, nil 366 | } 367 | 368 | func (db *symbolsDB) removeFileFromHeader(headerID, fid fileID) error { 369 | tudb, err := db.GetSymbolsTUDB(headerID) 370 | if err != nil { 371 | return err 372 | } 373 | 374 | delete(tudb.Includers, fid) 375 | db.TUDBs[headerID].dirty = true 376 | 377 | if len(tudb.Includers) == 0 { 378 | delete(db.TUDBs, headerID) 379 | os.Remove(getDBFileNameFromSha1(headerID)) 380 | } 381 | 382 | return nil 383 | } 384 | 385 | func (db *symbolsDB) RemoveFileReferences(file string) error { 386 | fileSha1 := getStringEncode(file) 387 | 388 | tudb, err := db.GetSymbolsTUDB(fileSha1) 389 | if err != nil { 390 | return err 391 | } 392 | 393 | for h := range tudb.Headers { 394 | err := db.removeFileFromHeader(h, fileSha1) 395 | if err != nil { 396 | return err 397 | } 398 | } 399 | 400 | delete(db.TUDBs, fileSha1) 401 | os.Remove(getDBFileName(file)) 402 | 403 | return nil 404 | } 405 | 406 | func (db *symbolsDB) GetSetFilesInDB() map[string]bool { 407 | fileSet := map[string]bool{} 408 | 409 | for _, cache := range db.TUDBs { 410 | if cache.Mtime.IsZero() { 411 | // ignore false files 412 | continue 413 | } 414 | fileSet[cache.Path] = true 415 | } 416 | 417 | return fileSet 418 | } 419 | 420 | func (db *symbolsDB) RemoveFileDepsReferences(file string) ([]string, error) { 421 | fileSha1 := getStringEncode(file) 422 | tudb, err := db.GetSymbolsTUDB(fileSha1) 423 | if err != nil { 424 | return nil, err 425 | } 426 | 427 | deps := db.getListOfFilenames(tudb.Includers) 428 | 429 | for _, dep := range deps { 430 | db.RemoveFileReferences(dep) 431 | } 432 | 433 | return deps, nil 434 | } 435 | 436 | func (db *symbolsDB) InsertTUDB(tudb *symbolsTUDB) error { 437 | var err error 438 | fileSha1 := getStringEncode(tudb.File) 439 | otudb := db.TUDBs[fileSha1] 440 | 441 | if otudb != nil { 442 | if otudb.Mtime.After(tudb.Mtime) { 443 | log.Panic("Inserting older tudb", otudb.Path, otudb.Mtime, tudb.Mtime) 444 | } 445 | 446 | db.RemoveFileReferences(tudb.File) 447 | } 448 | 449 | for header := range tudb.headersTUDB { 450 | var htudb *symbolsTUDB 451 | headerSha1 := getStringEncode(header) 452 | 453 | hcache := db.TUDBs[headerSha1] 454 | if hcache == nil { 455 | htudb = newSymbolsTUDB(header, tudb.Headers[headerSha1]) 456 | hcache = &tuSymbolsDBCache{ 457 | tudb: htudb, 458 | Mtime: htudb.Mtime, 459 | Path: htudb.File, 460 | accTime: time.Now(), 461 | } 462 | db.TUDBs[headerSha1] = hcache 463 | } else { 464 | htudb, err = db.GetSymbolsTUDB(headerSha1) 465 | if err != nil { 466 | return err 467 | } 468 | } 469 | 470 | htudb.Includers[fileSha1] = true 471 | hcache.dirty = true 472 | } 473 | 474 | err = os.Rename(tudb.tmpFile, getDBFileName(tudb.File)) 475 | if err != nil { 476 | return err 477 | } 478 | db.TUDBs[fileSha1] = &tuSymbolsDBCache{ 479 | Mtime: tudb.Mtime, 480 | Path: tudb.File, 481 | } 482 | 483 | return nil 484 | } 485 | 486 | ///// symbolsDB query methods 487 | 488 | func getSymbolLoc(sym *SymbolLocReq) *symbolLoc { 489 | fileSha1 := getStringEncode(filepath.Clean(sym.File)) 490 | return &symbolLoc{ 491 | fileSha1, 492 | int16(sym.Line), 493 | int16(sym.Col), 494 | } 495 | } 496 | 497 | func (db *symbolsDB) getSymbolLocReq(syms []symbolLoc) []*SymbolLocReq { 498 | res := []*SymbolLocReq{} 499 | 500 | for _, sym := range syms { 501 | cache := db.TUDBs[sym.File] 502 | if cache == nil { 503 | continue 504 | } 505 | 506 | res = append(res, &SymbolLocReq{ 507 | cache.Path, 508 | int(sym.Line), 509 | int(sym.Col), 510 | }) 511 | } 512 | 513 | if len(res) == 0 { 514 | return nil 515 | } 516 | 517 | return res 518 | } 519 | 520 | func getIncluder(htudb *symbolsTUDB) *symbolsTUDB { 521 | for fileSha1 := range htudb.Includers { 522 | tudb, err := db.GetSymbolsTUDB(fileSha1) 523 | if err != nil { 524 | log.Panic("unable to find includer") 525 | } 526 | return tudb 527 | } 528 | 529 | return nil 530 | } 531 | 532 | func (db *symbolsDB) GetSymbolDecl(useReq *SymbolLocReq) ([]*SymbolLocReq, error) { 533 | loc := getSymbolLoc(useReq) 534 | tudb, err := db.GetSymbolsTUDB(loc.File) 535 | if err != nil { 536 | return nil, err 537 | } 538 | 539 | // if header file, we should use any of its tudb 540 | if len(tudb.Includers) > 0 { 541 | tudb = getIncluder(tudb) 542 | } 543 | 544 | // checking if we have the location in DB 545 | id, exist := tudb.SymLoc[*loc] 546 | if !exist { 547 | return nil, fmt.Errorf("Symbol use not found") 548 | } 549 | 550 | data := tudb.SymData[id] 551 | return db.getSymbolLocReq(data.Decls), nil 552 | } 553 | 554 | func (db *symbolsDB) GetSymbolUses(useReq *SymbolLocReq) ([]*SymbolLocReq, error) { 555 | loc := getSymbolLoc(useReq) 556 | tudb, err := db.GetSymbolsTUDB(loc.File) 557 | if err != nil { 558 | return nil, err 559 | } 560 | fileSha1 := loc.File 561 | 562 | // if header file, we should use any of its tudb 563 | if len(tudb.Includers) > 0 { 564 | tudb = getIncluder(tudb) 565 | fileSha1 = getStringEncode(tudb.File) 566 | } 567 | 568 | // checking if we have the location in DB 569 | id, exist := tudb.SymLoc[*loc] 570 | if !exist { 571 | return nil, fmt.Errorf("Symbol use not found") 572 | } 573 | 574 | data := tudb.SymData[id] 575 | 576 | // add uses in this TU 577 | uses := make(map[symbolLoc]bool) 578 | for _, use := range data.Uses { 579 | uses[use.Loc] = true 580 | } 581 | // look for uses in declarations in header files 582 | for _, decl := range data.Decls { 583 | if decl.File == fileSha1 { 584 | continue 585 | } 586 | 587 | htudb, err := db.GetSymbolsTUDB(decl.File) 588 | if err != nil { 589 | continue 590 | } 591 | 592 | for tuSha1 := range htudb.Includers { 593 | if tuSha1 == fileSha1 { 594 | continue 595 | } 596 | 597 | otudb, err := db.GetSymbolsTUDB(tuSha1) 598 | if err != nil { 599 | continue 600 | } 601 | 602 | odata := otudb.SymData[id] 603 | for _, use := range odata.Uses { 604 | uses[use.Loc] = true 605 | } 606 | } 607 | } 608 | 609 | useLocs := []symbolLoc{} 610 | for useLoc := range uses { 611 | useLocs = append(useLocs, useLoc) 612 | } 613 | 614 | return db.getSymbolLocReq(useLocs), nil 615 | } 616 | 617 | func (db *symbolsDB) GetSymbolDef(useReq *SymbolLocReq) (*SymbolLocReq, error) { 618 | loc := getSymbolLoc(useReq) 619 | tudb, err := db.GetSymbolsTUDB(loc.File) 620 | if err != nil { 621 | return nil, err 622 | } 623 | fileSha1 := loc.File 624 | 625 | // if header file, we should use any of its tudb 626 | if len(tudb.Includers) > 0 { 627 | tudb = getIncluder(tudb) 628 | fileSha1 = getStringEncode(tudb.File) 629 | } 630 | 631 | // checking if we have the location in DB 632 | id, exist := tudb.SymLoc[*loc] 633 | if !exist { 634 | return nil, fmt.Errorf("Symbol use not found") 635 | } 636 | 637 | data := tudb.SymData[id] 638 | 639 | if data.DefAvail { 640 | return db.getSymbolLocReq([]symbolLoc{data.Def})[0], nil 641 | } 642 | 643 | for _, decl := range data.Decls { 644 | if decl.File == fileSha1 { 645 | continue 646 | } 647 | 648 | htudb, err := db.GetSymbolsTUDB(decl.File) 649 | if err != nil { 650 | continue 651 | } 652 | 653 | for tuSha1 := range htudb.Includers { 654 | if tuSha1 == fileSha1 { 655 | continue 656 | } 657 | 658 | otudb, err := db.GetSymbolsTUDB(tuSha1) 659 | if err != nil { 660 | continue 661 | } 662 | 663 | odata := otudb.SymData[id] 664 | if odata.DefAvail { 665 | return db.getSymbolLocReq([]symbolLoc{odata.Def})[0], nil 666 | } 667 | } 668 | } 669 | 670 | return nil, fmt.Errorf("Definition not found") 671 | } 672 | 673 | func (db *symbolsDB) GetAllSymbolDefs(use *SymbolLocReq) ([]*SymbolLocReq, error) { 674 | // TODO: this worked nice in the old sqlite DB as we had all 675 | // definitions in a single table. Now, we would have to look on all 676 | // files to get the same result. We could look in the includers of the 677 | // headers included. Return nothing for now. 678 | return nil, fmt.Errorf("Definition not found") 679 | } 680 | 681 | func (db *symbolsDB) PrintAndCheckSymbolsTUDB(inputPath string) error { 682 | path := filepath.Clean(inputPath) 683 | tudb, err := db.GetSymbolsTUDB(getStringEncode(path)) 684 | if err != nil { 685 | return err 686 | } 687 | 688 | tudb.printAndCheck() 689 | 690 | return nil 691 | } 692 | 693 | ///// TU Symbol methods 694 | 695 | func newSymbolsTUDB(file string, mtime time.Time) *symbolsTUDB { 696 | return &symbolsTUDB{ 697 | File: file, 698 | Mtime: mtime, 699 | 700 | SymLoc: make(map[symbolLoc]symbolID), 701 | SymData: make(map[symbolID]symbolData), 702 | Headers: make(map[fileID]time.Time), 703 | Includers: make(map[fileID]bool), 704 | 705 | headersTUDB: make(map[string]bool), 706 | } 707 | } 708 | 709 | func loadSymbolsTUDB(dbPath string) (*symbolsTUDB, error) { 710 | var tudb symbolsTUDB 711 | 712 | dbFile, err := os.Open(dbPath) 713 | if err != nil { 714 | return nil, err 715 | } 716 | defer dbFile.Close() 717 | 718 | dec := gob.NewDecoder(dbFile) 719 | 720 | err = dec.Decode(&tudb) 721 | if err != nil { 722 | return nil, err 723 | } 724 | 725 | return &tudb, nil 726 | } 727 | 728 | func (db *symbolsTUDB) SaveSymbolsTUDB(path string) error { 729 | flags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC 730 | dbFile, err := os.OpenFile(path, flags, 0644) 731 | if err != nil { 732 | return err 733 | } 734 | defer dbFile.Close() 735 | 736 | enc := gob.NewEncoder(dbFile) 737 | 738 | err = enc.Encode(db) 739 | if err != nil { 740 | return err 741 | } 742 | 743 | return nil 744 | } 745 | 746 | func (db *symbolsTUDB) getSymbolData(id symbolID, name string) symbolData { 747 | data, exist := db.SymData[id] 748 | if !exist { 749 | data = symbolData{ 750 | Name: name, 751 | Uses: []symbolUse{}, 752 | Decls: []symbolLoc{}, 753 | } 754 | } 755 | 756 | return data 757 | } 758 | 759 | func (db *symbolsTUDB) insertSymbolDeclWithDef(sym, def *symbolInfo) { 760 | id := getStringEncode(sym.usr) 761 | symLoc := getSymbolLoc(&sym.loc) 762 | 763 | data := db.getSymbolData(id, sym.name) 764 | data.Decls = append(data.Decls, *symLoc) 765 | if def != nil { 766 | data.DefAvail = true 767 | data.Def = *getSymbolLoc(&def.loc) 768 | } 769 | 770 | db.SymLoc[*symLoc] = id 771 | db.SymData[id] = data 772 | } 773 | 774 | func (db *symbolsTUDB) InsertSymbolDecl(sym *symbolInfo) { 775 | db.insertSymbolDeclWithDef(sym, nil) 776 | } 777 | 778 | func (db *symbolsTUDB) InsertSymbolDeclWithDef(sym, def *symbolInfo) { 779 | db.insertSymbolDeclWithDef(sym, def) 780 | } 781 | 782 | func (db *symbolsTUDB) InsertSymbolUse(sym, dec *symbolInfo, funcCall bool) { 783 | if dec == nil { 784 | log.Println("use without decl, ignoring", sym) 785 | return 786 | } 787 | 788 | id := getStringEncode(dec.usr) 789 | symLoc := getSymbolLoc(&sym.loc) 790 | data := db.getSymbolData(id, sym.name) 791 | 792 | if _, exist := db.SymLoc[*symLoc]; exist { 793 | // The current symbol location was already registered. This 794 | // could be for two reasons: 795 | 796 | // 1. (TODO) A macro expanded in this location 797 | if db.SymLoc[*symLoc] != id { 798 | //symLoc = &db.SymData[db.SymLoc[*symLoc]].Decls[0] 799 | return 800 | } 801 | 802 | // 2. A call expression that is also a referenced symbol 803 | if len(data.Uses) > 0 { 804 | lastUse := &data.Uses[len(data.Uses)-1] 805 | if lastUse.Loc == *symLoc { 806 | lastUse.FuncCall = lastUse.FuncCall || funcCall 807 | return 808 | } 809 | } 810 | 811 | } 812 | 813 | data.Uses = append(data.Uses, symbolUse{ 814 | Loc: *symLoc, 815 | FuncCall: funcCall, 816 | }) 817 | 818 | db.SymLoc[*symLoc] = id 819 | db.SymData[id] = data 820 | } 821 | 822 | func (db *symbolsTUDB) InsertHeader(inclPath string, headFile clang.File) { 823 | var headModTime time.Time 824 | var headPath string 825 | if headFile.Name() == "" { 826 | headModTime = time.Time{} 827 | headPath = nonExistingHeaderName(filepath.Clean(inclPath)) 828 | } else { 829 | headModTime = headFile.Time() 830 | headPath = filepath.Clean(headFile.Name()) 831 | } 832 | db.Headers[getStringEncode(headPath)] = headModTime 833 | db.headersTUDB[headPath] = true 834 | } 835 | 836 | func (db *symbolsTUDB) TempSaveDB() error { 837 | tmpFile, err := ioutil.TempFile(dbDirTmp, "") 838 | if err != nil { 839 | return err 840 | } 841 | defer tmpFile.Close() 842 | 843 | db.tmpFile = tmpFile.Name() 844 | err = db.SaveSymbolsTUDB(db.tmpFile) 845 | if err != nil { 846 | return err 847 | } 848 | 849 | return nil 850 | } 851 | 852 | func (db *symbolsTUDB) check() { 853 | // check sym data 854 | for _, data := range db.SymData { 855 | for _, use := range data.Uses { 856 | if _, exists := db.SymLoc[use.Loc]; !exists { 857 | log.Println("Use in SymData not in DB!", use) 858 | } 859 | } 860 | } 861 | 862 | // check uses 863 | for loc, id := range db.SymLoc { 864 | if _, exists := db.SymData[id]; !exists { 865 | log.Println("Use without data!", loc, id) 866 | } 867 | } 868 | } 869 | 870 | func (db *symbolsTUDB) printAndCheck() { 871 | if len(db.SymData) > 0 { 872 | fmt.Println("Data:") 873 | for id, data := range db.SymData { 874 | fmt.Println(id, "->") 875 | fmt.Println("\tName:", data.Name) 876 | fmt.Println("\tDefAvail:", data.DefAvail) 877 | fmt.Println("\tDef:", data.Def) 878 | fmt.Println("\tDecls:") 879 | for _, decl := range data.Decls { 880 | fmt.Println("\t\t", decl) 881 | } 882 | fmt.Println("\tUses:") 883 | for _, use := range data.Uses { 884 | fmt.Println("\t\t", use) 885 | } 886 | } 887 | fmt.Println("Loc:") 888 | for loc, id := range db.SymLoc { 889 | fmt.Println("\t", loc, "->", id) 890 | } 891 | db.check() 892 | } 893 | 894 | if len(db.Includers) > 0 { 895 | fmt.Println("Includers:") 896 | for fileID := range db.Includers { 897 | fmt.Println("\t", fileID) 898 | } 899 | } 900 | } 901 | -------------------------------------------------------------------------------- /test/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "log" 21 | "net" 22 | "net/rpc" 23 | "net/rpc/jsonrpc" 24 | ) 25 | 26 | func main() { 27 | conn, err := net.Dial("unix", "/tmp/navc.sock") 28 | if err != nil { 29 | log.Fatal("dial socket", err) 30 | } 31 | defer conn.Close() 32 | 33 | codec := jsonrpc.NewClientCodec(conn) 34 | defer codec.Close() 35 | 36 | client := rpc.NewClientWithCodec(codec) 37 | defer client.Close() 38 | 39 | // sample call 40 | args := Symbol{"", "", "sample/a.c", 16, 2} 41 | var reply Symbol 42 | err = client.Call("RequestHandler.GetSymbolDecl", 43 | &args, 44 | &reply) 45 | if err != nil { 46 | log.Fatal("calling ", err) 47 | } 48 | 49 | log.Println(reply) 50 | } 51 | --------------------------------------------------------------------------------