Merge lp:~ben-hutchings/ensoft-sextant/wierd-names-clean into lp:ensoft-sextant

Proposed by Ben Hutchings
Status: Superseded
Proposed branch: lp:~ben-hutchings/ensoft-sextant/wierd-names-clean
Merge into: lp:ensoft-sextant
Diff against target: 1374 lines (+494/-427)
11 files modified
resources/sextant/web/interface.html (+10/-3)
resources/sextant/web/queryjavascript.js (+7/-2)
src/sextant/db_api.py (+248/-99)
src/sextant/export.py (+9/-5)
src/sextant/objdump_parser.py (+68/-18)
src/sextant/test_db.py (+97/-0)
src/sextant/test_db_api.py (+0/-275)
src/sextant/test_parser.py (+21/-17)
src/sextant/test_resources/parser_test.dump (+25/-0)
src/sextant/update_db.py (+3/-3)
src/sextant/web/server.py (+6/-5)
To merge this branch: bzr merge lp:~ben-hutchings/ensoft-sextant/wierd-names-clean
Reviewer Review Type Date Requested Status
Robert Pending
Review via email: mp+242751@code.launchpad.net

This proposal has been superseded by a proposal from 2014-11-25.

Commit message

Merge functions which end in '.' or '..<digit>'.

Description of the change

Merge functions which end in '.' or '..<digit>'.

To post a comment you must log in.
64. By Ben Hutchings

merge from rel-merge markups

65. By Ben Hutchings

extended tests, replaced test_all.sh with test_all.py which also generates code coverage reports for the tested modeuls

66. By Ben Hutchings

whitespace fix

67. By Ben Hutchings

fixed bug with get_all_functions_calling

68. By Ben Hutchings

very minor change to the default objdump_parser print output format to include more information added since this was originally written

69. By Ben Hutchings

doc comment fix

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'resources/sextant/web/interface.html'
--- resources/sextant/web/interface.html 2014-11-13 10:47:34 +0000
+++ resources/sextant/web/interface.html 2014-11-25 10:17:59 +0000
@@ -27,8 +27,8 @@
27 All functions calling specific function</option>27 All functions calling specific function</option>
28 <option value="functions_called_by">28 <option value="functions_called_by">
29 All functions called by a specific function</option>29 All functions called by a specific function</option>
30 <!--option value="all_call_paths"> REMOVED AS THIS IS SLOW FOR IOS30 <option value="all_call_paths">
31 All function call paths between two functions</option-->31 All function call paths between two functions</option>
32 <option value="shortest_call_path">32 <option value="shortest_call_path">
33 Shortest path between two functions</option>33 Shortest path between two functions</option>
34 <option value="function_names">34 <option value="function_names">
@@ -39,7 +39,14 @@
39 <input type="checkbox" id="suppress_common" value="True"></input>39 <input type="checkbox" id="suppress_common" value="True"></input>
40 Suppress common functions?40 Suppress common functions?
41 </label>41 </label>
4242 <label>
43 <input type="checkbox" id="limit_internal" value="True"></input>
44 Limit to internal calls?
45 </label>
46 <label>
47 <input type="number" id="max_depth" value="1" min="0" max="10"></input>
48 Maximum call depth.
49 </label>
43 <button class="button" style="float:right; margin: 1px 20px -1px 0;" onclick="execute_query()">Run Query</button>50 <button class="button" style="float:right; margin: 1px 20px -1px 0;" onclick="execute_query()">Run Query</button>
44 </div>51 </div>
45 <div id="toolbar-row2" style="margin-left: 234px;">52 <div id="toolbar-row2" style="margin-left: 234px;">
4653
=== modified file 'resources/sextant/web/queryjavascript.js'
--- resources/sextant/web/queryjavascript.js 2014-11-21 12:50:51 +0000
+++ resources/sextant/web/queryjavascript.js 2014-11-25 10:17:59 +0000
@@ -157,7 +157,7 @@
157 } else {157 } else {
158 //If not function names we will want a graph as an output; 158 //If not function names we will want a graph as an output;
159 //url returns svg file of graph.159 //url returns svg file of graph.
160 // We use a random number argument to prevent caching.160 // We use a random number argument to prevent caching.
161 var string = "/output_graph.svg?stop_cache=" + String(Math.random()) + "&program_name=" + 161 var string = "/output_graph.svg?stop_cache=" + String(Math.random()) + "&program_name=" +
162 document.getElementById("program_name").value + 162 document.getElementById("program_name").value +
163 "&query=" + query_id + "&func1=";163 "&query=" + query_id + "&func1=";
@@ -165,8 +165,13 @@
165 "&func2=" + document.getElementById("function_2").value;165 "&func2=" + document.getElementById("function_2").value;
166 string = string + "&suppress_common=" + 166 string = string + "&suppress_common=" +
167 document.getElementById('suppress_common').checked.toString();167 document.getElementById('suppress_common').checked.toString();
168 string = string + "&limit_internal=" +
169 document.getElementById('limit_internal').checked.toString();
170 string = string + "&max_depth=" +
171 document.getElementById('max_depth').value.toString();
172 }
173
168174
169 }
170 var xmlhttp = new XMLHttpRequest();175 var xmlhttp = new XMLHttpRequest();
171 xmlhttp.open("GET", string, true);176 xmlhttp.open("GET", string, true);
172 xmlhttp.send();177 xmlhttp.send();
173178
=== modified file 'src/sextant/db_api.py'
--- src/sextant/db_api.py 2014-11-19 10:40:24 +0000
+++ src/sextant/db_api.py 2014-11-25 10:17:59 +0000
@@ -162,7 +162,7 @@
162 headers=['name', 'type', 'file'], 162 headers=['name', 'type', 'file'],
163 max_rows=5000)163 max_rows=5000)
164 self.call_writer = CSVWriter(tmp_path.format('calls'), 164 self.call_writer = CSVWriter(tmp_path.format('calls'),
165 headers=['caller', 'callee'], 165 headers=['caller', 'callee', 'is_internal'],
166 max_rows=5000)166 max_rows=5000)
167167
168 # Define the queries we use to upload the functions and calls.168 # Define the queries we use to upload the functions and calls.
@@ -173,14 +173,26 @@
173 ' CREATE (n)-[:subject]->(m:func {{name: line.name,'173 ' CREATE (n)-[:subject]->(m:func {{name: line.name,'
174 ' id: lineid, type: line.type, file: line.file}})')174 ' id: lineid, type: line.type, file: line.file}})')
175 175
176 self.add_call_query = (' USING PERIODIC COMMIT 250'176 self.add_internal_call_query = (' USING PERIODIC COMMIT 250'
177 ' LOAD CSV WITH HEADERS FROM "file:{}" AS line'177 ' LOAD CSV WITH HEADERS FROM "file:{}" AS line'
178 ' MATCH (p:program {{name: "{}"}})'178 ' WITH line WHERE line.is_internal = "True"'
179 ' MATCH (p)-[:subject]->(n:func {{name: line.caller}})'179 ' MATCH (p:program {{name: "{}"}})'
180 ' USING INDEX n:func(name)'180 ' MATCH (p)-[:subject]->(n:func {{name: line.caller}})'
181 ' MATCH (p)-[:subject]->(m:func {{name: line.callee}})' 181 ' USING INDEX n:func(name)'
182 ' USING INDEX m:func(name)'182 ' MATCH (p)-[:subject]->(m:func {{name: line.callee}})'
183 ' CREATE (n)-[r:calls]->(m)')183 ' USING INDEX m:func(name)'
184 ' CREATE (n)-[r:internal]->(m)')
185
186 self.add_external_call_query = (' USING PERIODIC COMMIT 250'
187 ' LOAD CSV WITH HEADERS FROM "file:{}" AS line'
188 ' WITH line WHERE line.is_internal <> "True"'
189 ' MATCH (p:program {{name: "{}"}})'
190 ' MATCH (p)-[:subject]->(n:func {{name: line.caller}})'
191 ' USING INDEX n:func(name)'
192 ' MATCH (p)-[:subject]->(m:func {{name: line.callee}})'
193 ' USING INDEX m:func(name)'
194 ' CREATE (n)-[r:external]->(m)')
195
184196
185 self.add_program_query = ('CREATE (p:program {{name: "{}", uploader: "{}", '197 self.add_program_query = ('CREATE (p:program {{name: "{}", uploader: "{}", '
186 ' uploader_id: "{}", date: "{}",'198 ' uploader_id: "{}", date: "{}",'
@@ -221,7 +233,7 @@
221 """233 """
222 self.func_writer.write(name, typ, source)234 self.func_writer.write(name, typ, source)
223235
224 def add_call(self, caller, callee):236 def add_call(self, caller, callee, is_internal=False):
225 """237 """
226 Add a function call.238 Add a function call.
227239
@@ -230,8 +242,11 @@
230 The name of the function making the call.242 The name of the function making the call.
231 callee:243 callee:
232 The name of the function called.244 The name of the function called.
245 is_internal:
246 True if the caller's source file is the same as callee's,
247 unless either one is 'unknown'.
233 """248 """
234 self.call_writer.write(caller, callee)249 self.call_writer.write(caller, callee, is_internal)
235250
236251
237 def _copy_local_to_remote_tmp_dir(self):252 def _copy_local_to_remote_tmp_dir(self):
@@ -257,7 +272,6 @@
257 remote_paths:272 remote_paths:
258 A list of the paths of the remote fils.273 A list of the paths of the remote fils.
259 """274 """
260
261 def try_rmdir(path):275 def try_rmdir(path):
262 # Helper function to try and remove a directory, silently276 # Helper function to try and remove a directory, silently
263 # fail if it contains files, otherwise raise the exception.277 # fail if it contains files, otherwise raise the exception.
@@ -270,6 +284,7 @@
270 else:284 else:
271 raise e285 raise e
272286
287
273 print('Cleaning temporary files...', end='')288 print('Cleaning temporary files...', end='')
274 file_paths = list(itertools.chain(self.func_writer.file_iter(),289 file_paths = list(itertools.chain(self.func_writer.file_iter(),
275 self.call_writer.file_iter()))290 self.call_writer.file_iter()))
@@ -279,7 +294,7 @@
279294
280 try_rmdir(self._tmp_dir)295 try_rmdir(self._tmp_dir)
281 try_rmdir(TMP_DIR)296 try_rmdir(TMP_DIR)
282 297
283 self._ssh.remove_from_tmp_dir(remote_paths)298 self._ssh.remove_from_tmp_dir(remote_paths)
284299
285 print('done.')300 print('done.')
@@ -336,9 +351,12 @@
336 tx.commit()351 tx.commit()
337352
338 # Create the functions.353 # Create the functions.
339 for files, query, descr in zip((remote_funcs, remote_calls),354 file_lists = (remote_funcs, remote_calls, remote_calls)
340 (self.add_func_query, self.add_call_query),355 queries = (self.add_func_query, self.add_internal_call_query,
341 ('funcs', 'calls')):356 self.add_external_call_query)
357 descrs = ('functions', 'internal calls', 'external calls')
358
359 for files, query, descr in zip(file_lists, queries, descrs):
342 start = time()360 start = time()
343 for i, path in enumerate(files):361 for i, path in enumerate(files):
344 completed = int(100*float(i+1)/len(files))362 completed = int(100*float(i+1)/len(files))
@@ -377,7 +395,7 @@
377 Loop over all functions: increment the called-by count of their callees.395 Loop over all functions: increment the called-by count of their callees.
378 """396 """
379 for func in self.functions:397 for func in self.functions:
380 for called in func.functions_i_call:398 for called, is_internal in func.functions_i_call:
381 called.number_calling_me += 1399 called.number_calling_me += 1
382400
383 def _rest_node_output_to_graph(self, rest_output):401 def _rest_node_output_to_graph(self, rest_output):
@@ -442,8 +460,8 @@
442 for_query=True)460 for_query=True)
443 for index in result:461 for index in result:
444 q = ("START n=node({})"462 q = ("START n=node({})"
445 "MATCH n-[calls:calls]->(m)"463 "MATCH n-[r]->(m)"
446 "RETURN n.name, m.name").format(result[index][2])464 "RETURN n.name, m.name, type(r) = 'internal'").format(result[index][2])
447 new_tx.append(q)465 new_tx.append(q)
448466
449 logging.debug('exec')467 logging.debug('exec')
@@ -454,13 +472,17 @@
454472
455 for call_list in results:473 for call_list in results:
456 if call_list:474 if call_list:
457 # call_list has element 0 being an arbitrary call this475 elements = call_list.elements
458 # function makes; element 0 of that call is the name of the476 # elements is a list of lists of form:
459 # function itself. Think {{'orig', 'b'}, {'orig', 'c'}}.477 # [[caller1, callee1, is_internal],
460 orig = call_list[0][0]478 # [caller1, callee2, is_internal],
461 # result['orig'] is [<Function>, ('callee1','callee2')]479 # ...]
462 result[orig][1] |= set(list(zip(*call_list.elements))[1])480
463 # recall: set union is denoted by |481 caller = elements[0][0]
482 callees, is_internals = zip(*elements)[1:]
483
484 # result[caller] is [<Function>, <set of callee, is_internal tuples>]
485 result[caller][1] |= set(zip(callees, is_internals))
464486
465 else:487 else:
466 # We don't have a parent database connection.488 # We don't have a parent database connection.
@@ -478,8 +500,8 @@
478 named_function = lambda name: result[name][0] if name in result else None500 named_function = lambda name: result[name][0] if name in result else None
479501
480 for function, calls, node_id in result.values():502 for function, calls, node_id in result.values():
481 what_i_call = [named_function(name)503 what_i_call = [(named_function(name), is_internal)
482 for name in calls504 for name, is_internal in calls
483 if named_function(name) is not None]505 if named_function(name) is not None]
484 function.functions_i_call = what_i_call506 function.functions_i_call = what_i_call
485507
@@ -707,7 +729,7 @@
707 func_count, call_count = tx.commit()[0].elements[0]729 func_count, call_count = tx.commit()[0].elements[0]
708730
709 del_call_query = ('OPTIONAL MATCH (p:program {{name: "{}"}})'731 del_call_query = ('OPTIONAL MATCH (p:program {{name: "{}"}})'
710 '-[:subject]->(f:func)-[c:calls]->()'732 '-[:subject]->(f:func)-[c]->()'
711 ' WITH c LIMIT 5000 DELETE c RETURN count(distinct(c))'733 ' WITH c LIMIT 5000 DELETE c RETURN count(distinct(c))'
712 .format(program_name))734 .format(program_name))
713735
@@ -817,40 +839,32 @@
817 result = self._db.query(q, returns=neo4jrestclient.Node)839 result = self._db.query(q, returns=neo4jrestclient.Node)
818 return bool(result)840 return bool(result)
819841
820 def check_function_exists(self, program_name, function_name):
821 """
822 Execute query to check whether a function with the given name exists.
823 We only check for functions which are children of a program with the
824 given program_name.
825 :param program_name: string name of the program within which to check
826 :param function_name: string name of the function to check for existence
827 :return: bool(names validate correctly, and function exists in program)
828 """
829 if not validate_query(program_name):
830 return False
831
832 pmatch = '(:program {{name: "{}"}})'.format(program_name)
833 fmatch = '(f:func {{name: "{}"}})'.format(function_name)
834 # be explicit about index usage
835 q = (' MATCH {}-[:subject]->{} USING INDEX f:func(name)'
836 ' RETURN f LIMIT 1'.format(pmatch, fmatch))
837
838 # result will be an empty list if the function was not found
839 result = self._db.query(q, returns=neo4jrestclient.Node)
840 return bool(result)
841
842 def get_function_names(self, program_name, search=None, max_funcs=None):842 def get_function_names(self, program_name, search=None, max_funcs=None):
843 """843 """
844 Execute query to retrieve a list of all functions in the program.844 Execute query to retrieve a list of all functions in the program.
845 Any of the output names can be used verbatim in any SextantConnection845 Any of the output names can be used verbatim in any SextantConnection
846 method which requires a function-name input.846 method which requires a function-name input.
847 :param program_name: name of the program whose functions to retrieve847
848 :return: None if program_name doesn't exist in the remote database,848 Return:
849 a set of function-name strings otherwise.849 None if program_name doesn't exist in the remote database,
850 a set of function-name strings otherwise.
851 Arguments:
852 program_name:
853 The name of the program to query.
854
855 search:
856 A string of form <name_match>:<file_match>, where at least
857 one of name_match and file_match is provided, and each may be a
858 comma separated list of strings containing wildcard '.*'
859 sequences.
860
861 max_funcs:
862 An integer limiting the number of functions returned by this
863 method.
850 """864 """
851865
852 if not validate_query(program_name):866 if not validate_query(program_name):
853 return set()867 return None
854868
855 limit = "LIMIT {}".format(max_funcs) if max_funcs else ""869 limit = "LIMIT {}".format(max_funcs) if max_funcs else ""
856870
@@ -859,8 +873,8 @@
859 ' RETURN f.name {}').format(program_name, limit)873 ' RETURN f.name {}').format(program_name, limit)
860 else:874 else:
861 q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)'875 q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)'
862 ' WHERE f.name =~ ".*{}.*" RETURN f.name {}'876 ' {} RETURN f.name {}'
863 .format(program_name, search, limit))877 .format(program_name, self.get_query('f', search), limit))
864 return {func[0] for func in self._db.query(q)}878 return {func[0] for func in self._db.query(q)}
865879
866 @staticmethod880 @staticmethod
@@ -888,6 +902,7 @@
888 etc.902 etc.
889 903
890 """904 """
905
891 if ':' in search:906 if ':' in search:
892 func_subs, file_subs = search.split(':')907 func_subs, file_subs = search.split(':')
893 else:908 else:
@@ -928,49 +943,140 @@
928943
929 return query_str944 return query_str
930945
931 def get_all_functions_called(self, program_name, function_calling):946 def get_all_functions_called(self, program_name, function_calling,
932 """947 limit_internal, max_depth):
933 Execute query to find all functions called by a function (indirectly).948 """
934 If the given function is not present in the program, returns None;949 Return the subtrees of the callgraph rooted at the specified functions.
935 likewise if the program_name does not exist.950
936 :param program_name: a string name of the program we wish to query under951 Optionally limit to a maximum call depth, or to only internal (same
937 :param function_calling: string name of a function whose children to find952 source file) calls. In the case of the latter, also include a single
938 :return: FunctionQueryResult, maximal subgraph rooted at function_calling953 extra hop into external functions.
939 """954
955 If the function has no calls, it will be returned alone.
956
957 Return:
958 None if program_name doesn't exist in the remote database,
959 a FunctionQueryResult containing the nodes and relationships
960 otherwise.
961 Arguments:
962 program_name:
963 The name of the program to query.
964
965 function_calling:
966 A string of form <name_match>:<file_match>, where at least
967 one of name_match and file_match is provided, and each may be a
968 comma separated list of strings containing wildcard '.*'
969 sequences. Specifies the list of subtree roots.
970
971 limit_internal:
972 If true, only explore internal calls, but also add one extra
973 level (above max_depth) along external calls.
974
975 max_depth:
976 A STRING containing an integer which will limit the depth
977 of the subtrees. '0' corresponds to unlimited depth.
978 """
979
980 if not validate_query(program_name):
981 return None
982
983 max_depth = max_depth or '1'
984
940 q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(f:func) {}'985 q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(f:func) {}'
941 ' MATCH (f)-[:calls]->(g:func) RETURN distinct f, g'986 ' MATCH (f)-[{}*0..{}]->(g:func)'
942 .format(program_name, SextantConnection.get_query('f', function_calling)))987 .format(program_name, SextantConnection.get_query('f', function_calling),
988 ':internal' if limit_internal=='true' else '',
989 max_depth if int(max_depth) > 0 else ''))
990
991 if limit_internal == 'true':
992 q += (' WITH f, g MATCH (g)-[:external*0..1]->(h)'
993 ' RETURN distinct f, h')
994 else:
995 q += ' RETURN distinct f, g'
943996
944 return self._execute_query(program_name, q)997 return self._execute_query(program_name, q)
945998
946 def get_all_functions_calling(self, program_name, function_called):999 def get_all_functions_calling(self, program_name, function_called,
947 """1000 limit_internal, max_depth):
948 Execute query to find all functions which call a function (indirectly).1001 """
949 If the given function is not present in the program, returns None;1002 Return functions calling the specified functions.
950 likewise if the program_name does not exist.1003
951 :param program_name: a string name of the program we wish to query1004 Optionally limit to a maximum call depth, or to only internal (same
952 :param function_called: string name of a function whose parents to find1005 source file) calls.
953 :return: FunctionQueryResult, maximal connected subgraph with leaf function_called1006
954 """1007 If the function is not called, return it alone.
1008
1009 Return:
1010 None if program_name doesn't exist in the remote database,
1011 a FunctionQueryResult containing the nodes and relationships
1012 otherwise.
1013 Arguments:
1014 program_name:
1015 The name of the program to query.
1016
1017 function_called:
1018 A string of form <name_match>:<file_match>, where at least
1019 one of name_match and file_match is provided, and each may be a
1020 comma separated list of strings containing wildcard '.*'
1021 sequences. Specifies the list of functions to match.
1022
1023 limit_internal:
1024 If true, only explore internal calls.
1025
1026 max_depth:
1027 A STRING containing an integer which will limit the depth
1028 of the subtrees. '0' corresponds to unlimited depth.
1029 """
1030
1031 if not validate_query(program_name):
1032 return None
1033
1034 max_depth = max_depth or '1'
9551035
956 q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(g:func) {}'1036 q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(g:func) {}'
957 ' MATCH (f)-[:calls]->(g)'1037 ' MATCH (f)-[{}*0..{}]->(g)'
958 ' RETURN distinct f, g')1038 ' RETURN distinct f, g')
959 q = q.format(program_name, SextantConnection.get_query('g', function_called), program_name)1039 q = q.format(program_name, SextantConnection.get_query('g', function_called),
1040 ':internal' if limit_internal=='true' else ':internal|external',
1041 max_depth if int(max_depth) > 0 else '')
9601042
961 return self._execute_query(program_name, q)1043 return self._execute_query(program_name, q)
9621044
963 def get_call_paths(self, program_name, function_calling, function_called):1045 def get_call_paths(self, program_name, function_calling, function_called,
964 """1046 limit_internal, max_depth):
965 Execute query to find all possible routes between two specific nodes.1047 """
966 If the given functions are not present in the program, returns None;1048 Return all call paths between the sets of functions specified by the
967 ditto if the program_name does not exist.1049 search strings function_calling and function_called.
968 :param program_name: string program name1050
969 :param function_calling: string1051 Optionally limit to a maximum call depth, or to only internal (same
970 :param function_called: string1052 source file) calls.
971 :return: FunctionQueryResult, the union of all subgraphs reachable by1053
972 adding a source at function_calling and a sink at function_called.1054 Return:
973 """1055 None if program_name doesn't exist in the remote database,
1056 a FunctionQueryResult object containing the nodes and relationships
1057 otherwise.
1058 Arguments:
1059 program_name:
1060 The name of the program to query.
1061
1062 function_calling, function_called:
1063 A string of form <name_match>:<file_match>, where at least
1064 one of name_match and file_match is provided, and each may be a
1065 comma separated list of strings containing wildcard '.*'
1066 sequences. Specifies the list of functions to match.
1067
1068 limit_internal:
1069 If true, only explore internal calls.
1070
1071 max_depth:
1072 A STRING containing an integer which will limit the depth
1073 of the subtrees. '0' corresponds to unlimited depth.
1074 """
1075
1076 if not validate_query(program_name):
1077 return None
1078
1079 max_depth = max_depth or '1'
9741080
975 if not self.check_program_exists(program_name):1081 if not self.check_program_exists(program_name):
976 return None1082 return None
@@ -981,18 +1087,25 @@
981 q = (' MATCH (p:program {{name: "{}"}})'1087 q = (' MATCH (p:program {{name: "{}"}})'
982 ' MATCH (p)-[:subject]->(start:func) {} WITH start, p'1088 ' MATCH (p)-[:subject]->(start:func) {} WITH start, p'
983 ' MATCH (p)-[:subject]->(end:func) {} WITH start, end'1089 ' MATCH (p)-[:subject]->(end:func) {} WITH start, end'
984 ' MATCH path=(start)-[:calls*]->(end)'1090 ' MATCH path=(start)-[{}*0..{}]->(end)'
985 ' WITH DISTINCT nodes(path) AS result'1091 ' WITH DISTINCT nodes(path) AS result'
986 ' UNWIND result AS answer'1092 ' UNWIND result AS answer'
987 ' RETURN answer')1093 ' RETURN answer')
988 q = q.format(program_name, start_q, end_q)1094 q = q.format(program_name, start_q, end_q, ':internal' if limit_internal=='true' else '',
1095 max_depth if int(max_depth) > 0 else '')
989 return self._execute_query(program_name, q)1096 return self._execute_query(program_name, q)
9901097
991 def get_whole_program(self, program_name):1098 def get_whole_program(self, program_name):
992 """Execute query to find the entire program with a given name.1099 """
993 If the program is not present in the remote database, returns None.1100 Return the full call graph of the program.
994 :param: program_name: a string name of the program we wish to return.1101
995 :return: a FunctionQueryResult consisting of the program graph.1102 Return:
1103 None if program_name doesn't exist in the remote database,
1104 a FunctionQueryResult object containing all of the program nodes
1105 and calls otherwise.
1106 Arguments:
1107 program_name:
1108 The name of the program to query.
996 """1109 """
9971110
998 if not self.check_program_exists(program_name):1111 if not self.check_program_exists(program_name):
@@ -1002,7 +1115,9 @@
1002 ' RETURN (f)'.format(program_name))1115 ' RETURN (f)'.format(program_name))
1003 return self._execute_query(program_name, q)1116 return self._execute_query(program_name, q)
10041117
1005 def get_shortest_path_between_functions(self, program_name, function_calling, function_called):1118 def get_shortest_path_between_functions(self, program_name,
1119 function_calling, function_called,
1120 limit_internal, max_depth):
1006 """1121 """
1007 Execute query to get a single, shortest, path between two functions.1122 Execute query to get a single, shortest, path between two functions.
1008 :param program_name: string name of the program we wish to search under1123 :param program_name: string name of the program we wish to search under
@@ -1010,6 +1125,39 @@
1010 :param func2: the name of the function at which to terminate the path1125 :param func2: the name of the function at which to terminate the path
1011 :return: FunctionQueryResult shortest path between func1 and func2.1126 :return: FunctionQueryResult shortest path between func1 and func2.
1012 """1127 """
1128 """
1129 Return all shortest paths between the sets of functions specified.
1130
1131 Optionally limit to a maximum call depth, or to only internal (same
1132 source file) calls.
1133
1134 Return:
1135 None if program_name doesn't exist in the remote database,
1136 a FunctionQueryResult object containing the nodes and relationships
1137 otherwise.
1138 Arguments:
1139 program_name:
1140 The name of the program to query.
1141
1142 function_calling, function_called:
1143 A string of form <name_match>:<file_match>, where at least
1144 one of name_match and file_match is provided, and each may be a
1145 comma separated list of strings containing wildcard '.*'
1146 sequences. Specifies the list of functions to match.
1147
1148 limit_internal:
1149 If true, only explore internal calls.
1150
1151 max_depth:
1152 A STRING containing an integer which will limit the depth
1153 of the subtrees. '0' corresponds to unlimited depth.
1154 """
1155
1156 if not self.check_program_exists(program_name):
1157 return None
1158
1159 max_depth = max_depth or '1'
1160
1013 if not self.check_program_exists(program_name):1161 if not self.check_program_exists(program_name):
1014 return None1162 return None
10151163
@@ -1019,10 +1167,11 @@
1019 q = (' MATCH (p:program {{name: "{}"}})'1167 q = (' MATCH (p:program {{name: "{}"}})'
1020 ' MATCH (p)-[:subject]->(start:func) {} WITH start, p'1168 ' MATCH (p)-[:subject]->(start:func) {} WITH start, p'
1021 ' MATCH (p)-[:subject]->(end:func) {} WITH start, end'1169 ' MATCH (p)-[:subject]->(end:func) {} WITH start, end'
1022 ' MATCH path=shortestPath((start)-[:calls*]->(end))'1170 ' MATCH path=allShortestPaths((start)-[{}*..{}]->(end))'
1023 ' UNWIND nodes(path) AS answer'1171 ' UNWIND nodes(path) AS answer'
1024 ' RETURN answer')1172 ' RETURN answer')
1025 q = q.format(program_name, start_q, end_q)1173 q = q.format(program_name, start_q, end_q, ':internal' if limit_internal=='true' else '',
1174 max_depth if int(max_depth) > 0 else '')
10261175
1027 return self._execute_query(program_name, q)1176 return self._execute_query(program_name, q)
10281177
10291178
=== modified file 'src/sextant/export.py'
--- src/sextant/export.py 2014-11-19 10:32:34 +0000
+++ src/sextant/export.py 2014-11-25 10:17:59 +0000
@@ -71,9 +71,10 @@
71 if func_called == func:71 if func_called == func:
72 functions_called.remove(func_called)72 functions_called.remove(func_called)
7373
74 for func_called in func.functions_i_call:74 for func_called, is_internal in func.functions_i_call:
75 if not (suppress_common_nodes and func_called.is_common):75 if not (suppress_common_nodes and func_called.is_common):
76 output_str += ' "{}" -> "{}"\n'.format(func.name, func_called.name)76 color = 'black' if is_internal else 'red'
77 output_str += ' "{}" -> "{}" [color={}]\n'.format(func.name, func_called.name, color)
7778
78 output_str += '}'79 output_str += '}'
79 return output_str80 return output_str
@@ -140,18 +141,21 @@
140 </node>\n""".format(func.name, 20, len(display_func)*8, colour, display_func)141 </node>\n""".format(func.name, 20, len(display_func)*8, colour, display_func)
141142
142 functions_called = func.functions_i_call143 functions_called = func.functions_i_call
144 print(functions_called)
143 if remove_self_calls is True:145 if remove_self_calls is True:
144 #remove calls where a function calls itself146 #remove calls where a function calls itself
145 for func_called in functions_called:147 for func_called, is_internal, in functions_called:
146 if func_called == func:148 if func_called == func:
147 functions_called.remove(func_called)149 functions_called.remove(func_called)
148150
149 for callee in functions_called:151 for callee, is_internal in functions_called:
152 print(callee, is_internal)
153 color = "#000000" if is_internal else "#ff0000"
150 if callee not in commonly_called:154 if callee not in commonly_called:
151 if not(suppress_common_nodes and callee.is_common):155 if not(suppress_common_nodes and callee.is_common):
152 output_str += """<edge source="{}" target="{}"> <data key="d9">156 output_str += """<edge source="{}" target="{}"> <data key="d9">
153 <y:PolyLineEdge>157 <y:PolyLineEdge>
154 <y:LineStyle color="#000000" type="line" width="1.0"/>158 <y:LineStyle color="#ff0000" type="line" width="1.0"/>
155 <y:Arrows source="none" target="standard"/>159 <y:Arrows source="none" target="standard"/>
156 <y:BendStyle smoothed="false"/>160 <y:BendStyle smoothed="false"/>
157 </y:PolyLineEdge>161 </y:PolyLineEdge>
158162
=== modified file 'src/sextant/objdump_parser.py'
--- src/sextant/objdump_parser.py 2014-11-21 15:19:15 +0000
+++ src/sextant/objdump_parser.py 2014-11-25 10:17:59 +0000
@@ -43,11 +43,15 @@
43 function_ptr_count:43 function_ptr_count:
44 The number of function pointers that have been detected.44 The number of function pointers that have been detected.
45 _known_functions:45 _known_functions:
46 A set of the names of functions that have been46 A dict of the names of functions that have been
47 parsed - used to avoid registering a function multiple times.47 parsed - used to avoid registering a function multiple times
48 and to label calls as internal/external.
48 _partial_functions:49 _partial_functions:
49 A set of functions whose names we have seen but whose source50 A set of functions whose names we have seen but whose source
50 files we don't yet know.51 files we don't yet know.
52 _partial_calls:
53 A set of the (caller, callee) tuples representing calls between
54 a _partial_function and another function.
5155
52 """56 """
53 def __init__(self, file_path, file_object=None, 57 def __init__(self, file_path, file_object=None,
@@ -106,16 +110,18 @@
106 self.function_ptr_count = 0110 self.function_ptr_count = 0
107 111
108 # Avoid adding duplicate functions.112 # Avoid adding duplicate functions.
109 self._known_functions = set()113 self._known_functions = dict()
114 self._known_calls = set()
110 # Set of partially-parsed functions.115 # Set of partially-parsed functions.
111 self._partial_functions = set()116 self._partial_functions = set()
117 self._partial_calls = set()
112118
113 # By default print information to stdout.119 # By default print information to stdout.
114 def print_func(name, typ, source='unknown'):120 def print_func(name, typ, source='unknown'):
115 print('func {:25}{:15}{}'.format(name, typ, source))121 print('func {:25} {:15}{}'.format(name, typ, source))
116122
117 def print_call(caller, callee):123 def print_call(caller, callee, is_internal):
118 print('call {:25}{:25}'.format(caller, callee))124 print('call {:25} {:25}'.format(caller, callee))
119125
120 def print_started(parser):126 def print_started(parser):
121 print('parse started: {}[{}]'.format(self.path, ', '.join(self.sections)))127 print('parse started: {}[{}]'.format(self.path, ', '.join(self.sections)))
@@ -148,6 +154,7 @@
148 self._partial_functions.add(name)154 self._partial_functions.add(name)
149 elif source == 'unknown':155 elif source == 'unknown':
150 # Manually adding a stub function.156 # Manually adding a stub function.
157 self._known_functions[name] = source
151 self.add_function(name, 'stub', source)158 self.add_function(name, 'stub', source)
152 self.function_count += 1159 self.function_count += 1
153 elif name not in self._known_functions:160 elif name not in self._known_functions:
@@ -160,7 +167,7 @@
160 except KeyError:167 except KeyError:
161 pass168 pass
162169
163 self._known_functions.add(name)170 self._known_functions[name] = source
164 self.add_function(name, 'normal', source)171 self.add_function(name, 'normal', source)
165 self.function_count += 1172 self.function_count += 1
166173
@@ -168,15 +175,50 @@
168 """175 """
169 Add a function pointer.176 Add a function pointer.
170 """177 """
171 self.add_function(name, 'pointer')178 self.add_function(name, 'pointer', 'unknown')
172 self.function_count += 1179 self.function_count += 1
173180
174 def _add_call(self, caller, callee):181 def _add_call(self, caller, callee, force=False):
175 """182 """
176 Add a function call from caller to callee.183 Add a function call from caller to callee.
177 """184 """
178 self.add_call(caller, callee)185 if (caller, callee) in self._known_calls:
179 self.call_count += 1186 return
187
188 try:
189 files = self._known_functions
190 is_internal = (callee.startswith('func_ptr_')
191 or (files[caller] != 'unknown' and files[caller] == files[callee]))
192 self.add_call(caller, callee, is_internal)
193 self._known_calls.add((caller, callee))
194 self.call_count += 1
195 except KeyError:
196 if force:
197 self._add_function(callee, 'unknown')
198 self.add_call(caller, callee, False)
199 self._known_calls.add((caller, callee))
200 self.call_count += 1
201 print(caller, callee)
202 else:
203 self._partial_calls.add((caller, callee))
204
205 @staticmethod
206 def clean_id(function_identifier):
207 """
208 Clean the funciton identifier string.
209 """
210 # IOS builds add a __be_ (big endian) prefix to all functions,
211 # get rid of it if it is there,
212 if function_identifier.startswith('__be_'):
213 function_identifier = function_identifier[len('__be_'):]
214
215 # Some functions look like <identifier>. or <identifier>..<digit>
216 # - get rid of the extra bits here:
217 if '.' in function_identifier:
218 function_identifier = function_identifier.split('.')[0]
219
220 return function_identifier
221
180222
181 def parse(self):223 def parse(self):
182 """224 """
@@ -193,6 +235,10 @@
193 if to_add:235 if to_add:
194 file_line = line.startswith('/')236 file_line = line.startswith('/')
195 source = line.split(':')[0] if file_line else None237 source = line.split(':')[0] if file_line else None
238 if source:
239 # Prune out the relative parts of the filepath.
240 source = source.rsplit('./', 1)[-1]
241
196 self._add_function(current_function, source)242 self._add_function(current_function, source)
197 to_add = False243 to_add = False
198244
@@ -207,16 +253,15 @@
207 self.section_count += 1253 self.section_count += 1
208254
209 elif in_section:255 elif in_section:
256
257
210 if line.endswith('>:\n'):258 if line.endswith('>:\n'):
211 # '<address> <<function_identifier>>:\n'259 # '<address> <<function_identifier>>:\n'
212 # with <function_identifier> of form:260 # with <function_identifier> of form:
213 # <function_name>[@plt]261 # <function_name>[@plt]
214 function_identifier = line.split('<')[-1].split('>')[0]262 function_identifier = line.split('<')[-1].split('>')[0]
215263
216 # IOS builds add a __be_ (big endian) prefix to all functions,264 function_identifier = self.clean_id(function_identifier)
217 # get rid of it if it is there,
218 if function_identifier.startswith('__be_'):
219 function_identifier = function_identifier[len('__be_'):]
220265
221 if '@' in function_identifier:266 if '@' in function_identifier:
222 # Of form <function name>@<other stuff>.267 # Of form <function name>@<other stuff>.
@@ -227,6 +272,10 @@
227 # Flag function - we look for source on the next line.272 # Flag function - we look for source on the next line.
228 to_add = True273 to_add = True
229274
275 # If we have come to a new current_function, then we can
276 # forget about the calls we knew about from the last.
277 self._known_calls = set()
278
230 elif 'call ' in line or 'callq ' in line:279 elif 'call ' in line or 'callq ' in line:
231 # WHITESPACE to prevent picking up function names 280 # WHITESPACE to prevent picking up function names
232 # containing 'call'281 # containing 'call'
@@ -244,8 +293,8 @@
244 # from which we extract name293 # from which we extract name
245 callee_is_ptr = False294 callee_is_ptr = False
246 function_identifier = callee_info.lstrip('<').rstrip('>\n')295 function_identifier = callee_info.lstrip('<').rstrip('>\n')
247 if function_identifier.startswith('__be_'):296
248 function_identifier = function_identifier[len('__be_'):]297 function_identifier = self.clean_id(function_identifier)
249298
250 if '@' in function_identifier:299 if '@' in function_identifier:
251 callee = function_identifier.split('@')[0]300 callee = function_identifier.split('@')[0]
@@ -268,7 +317,8 @@
268317
269 for name in self._partial_functions:318 for name in self._partial_functions:
270 self._add_function(name, 'unknown')319 self._add_function(name, 'unknown')
271 320 for call in sorted(self._partial_calls, key=lambda el: el[0]):
321 self._add_call(*call, force=True)
272 322
273 self.finished()323 self.finished()
274324
275325
=== added file 'src/sextant/test_db.py'
--- src/sextant/test_db.py 1970-01-01 00:00:00 +0000
+++ src/sextant/test_db.py 2014-11-25 10:17:59 +0000
@@ -0,0 +1,97 @@
1#!/usr/bin/python
2# -----------------------------------------
3# Sextant
4# Copyright 2014, Ensoft Ltd.
5# Author: Patrick Stevens, James Harkin
6# -----------------------------------------
7#Testing module
8
9import unittest
10
11import db_api
12import update_db
13
14PNAME = 'tester-parser_test'
15NORMAL = {'main', 'normal', 'wierd$name', 'duplicates'}
16
17
18class TestFunctionQueryResults(unittest.TestCase):
19 @classmethod
20 def setUpClass(cls):
21 # we need to set up the remote database by using the neo4j_input_api
22 cls.remote_url = 'http://ensoft-sandbox:7474'
23 cls.connection = db_api.SextantConnection('ensoft-sandbox', 7474)
24
25 update_db.upload_program
26 update_db.upload_program(cls.connection, 'tester', 'test_resources/parser_test',
27 program_name=None, not_object_file=False, add_file_paths=True)
28
29 @classmethod
30 def tearDownClass(cls):
31 cls.connection.delete_program('tester-parser_test')
32 cls.connection.close()
33
34 def test_get_function_names(self):
35 get_names = self.connection.get_function_names
36 # Test getting all names
37 names = get_names(PNAME)
38 # Test file wildcard search
39 parser_names = get_names(PNAME, search=':.*parser_test.c')
40
41 self.assertTrue(names.issuperset(NORMAL))
42 self.assertEquals(len(names), 24)
43 self.assertEquals(parser_names, {u'main', u'normal', u'duplicates', u'wierd$name'})
44
45 # Test the wildcard matching
46 search = self.connection.get_function_names(PNAME, search='.*libc.*')
47 search_exp = {u'__libc_csu_init', u'__libc_csu_fini', u'__libc_start_main'}
48
49 self.assertEquals(search, search_exp)
50
51 # Test the limiting
52 too_many = self.connection.get_function_names(PNAME, max_funcs=3)
53 self.assertEquals(len(too_many), 3)
54
55 # Test empty for non-existant program
56 self.assertFalse(self.connection.get_function_names('blah blah blah'))
57
58 def test_get_all_functions_called(self):
59 get_fns = self.connection.get_all_functions_called
60
61 for depth, num in zip([0, 1, 2, 3], [8, 3, 8, 8]):
62 result = get_fns(PNAME, 'main', "false", str(depth)).functions
63 self.assertEquals(len(result), num, str(result))
64
65 for depth, num in zip([0, 1, 2, 3], [8, 4, 8, 8]):
66 # Limit to internal functions
67 # TODO this isn't a great test - need greater call depth
68 result = get_fns(PNAME, 'main', "true", str(depth)).functions
69 self.assertEquals(len(result), num)
70
71 def test_get_all_functions_calling(self):
72 get_fns = self.connection.get_all_functions_calling
73
74 for depth, num in zip(range(3), [4, 3, 4, 4]):
75 result = get_fns(PNAME, 'printf', limit_internal="false", max_depth=str(depth)).functions
76 self.assertEquals(len(result), num)
77
78 for depth, num in zip(range(3), [1, 1, 1, 1]):
79 result = get_fns(PNAME, 'printf', limit_internal="true", max_depth=str(depth)).functions
80 self.assertEquals(len(result), num)
81
82 def test_get_all_paths_between(self):
83 get_paths = self.connection.get_call_paths
84
85 result = {f.name for f in get_paths(PNAME, 'main', 'wierd$name', 'false', '0').functions}
86 exp = {'main', 'normal', 'duplicates', 'wierd$name'}
87 self.assertEquals(result, exp)
88
89 def test_get_shortest_paths_between(self):
90 get_paths = self.connection.get_shortest_path_between_functions
91
92 result = {f.name for f in get_paths(PNAME, 'main', 'wierd$name', 'false', '0').functions}
93 exp = {u'main', u'normal', u'wierd$name'}
94 self.assertEquals(result, exp)
95
96if __name__ == '__main__':
97 unittest.main()
098
=== removed file 'src/sextant/test_db_api.py'
--- src/sextant/test_db_api.py 2014-10-16 15:26:47 +0000
+++ src/sextant/test_db_api.py 1970-01-01 00:00:00 +0000
@@ -1,275 +0,0 @@
1#!/usr/bin/python
2# -----------------------------------------
3# Sextant
4# Copyright 2014, Ensoft Ltd.
5# Author: Patrick Stevens, James Harkin
6# -----------------------------------------
7#Testing module
8
9import unittest
10
11from db_api import Function
12from db_api import FunctionQueryResult
13from db_api import SextantConnection
14from db_api import validate_query
15
16
17class TestFunctionQueryResults(unittest.TestCase):
18 @classmethod
19 def setUpClass(cls):
20 # we need to set up the remote database by using the neo4j_input_api
21 cls.remote_url = 'http://ensoft-sandbox:7474'
22
23 cls.setter_connection = SextantConnection('ensoft-sandbox', 7474)
24
25 cls.program_1_name = 'testprogram'
26 cls.one_node_program_name = 'testprogram1'
27 cls.empty_program_name = 'testprogramblank'
28
29 # if anything failed before, delete programs now
30 cls.setter_connection.delete_program(cls.program_1_name)
31 cls.setter_connection.delete_program(cls.one_node_program_name)
32 cls.setter_connection.delete_program(cls.empty_program_name)
33
34
35 cls.upload_program = cls.setter_connection.new_program(cls.program_1_name)
36 cls.upload_program.add_function('func1')
37 cls.upload_program.add_function('func2')
38 cls.upload_program.add_function('func3')
39 cls.upload_program.add_function('func4')
40 cls.upload_program.add_function('func5')
41 cls.upload_program.add_function('func6')
42 cls.upload_program.add_function('func7')
43 cls.upload_program.add_call('func1', 'func2')
44 cls.upload_program.add_call('func1', 'func4')
45 cls.upload_program.add_call('func2', 'func1')
46 cls.upload_program.add_call('func2', 'func4')
47 cls.upload_program.add_call('func3', 'func5')
48 cls.upload_program.add_call('func4', 'func4')
49 cls.upload_program.add_call('func4', 'func5')
50 cls.upload_program.add_call('func5', 'func1')
51 cls.upload_program.add_call('func5', 'func2')
52 cls.upload_program.add_call('func5', 'func3')
53 cls.upload_program.add_call('func6', 'func7')
54
55 cls.upload_program.commit()
56
57 cls.upload_one_node_program = cls.setter_connection.new_program(cls.one_node_program_name)
58 cls.upload_one_node_program.add_function('lonefunc')
59
60 cls.upload_one_node_program.commit()
61
62 cls.upload_empty_program = cls.setter_connection.new_program(cls.empty_program_name)
63
64 cls.upload_empty_program.commit()
65
66 cls.getter_connection = cls.setter_connection
67
68
69 @classmethod
70 def tearDownClass(cls):
71 cls.setter_connection.delete_program(cls.upload_program.program_name)
72 cls.setter_connection.delete_program(cls.upload_one_node_program.program_name)
73 cls.setter_connection.delete_program(cls.upload_empty_program.program_name)
74
75 cls.setter_connection.close()
76 del(cls.setter_connection)
77
78 def test_17_get_call_paths(self):
79 reference1 = FunctionQueryResult(parent_db=None, program_name=self.program_1_name)
80 reference1.functions = [Function(self.program_1_name, 'func1'), Function(self.program_1_name, 'func2'),
81 Function(self.program_1_name, 'func3'),
82 Function(self.program_1_name, 'func4'), Function(self.program_1_name, 'func5')]
83 reference1.functions[0].functions_i_call = reference1.functions[1:4:2] # func1 calls func2, func4
84 reference1.functions[1].functions_i_call = reference1.functions[0:4:3] # func2 calls func1, func4
85 reference1.functions[2].functions_i_call = [reference1.functions[4]] # func3 calls func5
86 reference1.functions[3].functions_i_call = reference1.functions[3:5] # func4 calls func4, func5
87 reference1.functions[4].functions_i_call = reference1.functions[0:3] # func5 calls func1, func2, func3
88 self.assertEquals(reference1, self.getter_connection.get_call_paths(self.program_1_name, 'func1', 'func2'))
89 self.assertIsNone(self.getter_connection.get_call_paths('not a prog', 'func1', 'func2')) # shouldn't validation
90 self.assertIsNone(self.getter_connection.get_call_paths('notaprogram', 'func1', 'func2'))
91 self.assertIsNone(self.getter_connection.get_call_paths(self.program_1_name, 'notafunc', 'func2'))
92 self.assertIsNone(self.getter_connection.get_call_paths(self.program_1_name, 'func1', 'notafunc'))
93
94 def test_02_get_whole_program(self):
95 reference = FunctionQueryResult(parent_db=None, program_name=self.program_1_name)
96 reference.functions = [Function(self.program_1_name, 'func1'), Function(self.program_1_name, 'func2'),
97 Function(self.program_1_name, 'func3'),
98 Function(self.program_1_name, 'func4'), Function(self.program_1_name, 'func5'),
99 Function(self.program_1_name, 'func6'), Function(self.program_1_name, 'func7')]
100 reference.functions[0].functions_i_call = reference.functions[1:4:2] # func1 calls func2, func4
101 reference.functions[1].functions_i_call = reference.functions[0:4:3] # func2 calls func1, func4
102 reference.functions[2].functions_i_call = [reference.functions[4]] # func3 calls func5
103 reference.functions[3].functions_i_call = reference.functions[3:5] # func4 calls func4, func5
104 reference.functions[4].functions_i_call = reference.functions[0:3] # func5 calls func1, func2, func3
105 reference.functions[5].functions_i_call = [reference.functions[6]] # func6 calls func7
106
107
108 self.assertEqual(reference, self.getter_connection.get_whole_program(self.program_1_name))
109 self.assertIsNone(self.getter_connection.get_whole_program('nottherightprogramname'))
110
111 def test_03_get_whole_one_node_program(self):
112 reference = FunctionQueryResult(parent_db=None, program_name=self.one_node_program_name)
113 reference.functions = [Function(self.one_node_program_name, 'lonefunc')]
114
115 self.assertEqual(reference, self.getter_connection.get_whole_program(self.one_node_program_name))
116
117 def test_04_get_whole_empty_program(self):
118 reference = FunctionQueryResult(parent_db=None, program_name=self.empty_program_name)
119 reference.functions = []
120
121 self.assertEqual(reference, self.getter_connection.get_whole_program(self.empty_program_name))
122
123 def test_05_get_function_names(self):
124 reference = {'func1', 'func2', 'func3', 'func4', 'func5', 'func6', 'func7'}
125 self.assertEqual(reference, self.getter_connection.get_function_names(self.program_1_name))
126
127 def test_06_get_function_names_one_node_program(self):
128 reference = {'lonefunc'}
129 self.assertEqual(reference, self.getter_connection.get_function_names(self.one_node_program_name))
130
131 def test_07_get_function_names_empty_program(self):
132 reference = set()
133 self.assertEqual(reference, self.getter_connection.get_function_names(self.empty_program_name))
134
135 def test_09_validation_is_used(self):
136 self.assertFalse(self.getter_connection.get_function_names('not alphanumeric'))
137 self.assertFalse(self.getter_connection.get_whole_program('not alphanumeric'))
138 self.assertFalse(self.getter_connection.check_program_exists('not alphanumeric'))
139 self.assertFalse(self.getter_connection.check_function_exists('not alphanumeric', 'alpha'))
140 self.assertFalse(self.getter_connection.check_function_exists('alpha', 'not alpha'))
141 self.assertFalse(self.getter_connection.get_all_functions_called('alphaprogram', 'not alpha function'))
142 self.assertFalse(self.getter_connection.get_all_functions_called('not alpha program', 'alphafunction'))
143 self.assertFalse(self.getter_connection.get_all_functions_calling('not alpha program', 'alphafunction'))
144 self.assertFalse(self.getter_connection.get_all_functions_calling('alphaprogram', 'not alpha function'))
145 self.assertFalse(self.getter_connection.get_call_paths('not alpha program','alphafunc1', 'alphafunc2'))
146 self.assertFalse(self.getter_connection.get_call_paths('alphaprogram','not alpha func 1', 'alphafunc2'))
147 self.assertFalse(self.getter_connection.get_call_paths('alphaprogram','alphafunc1', 'not alpha func 2'))
148
149 def test_08_get_program_names(self):
150 reference = {self.program_1_name, self.one_node_program_name, self.empty_program_name}
151 self.assertTrue(reference.issubset(self.getter_connection.get_program_names()))
152
153
154 def test_11_get_all_functions_called(self):
155 reference1 = FunctionQueryResult(parent_db=None, program_name=self.program_1_name) # this will be the 1,2,3,4,5 component
156 reference2 = FunctionQueryResult(parent_db=None, program_name=self.program_1_name) # this will be the 6,7 component
157 reference3 = FunctionQueryResult(parent_db=None, program_name=self.program_1_name) # this will be the 7 component
158 reference1.functions = [Function(self.program_1_name, 'func1'), Function(self.program_1_name, 'func2'),
159 Function(self.program_1_name, 'func3'),
160 Function(self.program_1_name, 'func4'), Function(self.program_1_name, 'func5')]
161 reference2.functions = [Function(self.program_1_name, 'func6'), Function(self.program_1_name, 'func7')]
162 reference3.functions = []
163
164 reference1.functions[0].functions_i_call = reference1.functions[1:4:2] # func1 calls func2, func4
165 reference1.functions[1].functions_i_call = reference1.functions[0:4:3] # func2 calls func1, func4
166 reference1.functions[2].functions_i_call = [reference1.functions[4]] # func3 calls func5
167 reference1.functions[3].functions_i_call = reference1.functions[3:5] # func4 calls func4, func5
168 reference1.functions[4].functions_i_call = reference1.functions[0:3] # func5 calls func1, func2, func3
169
170 reference2.functions[0].functions_i_call = [reference2.functions[1]]
171
172 self.assertEquals(reference1, self.getter_connection.get_all_functions_called(self.program_1_name, 'func1'))
173 self.assertEquals(reference1, self.getter_connection.get_all_functions_called(self.program_1_name, 'func2'))
174 self.assertEquals(reference1, self.getter_connection.get_all_functions_called(self.program_1_name, 'func3'))
175 self.assertEquals(reference1, self.getter_connection.get_all_functions_called(self.program_1_name, 'func4'))
176 self.assertEquals(reference1, self.getter_connection.get_all_functions_called(self.program_1_name, 'func5'))
177
178 self.assertEquals(reference2, self.getter_connection.get_all_functions_called(self.program_1_name, 'func6'))
179
180 self.assertEquals(reference3, self.getter_connection.get_all_functions_called(self.program_1_name, 'func7'))
181
182 self.assertIsNone(self.getter_connection.get_all_functions_called(self.program_1_name, 'nottherightfunction'))
183 self.assertIsNone(self.getter_connection.get_all_functions_called('nottherightprogram', 'func2'))
184
185 def test_12_get_all_functions_called_1(self):
186 reference1 = FunctionQueryResult(parent_db=None, program_name=self.one_node_program_name)
187 reference1.functions = []
188
189 d=self.getter_connection.get_all_functions_called(self.one_node_program_name, 'lonefunc')
190 self.assertEquals(reference1, self.getter_connection.get_all_functions_called(self.one_node_program_name,
191 'lonefunc'))
192 self.assertIsNone(self.getter_connection.get_all_functions_called(self.one_node_program_name,
193 'not the right function'))
194 self.assertIsNone(self.getter_connection.get_all_functions_called('not the right program', 'lonefunc'))
195
196 def test_13_get_all_functions_called_blank(self):
197 reference1 = FunctionQueryResult(parent_db=None, program_name=self.empty_program_name)
198 reference1.functions = []
199
200 self.assertIsNone(self.getter_connection.get_all_functions_called(self.empty_program_name,
201 'not the right function'))
202
203 def test_14_get_all_functions_calling(self):
204 reference1 = FunctionQueryResult(parent_db=None, program_name=self.program_1_name) # this will be the 1,2,3,4,5 component
205 reference2 = FunctionQueryResult(parent_db=None, program_name=self.program_1_name) # this will be the 6,7 component
206 reference3 = FunctionQueryResult(parent_db=None, program_name=self.program_1_name) # this will be the 7 component
207 reference1.functions = [Function(self.program_1_name, 'func1'), Function(self.program_1_name, 'func2'),
208 Function(self.program_1_name, 'func3'),
209 Function(self.program_1_name, 'func4'), Function(self.program_1_name, 'func5')]
210
211 reference1.functions[0].functions_i_call = reference1.functions[1:4:2] # func1 calls func2, func4
212 reference1.functions[1].functions_i_call = reference1.functions[0:4:3] # func2 calls func1, func4
213 reference1.functions[2].functions_i_call = [reference1.functions[4]] # func3 calls func5
214 reference1.functions[3].functions_i_call = reference1.functions[3:5] # func4 calls func4, func5
215 reference1.functions[4].functions_i_call = reference1.functions[0:3] # func5 calls func1, func2, func3
216
217 reference2.functions = [Function(self.program_1_name, 'func6'), Function(self.program_1_name, 'func7')]
218
219 reference2.functions[0].functions_i_call = [reference2.functions[1]]
220
221 reference3.functions = [Function(self.program_1_name, 'func6')]
222
223 reference3.functions = []
224
225 self.assertEquals(reference1, self.getter_connection.get_all_functions_calling(self.program_1_name, 'func1'))
226 self.assertEquals(reference1, self.getter_connection.get_all_functions_calling(self.program_1_name, 'func2'))
227 self.assertEquals(reference1, self.getter_connection.get_all_functions_calling(self.program_1_name, 'func3'))
228 self.assertEquals(reference1, self.getter_connection.get_all_functions_calling(self.program_1_name, 'func4'))
229 self.assertEquals(reference1, self.getter_connection.get_all_functions_calling(self.program_1_name, 'func5'))
230
231 self.assertEquals(reference2, self.getter_connection.get_all_functions_calling(self.program_1_name,'func7'))
232
233 self.assertEquals(reference3, self.getter_connection.get_all_functions_calling(self.program_1_name, 'func6'))
234
235 self.assertIsNone(self.getter_connection.get_all_functions_calling(self.program_1_name, 'nottherightfunction'))
236 self.assertIsNone(self.getter_connection.get_all_functions_calling('nottherightprogram', 'func2'))
237
238 def test_15_get_all_functions_calling_one_node_prog(self):
239 reference1 = FunctionQueryResult(parent_db=None, program_name=self.one_node_program_name)
240 reference1.functions = []
241 self.assertEquals(reference1, self.getter_connection.get_all_functions_calling(self.one_node_program_name,
242 'lonefunc'))
243 self.assertIsNone(self.getter_connection.get_all_functions_calling(self.one_node_program_name,
244 'not the right function'))
245 self.assertIsNone(self.getter_connection.get_all_functions_calling('not the right program', 'lonefunc'))
246
247 def test_16_get_all_functions_calling_blank_prog(self):
248 reference1 = FunctionQueryResult(parent_db=None, program_name=self.empty_program_name)
249 reference1.functions=[]
250
251 self.assertIsNone(self.getter_connection.get_all_functions_called(self.empty_program_name,
252 'not the right function'))
253
254
255
256 def test_18_get_call_paths_between_two_functions_one_node_prog(self):
257 reference = FunctionQueryResult(parent_db=None, program_name=self.one_node_program_name)
258 reference.functions = [] # that is, reference is the empty program with name self.one_node_program_name
259
260 self.assertEquals(self.getter_connection.get_call_paths(self.one_node_program_name, 'lonefunc', 'lonefunc'),
261 reference)
262 self.assertIsNone(self.getter_connection.get_call_paths(self.one_node_program_name, 'lonefunc', 'notafunc'))
263 self.assertIsNone(self.getter_connection.get_call_paths(self.one_node_program_name, 'notafunc', 'notafunc'))
264
265 def test_10_validator(self):
266 self.assertFalse(validate_query(''))
267 self.assertTrue(validate_query('thisworks'))
268 self.assertTrue(validate_query('th1sw0rks'))
269 self.assertTrue(validate_query('12345'))
270 self.assertFalse(validate_query('this does not work'))
271 self.assertTrue(validate_query('this_does_work'))
272 self.assertFalse(validate_query("'")) # string consisting of a single quote mark
273
274if __name__ == '__main__':
275 unittest.main()
2760
=== modified file 'src/sextant/test_parser.py'
--- src/sextant/test_parser.py 2014-11-05 16:09:16 +0000
+++ src/sextant/test_parser.py 2014-11-25 10:17:59 +0000
@@ -11,20 +11,20 @@
11 def setUp(self):11 def setUp(self):
12 pass12 pass
13 13
14 def add_function(self, dct, name, typ):14 def add_function(self, dct, name, typ, source):
15 self.assertFalse(name in dct, "duplicate function added: {} into {}".format(name, dct.keys()))15 self.assertFalse(name in dct, "duplicate function added: {} into {}".format(name, dct.keys()))
16 dct[name] = typ16 dct[name] = (typ, source)
1717
18 def add_call(self, dct, caller, callee):18 def add_call(self, dct, caller, callee, is_internal):
19 dct[caller].append(callee)19 dct[caller].append((callee, is_internal))
2020
21 def do_parse(self, path=DUMP_FILE, sections=['.text'], ignore_ptrs=False):21 def do_parse(self, path=DUMP_FILE, sections=['.text'], ignore_ptrs=False):
22 functions = {}22 functions = {}
23 calls = defaultdict(list)23 calls = defaultdict(list)
24 24
25 # set the Parser to put output in local dictionaries25 # set the Parser to put output in local dictionaries
26 add_function = lambda n, t, s='unknown': self.add_function(functions, n, t)26 add_function = lambda n, t, s: self.add_function(functions, n, t, s)
27 add_call = lambda a, b: self.add_call(calls, a, b)27 add_call = lambda a, b, i: self.add_call(calls, a, b, i)
2828
29 p = parser.Parser(path, sections=sections, ignore_ptrs=ignore_ptrs, 29 p = parser.Parser(path, sections=sections, ignore_ptrs=ignore_ptrs,
30 add_function=add_function, add_call=add_call)30 add_function=add_function, add_call=add_call)
@@ -43,12 +43,16 @@
43 # ensure that the correct functions are listed with the correct types43 # ensure that the correct functions are listed with the correct types
44 res, funcs, calls = self.do_parse()44 res, funcs, calls = self.do_parse()
4545
46 for name, typ in zip(['normal', 'duplicates', 'wierd$name', 'printf', 'func_ptr_3'], 46 known = 'parser_test.c'
47 ['normal', 'normal', 'normal', 'stub', 'pointer']):47 unknown = 'unknown'
48
49 for name, typ, fle in zip(['normal', 'duplicates', 'wierd$name', 'printf', 'func_ptr_3'],
50 ['normal', 'normal', 'normal', 'stub', 'pointer'],
51 [known, known, known, unknown, unknown]):
48 self.assertTrue(name in funcs, "'{}' not found in function dictionary".format(name))52 self.assertTrue(name in funcs, "'{}' not found in function dictionary".format(name))
49 self.assertEquals(funcs[name], typ)53 self.assertEquals(funcs[name][0], typ)
54 self.assertTrue(funcs[name][1].endswith(fle))
5055
51 self.assertFalse('__gmon_start__' in funcs, "don't see a function defined in .plt")
5256
53 def test_no_ptrs(self):57 def test_no_ptrs(self):
54 # ensure that the ignore_ptrs flags is working58 # ensure that the ignore_ptrs flags is working
@@ -61,17 +65,17 @@
61 def test_calls(self):65 def test_calls(self):
62 res, funcs, calls = self.do_parse()66 res, funcs, calls = self.do_parse()
6367
64 self.assertTrue('normal' in calls['main'])68 self.assertTrue(('normal', True) in calls['main'])
65 self.assertTrue('duplicates' in calls['main'])69 self.assertTrue(('duplicates', True) in calls['main'])
6670
67 normal_calls = sorted(['wierd$name', 'printf', 'func_ptr_3'])71 normal_calls = sorted(['wierd$name', 'printf', 'func_ptr_3'])
68 self.assertEquals(sorted(calls['normal']), normal_calls)72 self.assertEquals(sorted(zip(*calls['normal'])[0]), normal_calls)
6973
70 self.assertEquals(calls['duplicates'].count('normal'), 2)74 self.assertEquals(calls['duplicates'].count(('normal', True)), 1)
71 self.assertEquals(calls['duplicates'].count('printf'), 2, 75 self.assertEquals(calls['duplicates'].count(('printf', False)), 1,
72 "expected 2 printf calls in {}".format(calls['duplicates']))76 "expected 2 printf calls in {}".format(calls['duplicates']))
73 self.assertTrue('func_ptr_4' in calls['duplicates'])77 self.assertTrue(('func_ptr_4', True) in calls['duplicates'])
74 self.assertTrue('func_ptr_5' in calls['duplicates'])78 self.assertTrue(('func_ptr_5', True) in calls['duplicates'])
7579
76 def test_sections(self):80 def test_sections(self):
77 res, funcs, calls = self.do_parse(sections=['.plt', '.text'])81 res, funcs, calls = self.do_parse(sections=['.plt', '.text'])
7882
=== modified file 'src/sextant/test_resources/parser_test.dump'
--- src/sextant/test_resources/parser_test.dump 2014-10-16 15:21:43 +0000
+++ src/sextant/test_resources/parser_test.dump 2014-11-25 10:17:59 +0000
@@ -20,20 +20,45 @@
20080483f0 <frame_dummy>:20080483f0 <frame_dummy>:
21 804840f: call *%eax21 804840f: call *%eax
220804841d <normal>:220804841d <normal>:
23/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:14
24/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:18
25/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:20
23 8048430: call 8048458 <wierd$name>26 8048430: call 8048458 <wierd$name>
27/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:21
24 8048443: call 80482f0 <printf@plt>28 8048443: call 80482f0 <printf@plt>
29/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:22
25 8048451: call *%eax30 8048451: call *%eax
31/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:24
32/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:25
2608048458 <wierd$name>:3308048458 <wierd$name>:
34/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:29
35/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:30
36/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:31
2708048460 <duplicates>:3708048460 <duplicates>:
38/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:35
39/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:36
40/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:39
28 804847b: call 80482f0 <printf@plt>41 804847b: call 80482f0 <printf@plt>
42/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:40
29 804848e: call 80482f0 <printf@plt>43 804848e: call 80482f0 <printf@plt>
44/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:42
30 8048499: call 804841d <normal>45 8048499: call 804841d <normal>
46/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:43
31 80484a4: call 804841d <normal>47 80484a4: call 804841d <normal>
48/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:45
32 80484b2: call *%eax49 80484b2: call *%eax
50/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:46
33 80484bd: call *%eax51 80484bd: call *%eax
52/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:48
53/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:49
34080484c4 <main>:54080484c4 <main>:
55/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:53
56/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:54
35 80484d4: call 804841d <normal>57 80484d4: call 804841d <normal>
58/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:55
36 80484e0: call 8048460 <duplicates>59 80484e0: call 8048460 <duplicates>
60/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:56
61/home/benhutc/filter-search/src/sextant/test_resources/parser_test.c:57
37080484f0 <__libc_csu_init>:62080484f0 <__libc_csu_init>:
38 80484f6: call 8048350 <__x86.get_pc_thunk.bx>63 80484f6: call 8048350 <__x86.get_pc_thunk.bx>
39 804850e: call 80482b4 <_init>64 804850e: call 80482b4 <_init>
4065
=== modified file 'src/sextant/update_db.py'
--- src/sextant/update_db.py 2014-11-13 14:01:55 +0000
+++ src/sextant/update_db.py 2014-11-25 10:17:59 +0000
@@ -9,9 +9,9 @@
99
10__all__ = ("upload_program", "delete_program")10__all__ = ("upload_program", "delete_program")
1111
12from .db_api import SextantConnection12from db_api import SextantConnection
13from .sshmanager import SSHConnectionError13from sshmanager import SSHConnectionError
14from .objdump_parser import Parser, run_objdump14from objdump_parser import Parser, run_objdump
15from os import path15from os import path
16from time import time16from time import time
17import subprocess17import subprocess
1818
=== modified file 'src/sextant/web/server.py'
--- src/sextant/web/server.py 2014-11-13 17:30:38 +0000
+++ src/sextant/web/server.py 2014-11-25 10:17:59 +0000
@@ -132,19 +132,19 @@
132 ),132 ),
133 'functions_calling': (133 'functions_calling': (
134 CONNECTION.get_all_functions_calling,134 CONNECTION.get_all_functions_calling,
135 ('func1',)135 ('func1', 'limit_internal', 'max_depth')
136 ),136 ),
137 'functions_called_by': (137 'functions_called_by': (
138 CONNECTION.get_all_functions_called,138 CONNECTION.get_all_functions_called,
139 ('func1',)139 ('func1', 'limit_internal', 'max_depth')
140 ),140 ),
141 'all_call_paths': (141 'all_call_paths': (
142 CONNECTION.get_call_paths,142 CONNECTION.get_call_paths,
143 ('func1', 'func2')143 ('func1', 'func2', 'limit_internal', 'max_depth')
144 ),144 ),
145 'shortest_call_path': (145 'shortest_call_path': (
146 CONNECTION.get_shortest_path_between_functions,146 CONNECTION.get_shortest_path_between_functions,
147 ('func1', 'func2')147 ('func1', 'func2', 'limit_internal', 'max_depth')
148 )148 )
149 }149 }
150150
@@ -187,6 +187,8 @@
187 res_code = RESPONSE_CODE_BAD_REQUEST187 res_code = RESPONSE_CODE_BAD_REQUEST
188 res_msg = "{}".format(e)188 res_msg = "{}".format(e)
189 print('\tfailed {}'.format(datetime.now()))189 print('\tfailed {}'.format(datetime.now()))
190 raise
191
190 192
191 if res_code is RESPONSE_CODE_OK:193 if res_code is RESPONSE_CODE_OK:
192 # we have received a response to our request194 # we have received a response to our request
@@ -237,7 +239,6 @@
237 max_funcs = AUTOCOMPLETE_NAMES_LIMIT + 1239 max_funcs = AUTOCOMPLETE_NAMES_LIMIT + 1
238 programs = CONNECTION.programs_with_metadata()240 programs = CONNECTION.programs_with_metadata()
239 result = CONNECTION.get_function_names(program_name, search, max_funcs)241 result = CONNECTION.get_function_names(program_name, search, max_funcs)
240 print(search, len(result))
241 return result if len(result) < max_funcs else set()242 return result if len(result) < max_funcs else set()
242 243
243244

Subscribers

People subscribed via source and target branches