def test_ResolveExpr(self): # Erect the edifice of caches. caches = cache_basics.SetUpCaches(self.tmp) parse_file_obj = parse_file.ParseFile(caches.includepath_map) symbol_table = {} # Set up symbol_table by parsing test_data/more_macros.c. self.assertEqual( parse_file_obj.Parse("test_data/more_macros.c", symbol_table), ([], [], ['TEMPLATE_VARNAME(foo)'], [])) # Check what we got in symbol_table. self.assertEqual( macro_eval.EvalExpression("TEMPLATE_VARNAME(foo)", symbol_table), set([ 'TEMPLATE_VARNAME(foo)', '"maps/foo.tpl.varnames.h"', 'AS_STRING(maps/foo.tpl.varnames.h)', 'AS_STRING_INTERNAL(maps/foo.tpl.varnames.h)' ])) # Verify that resolving this expression yields one actual file (which we # have placed in test_data/map). [((d, ip), rp)], symbols = macro_eval.ResolveExpr( caches.includepath_map.Index, caches.build_stat_cache.Resolve, 'TEMPLATE_VARNAME(foo)', caches.directory_map.Index(os.getcwd()), # current dir caches.directory_map.Index(""), # file directory [caches.directory_map.Index("test_data")], # search directory [], symbol_table) self.assertEqual(caches.directory_map.string[d], "test_data/") self.assertEqual(caches.includepath_map.string[ip], "maps/foo.tpl.varnames.h") self.assertEqual( symbols, set([ 'TEMPLATE_VARNAME', 'maps', 'AS_STRING', 'AS_STRING_INTERNAL', 'tpl', 'varnames', 'h', 'foo' ]))
def FindNode(self, nodes_for_incl_config, fp, resolution_mode, file_dir_idx=None, fp_real_idx=None): """Find a previously constructed node or create a new node. Arguments: nodes_for_incl_config: a dictionary (see class documentation). fp: a filepath index or, if resolution_mode == RESOLVED, a filepath pair resolution_mode: an integer in RESOLUTION_MODES file_dir_idx: consider the file F that has the line '#include "fp"' which is causing us to call FindNode on fp. file_dir_idx is the index of dirname(F). (This argument affects the semantics of resolution for resolution_mode == QUOTE.) fp_real_idx: the realpath index of resolved filepath (Useful for resolution_mode == RESOLVED only.) Returns: a node or None Raises: NotCoveredError This is function is long, too long. But function calls are expensive in Python. TODO(klarlund): refactor. """ # Convenient abbreviations for cache access. dir_map = self.directory_map includepath_map = self.includepath_map resolve = self.build_stat_cache.Resolve # Now a little dynamic type verification. Remember that "A implies B" is # exactly the same as "not A or B", at least in some primitive formal # systems. assert isinstance(nodes_for_incl_config, dict) assert (not self.IsFilepathPair(fp) or resolution_mode == RESOLVED) assert (not fp or (self.IsFilepathPair(fp) or (resolution_mode != RESOLVED and self.IsIncludepathIndex(fp)))) assert resolution_mode in RESOLUTION_MODES assert not resolution_mode == QUOTE or file_dir_idx assert not file_dir_idx or resolution_mode == QUOTE assert not fp_real_idx or resolution_mode == RESOLVED if __debug__: Debug(DEBUG_TRACE, "FindNode: fp: %s, mode: %s\n file_dir: %s,\n fp_real: %s" % (self._PrintableFilePath(fp), RESOLUTION_MODES_STR[resolution_mode], not file_dir_idx and " " or dir_map.string[file_dir_idx], not fp_real_idx and " " or self.realpath_map.string[fp_real_idx])) statistics.find_node_counter += 1 if fp == None: return # We must remember the resolution_mode when we key our function call. And # for resolution_mode == QUOTE it is important to also remember the # file_dir_idx, because the filepath is resolved against file_dir. key = (fp, resolution_mode, file_dir_idx) if key in nodes_for_incl_config: # Is the support record valid? if nodes_for_incl_config[key][self.SUPPORT_RECORD].valid: statistics.master_hit_counter += 1 return nodes_for_incl_config[key] else: # Invalid support record. The meaning of some computed includes may have # changed. node = nodes_for_incl_config[key] currdir_idx = self.currdir_idx quote_dirs = self.quote_dirs angle_dirs = self.angle_dirs # Retrieve filepath information. That is still OK. Disregard children, # because they will be rebuilt. Reuse support_record. Don't switch # support_record.valid to True before running through all the caching # code below -- we don't want to reuse an earlier result. [fp_real_idx, fp_resolved_pair, _, support_record] = node Debug(DEBUG_TRACE, "Invalid record for translation unit: %s, file: %s", self.translation_unit, self._PrintableFilePath(fp)) else: # This is a new file -- for this include configuration at least. support_record = SupportRecord(self.support_master) currdir_idx = self.currdir_idx quote_dirs = self.quote_dirs angle_dirs = self.angle_dirs if resolution_mode == QUOTE: (fp_resolved_pair, fp_real_idx) = ( resolve(fp, currdir_idx, file_dir_idx, quote_dirs)) elif resolution_mode == ANGLE: (fp_resolved_pair, fp_real_idx) = ( resolve(fp, currdir_idx, None, angle_dirs)) elif resolution_mode == NEXT: # The node we return is just a dummy whose children are all the # possible resolvants. fp_resolved_pair = None fp_real_idx = None else: assert resolution_mode == RESOLVED assert fp_real_idx # this is the realpath corresponding to fp assert self.IsFilepathPair(fp) fp_resolved_pair = fp # we are given the resolvant if fp_resolved_pair: # The resolution succeeded. Before recursing, make sure to # mirror the path. Guard the call of MirrorPath with a cache # check; many files will have been visited before (for other # include directories). (d_, fp_) = fp_resolved_pair if (fp_resolved_pair, currdir_idx) not in self.mirrored: self.mirrored.add((fp_resolved_pair, currdir_idx)) self.mirror_path.DoPath( os.path.join(dir_map.string[currdir_idx], dir_map.string[d_], includepath_map.string[fp_]), currdir_idx, self.client_root_keeper.client_root) # We have fp_resolved_pair if and only if we have fp_real_idx assert not fp_resolved_pair or fp_real_idx assert not fp_real_idx or fp_resolved_pair # Now construct the node, even before we know the children; this # early construction/late filling-in of children allows us to stop # a recursion early, when key is in nodes_for_incl_config. A cyclic # structure may arise in this way. children = [] node = (fp_real_idx, fp_resolved_pair, children, support_record) nodes_for_incl_config[key] = node if not fp_resolved_pair: if resolution_mode == NEXT: # Create children of this dummy node. Try against all # directories in quote_dirs; that list includes the # angle_dirs. Recurse for each success. for d in quote_dirs: (fp_resolved_pair_, fp_real_idx_) = ( resolve(fp, currdir_idx, None, (d,))) if fp_resolved_pair_ != None: node_ = self.FindNode(nodes_for_incl_config, fp_resolved_pair_, RESOLVED, None, # file_dir_idx fp_real_idx_) children.append(node_) return node else: # For non-NEXT resolution modes return node # Now, we've got the resolution: (search directory, include path). assert (fp and fp_real_idx and fp_resolved_pair) (searchdir_idx, includepath_idx) = fp_resolved_pair # We need the realpath index of the current file directory. That's because # we are going to ask whether we have really visited this file, despite the # failure above to recognize it using a possibly relative name. Here, # 'really' means 'with respect to realpath'. Please see the class # documentation for why we need to calculate the realpath index of file # directory as part of the investigation of whether we have 'really' # encountered the file before. try: (fp_dirname_idx, fp_dirname_real_idx) = ( self.dirname_cache.cache[(currdir_idx, searchdir_idx, includepath_idx)]) except KeyError: (fp_dirname_idx, fp_dirname_real_idx) = ( self.dirname_cache.Lookup(currdir_idx, searchdir_idx, includepath_idx)) if resolution_mode != RESOLVED: # See whether we know about filepath post-resolution. if ((fp_real_idx, fp_dirname_real_idx) in nodes_for_incl_config and support_record.valid): statistics.master_hit_counter += 1 # Redo former decision about node: we use the one that is # already there. node = nodes_for_incl_config[(fp_real_idx, fp_dirname_real_idx)] nodes_for_incl_config[key] = node return node # Couldn't find node under real name. We'll remember the node, but have to # continue processing it. nodes_for_incl_config[(fp_real_idx, fp_dirname_real_idx)] = node # All chances of hitting the node cache are now exhausted! statistics.master_miss_counter += 1 # If we're revisiting because the support record was invalid, then it is # time to set it. support_record.valid = True # Try to get the cached result of parsing file. try: (quote_includes, angle_includes, expr_includes, next_includes) = ( self.file_cache[fp_real_idx]) except KeyError: # Parse the file. self.file_cache[fp_real_idx] = self.parse_file.Parse( self.realpath_map.string[fp_real_idx], self.symbol_table) (quote_includes, angle_includes, expr_includes, next_includes) = ( self.file_cache[fp_real_idx]) # Do the includes of the form #include "foo.h". for quote_filepath in quote_includes: node_ = self.FindNode(nodes_for_incl_config, quote_filepath, QUOTE, fp_dirname_idx) if node_: children.append(node_) support_record.Update(node_[self.SUPPORT_RECORD].support_id) # Do the includes of the form #include <foo.h>. for angle_filepath in angle_includes: node_ = self.FindNode(nodes_for_incl_config, angle_filepath, ANGLE) if node_: children.append(node_) support_record.Update(node_[self.SUPPORT_RECORD].support_id) if __debug__: if expr_includes: # Computed includes are interesting Debug(DEBUG_DATA, "FindNode, expr_includes: file: %s: '%s'", (isinstance(fp, int) and includepath_map.String(fp) or (isinstance(fp, tuple) and (dir_map.string[fp[0]], includepath_map.string[fp[1]]))), expr_includes) # Do the includes of the form #include expr, the computed includes. for expr in expr_includes: # Use multi-valued semantics to gather set of possible filepaths that the # C/C++ string expr may evaluate to under preprocessing semantics, given # the current symbol table. The symbols are all those of possible # expansions. (files, symbols) = ( macro_eval.ResolveExpr(includepath_map.Index, resolve, expr, self.currdir_idx, fp_dirname_idx, self.quote_dirs, self.angle_dirs, self.symbol_table)) for (fp_resolved_pair_, fp_real_idx_) in files: node_ = self.FindNode(nodes_for_incl_config, fp_resolved_pair_, RESOLVED, None, fp_real_idx_) if node_: children.append(node_) support_record.Update(node_[self.SUPPORT_RECORD].support_id) # Now the resolution of includes of the file of the present node depends # on symbols. support_record.UpdateSet(symbols) # Do includes of the form #include_next "foo.h" or # #include_next <foo.h>. for include_next_filepath in next_includes: node_ = self.FindNode(nodes_for_incl_config, include_next_filepath, NEXT) if node_: children.append(node_) support_record.Update(node_[self.SUPPORT_RECORD].support_id) return node