class LinearStreamingInterpolator(StreamingInterpolatorBase): """ Linear streaming interpolator """ def __init__(self): self._history = AVLTree() def insert(self, x, val): """ Register a datapoint x (float) : the x coordinate of the datapoint val (float): the rest of the datapoint """ self._history.insert(x, val) def getInterpolatedVal(self, x): """ Get the interpolated value for the given x x (float) : the x coordinate of the datapoint returns (float) : interpolated value """ if self._history.is_empty(): return None leftX, leftVal = None, None rightX, rightVal = None, None if self._history.min_key() <= x: leftX, leftVal = self._history.floor_item(x) if leftX == x: return leftVal if self._history.max_key() >= x: rightX, rightVal = self._history.ceiling_item(x) if rightX == x: return rightVal # check if on edge if leftVal == None: return rightVal elif rightVal == None: return leftVal # find weighted average of the vals of the closest enclosing data # points intervalLength = abs(x - leftX) + abs(x - rightX) value = float(abs(x - rightX) * leftVal + abs(x - leftX) * rightVal) / intervalLength return value
class NearestNeighborStreamingInterpolator(StreamingInterpolatorBase): """ Nearest Neighbor 1D Streaming Interpolator """ def __init__(self): self._history = AVLTree() def insert(self, x, val): """ Register a datapoint x (float) : the x coordinate of the datapoint val (float): the rest of the datapoint """ self._history.insert(x, val) def getInterpolatedVal(self, x): """ Get the interpolated value for the given x x (float) : the x coordinate of the datapoint returns (float) : interpolated value """ if self._history.is_empty(): return None # find the nearest point to the left and right leftX, leftVal = None, None rightX, rightVal = None, None if self._history.min_key() <= x: leftX, leftVal = self._history.floor_item(x) if self._history.max_key() >= x: rightX, rightVal = self._history.ceiling_item(x) # if there is only one neighbor, return it if leftVal == None: return rightVal elif rightVal == None: return leftVal # return the nearest neighbor if abs(x - leftX) < abs(x - rightX): return leftVal else: return rightVal
class QLinearViewer(QWidget): def __init__(self, workspace, disasm_view, parent=None): super(QLinearViewer, self).__init__(parent) self.workspace = workspace self.disasm_view = disasm_view self.objects = [] # Objects that will be painted self.cfg = None self.cfb = None self._offset_to_addr = AVLTree() self._addr_to_offset = AVLTree() self._offset_to_object = AVLTree() self._offset = 0 self._paint_start_offset = 0 self._linear_view = None # type: QLinearGraphicsView self._disasms = {} self._init_widgets() # # Properties # @property def offset(self): return self._offset @offset.setter def offset(self, v): self._offset = v @property def paint_start_offset(self): return self._paint_start_offset @property def max_offset(self): # TODO: Cache it try: max_off, obj = self._offset_to_object.max_item() except (KeyError, ValueError): return 0 return max_off + obj.height # # Proxy properties # @property def selected_operands(self): return self._linear_view.selected_operands @property def selected_insns(self): return self._linear_view.selected_insns # # Public methods # def initialize(self): self._make_objects() def navigate_to_addr(self, addr): if not self._addr_to_offset: return try: _, floor_offset = self._addr_to_offset.floor_item(addr) except KeyError: _, floor_offset = floor_offset = self._addr_to_offset.min_item() self.navigate_to(floor_offset) def refresh(self): self._linear_view.refresh() def navigate_to(self, offset): self._linear_view.verticalScrollBar().setValue(int(offset)) self.prepare_objects(offset) self._linear_view.refresh() def prepare_objects(self, offset): if offset == self._offset: return try: start_offset = self._offset_to_object.floor_key(offset) except (KeyError, ValueError): try: start_offset = self._offset_to_object.min_key() except ValueError: # Tree is empty return # Update offset self._offset = offset self._paint_start_offset = start_offset self.objects = [] max_height = self.height() for off, obj in self._offset_to_object.iter_items( start_key=start_offset): self.objects.append(obj) if off - offset > max_height: break # # Private methods # def _init_widgets(self): self._linear_view = QLinearGraphicsView(self, self.disasm_view) layout = QHBoxLayout() layout.addWidget(self._linear_view) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) # Setup proxy methods self.update_label = self._linear_view.update_label self.select_instruction = self._linear_view.select_instruction self.unselect_instruction = self._linear_view.unselect_instruction self.unselect_all_instructions = self._linear_view.unselect_all_instructions self.select_operand = self._linear_view.select_operand self.unselect_operand = self._linear_view.unselect_operand self.unselect_all_operands = self._linear_view.unselect_all_operands self.show_selected = self._linear_view.show_selected self.show_instruction = self._linear_view.show_instruction def _make_objects(self): self._addr_to_offset.clear() self._offset_to_addr.clear() self._offset_to_object.clear() y = 0 self._linear_view._clear_insn_addr_block_mapping() for obj_addr, obj in self.cfb.floor_items(): if isinstance(obj, Block): func_addr = self.cfg.get_any_node( obj.addr).function_address # FIXME: Resiliency func = self.cfg.kb.functions[func_addr] # FIXME: Resiliency disasm = self._get_disasm(func) qobject = QBlock( self.workspace, func_addr, self.disasm_view, disasm, self.disasm_view.infodock, obj.addr, [obj], {}, mode='linear', ) for insn_addr in qobject.addr_to_insns.keys(): self._linear_view._add_insn_addr_block_mapping( insn_addr, qobject) elif isinstance(obj, Unknown): qobject = QUnknownBlock(self.workspace, obj_addr, obj.bytes) else: continue self._offset_to_object[y] = qobject if obj_addr not in self._addr_to_offset: self._addr_to_offset[obj_addr] = y y += qobject.height def _get_disasm(self, func): """ :param func: :return: """ if func.addr not in self._disasms: self._disasms[ func. addr] = self.workspace.instance.project.analyses.Disassembly( function=func) return self._disasms[func.addr]
class KeyedRegion(object): """ KeyedRegion keeps a mapping between stack offsets and all variables covering that offset. It assumes no variable in this region overlap with another variable in this region. Registers and function frames can all be viewed as a keyed region. """ def __init__(self, tree=None): self._storage = AVLTree() if tree is None else tree # type: AVLTree def __contains__(self, offset): """ Test if there is at least one varaible covering the given offset. :param offset: :return: """ try: base_offset, item = self._storage.floor_item(offset) #pylint:disable=unused-variable except KeyError: return False if item.includes(offset): return True return False def __len__(self): return len(self._storage) def __iter__(self): for _, item in self._storage.items(): yield item def __eq__(self, other): if set(self._storage.keys()) != set(other._storage.keys()): return False for k, v in self._storage.iter_items(): if v != other._storage[k]: return False return True def copy(self): if not self._storage: return KeyedRegion() kr = KeyedRegion() for key, ro in self._storage.iter_items(): kr._storage[key] = ro.copy() return kr def merge(self, other, make_phi_func=None): """ Merge another KeyedRegion into this KeyedRegion. :param KeyedRegion other: The other instance to merge with. :return: None """ # TODO: is the current solution not optimal enough? for _, item in other._storage.iter_items(): # type: RegionObject for loc_and_var in item.objects: self.__store(loc_and_var, overwrite=False, make_phi_func=make_phi_func) return self def dbg_repr(self): """ Get a debugging representation of this keyed region. :return: A string of debugging output. """ keys = self._storage.keys() offset_to_vars = {} for key in sorted(keys): ro = self._storage[key] variables = [obj.variable for obj in ro.objects] offset_to_vars[key] = variables s = [] for offset, variables in offset_to_vars.iteritems(): s.append("Offset %#x: %s" % (offset, variables)) return "\n".join(s) def add_variable(self, start, variable): """ Add a variable to this region at the given offset. :param int start: :param SimVariable variable: :return: None """ self._store(start, variable, overwrite=False) def set_variable(self, start, variable): """ Add a variable to this region at the given offset, and remove all other variables that are fully covered by this variable. :param int start: :param SimVariable variable: :return: None """ self._store(start, variable, overwrite=True) def get_base_addr(self, addr): """ Get the base offset (the key we are using to index variables covering the given offset) of a specific offset. :param int addr: :return: :rtype: int or None """ try: base_addr, item = self._storage.floor_item(addr) except KeyError: return None if item.includes(addr): return base_addr return None def get_variables_by_offset(self, start): """ Find variables covering the given region offset. :param int start: :return: A list of stack variables. :rtype: set """ try: base_addr = self._storage.floor_key(start) except KeyError: return [] item = self._storage[base_addr] # type: RegionObject if item.includes(start): return item.variables return [] # # Private methods # def _store(self, start, variable, overwrite=False): """ Store a variable into the storage. :param int start: The beginning address of the variable. :param variable: The variable to store. :param bool overwrite: Whether existing variables should be overwritten or not. :return: None """ loc_and_var = LocationAndVariable(start, variable) self.__store(loc_and_var, overwrite=overwrite) def __store(self, loc_and_var, overwrite=False, make_phi_func=None): """ Store a variable into the storage. :param LocationAndVariable loc_and_var: The descriptor describing start address and the variable. :param bool overwrite: Whether existing variables should be overwritten or not. :return: None """ start = loc_and_var.start variable = loc_and_var.variable variable_size = variable.size if variable.size is not None else 1 end = start + variable_size # region items in the middle overlapping_items = list(self._storage.item_slice(start, end)) # is there a region item that begins before the start and overlaps with this variable? try: floor_key, floor_item = self._storage.floor_item( start) # type: RegionObject if floor_item.includes(start): item = (floor_key, floor_item) if item not in overlapping_items: # insert it into the beginningq overlapping_items.insert(0, (floor_key, floor_item)) except KeyError: # no there isn't pass # scan through the entire list of region items, split existing regions and insert new regions as needed to_update = {start: RegionObject(start, variable_size, {loc_and_var})} last_end = start for _, item in overlapping_items: # type: RegionObject if item.start < start: # we need to break this item into two a, b = item.split(start) if overwrite: b.set_object(loc_and_var) else: self._add_object_or_make_phi(b, loc_and_var, make_phi_func=make_phi_func) to_update[a.start] = a to_update[b.start] = b last_end = b.end elif item.start > last_end: # there is a gap between the last item and the current item # fill in the gap new_item = RegionObject(last_end, item.start - last_end, {loc_and_var}) to_update[new_item.start] = new_item last_end = new_item.end elif item.end > end: # we need to split this item into two a, b = item.split(end) if overwrite: a.set_object(loc_and_var) else: self._add_object_or_make_phi(a, loc_and_var, make_phi_func=make_phi_func) to_update[a.start] = a to_update[b.start] = b last_end = b.end else: if overwrite: item.set_object(loc_and_var) else: self._add_object_or_make_phi(item, loc_and_var, make_phi_func=make_phi_func) to_update[loc_and_var.start] = item self._storage.update(to_update) def _is_overlapping(self, start, variable): if variable.size is not None: # make sure this variable does not overlap with any other variable end = start + variable.size try: prev_offset = self._storage.floor_key(end - 1) except KeyError: prev_offset = None if prev_offset is not None: if start <= prev_offset < end: return True prev_item = self._storage[prev_offset][0] prev_item_size = prev_item.size if prev_item.size is not None else 1 if start < prev_offset + prev_item_size < end: return True else: try: prev_offset = self._storage.floor_key(start) except KeyError: prev_offset = None if prev_offset is not None: prev_item = self._storage[prev_offset][0] prev_item_size = prev_item.size if prev_item.size is not None else 1 if prev_offset <= start < prev_offset + prev_item_size: return True return False def _add_object_or_make_phi(self, item, loc_and_var, make_phi_func=None): #pylint:disable=no-self-use if not make_phi_func or len({loc_and_var.variable} | item.variables) == 1: item.add_object(loc_and_var) else: # make a phi node item.set_object( LocationAndVariable( loc_and_var.start, make_phi_func(loc_and_var.variable, *item.variables)))
class LineMapper(object): def __init__(self, series): self.__x_tree = AVLTree() self.__y_tree = AVLTree() self.__mapping = {} for (line, (xs, ys)) in enumerate(series): for (point, (x, y)) in enumerate(zip(xs, ys)): self.__x_tree.insert(x, x) self.__y_tree.insert(y, y) if self.__mapping.get(x) is None: self.__mapping[x] = {} if self.__mapping[x].get(y) is None: self.__mapping[x][y] = [] self.__mapping[x][y].append((line, point, x, y)) def __euclidean_distance(self, x1, y1, x2, y2): return sqrt((x2 - x1)**2 + (y2 - y1)**2) def get_closests(self, x, y, radius=None): try: x_floor = self.__x_tree.floor_item(x)[0] except KeyError: x_floor = None try: y_floor = self.__y_tree.floor_item(y)[0] except KeyError: y_floor = None try: x_ceiling = self.__x_tree.ceiling_item(x)[0] except KeyError: x_ceiling = None try: y_ceiling = self.__y_tree.ceiling_item(y)[0] except KeyError: y_ceiling = None points = [ (x_floor, y_floor), (x_floor, y_ceiling), (x_ceiling, y_floor), (x_ceiling, y_ceiling), ] points = filter(lambda p: p[0] is not None and p[1] is not None, points) points = filter(lambda p: self.__mapping.get(p[0]) != None, points) points = filter(lambda p: self.__mapping[p[0]].get(p[1]) != None, points) distances = [(self.__euclidean_distance(x, y, *p), *p) for p in points] if radius is not None: distances = filter(lambda d: d[0] < radius, distances) distances = list(distances) if len(distances) == 0: return None (x, y) = min(distances, key=lambda p: p[0])[1:] return self.__mapping[x][y]
class CFBlanket(Analysis): """ A Control-Flow Blanket is a representation for storing all instructions, data entries, and bytes of a full program. """ def __init__(self, cfg=None): self._blanket = AVLTree() self._ffi = cffi.FFI() if cfg is not None: self._from_cfg(cfg) else: _l.debug( "CFG is not specified. Initialize CFBlanket from the knowledge base." ) for func in self.kb.functions.values(): self.add_function(func) def floor_item(self, addr): return self._blanket.floor_item(addr) def floor_items(self, addr=None): if addr is None: addr = self._blanket.min_key() else: try: addr = self.floor_addr(addr) except KeyError: try: addr = self.ceiling_addr(addr) except KeyError: # Nothing to yield raise StopIteration while True: try: item = self._blanket[addr] yield (addr, item) item_size = item.size if item.size > 0 else 1 # pylint: disable=no-member addr = self.ceiling_addr(addr + item_size) except KeyError: break def floor_addr(self, addr): return self._blanket.floor_key(addr) def ceiling_item(self, addr): return self._blanket.ceiling_item(addr) def ceiling_addr(self, addr): return self._blanket.ceiling_key(addr) def __getitem__(self, addr): return self._blanket[addr] def add_obj(self, addr, obj): """ :param addr: :param obj: :return: """ self._blanket[addr] = obj def add_function(self, func): """ Add a function and all blocks of this function to the blanket. :param angr.Function func: The function to add. :return: """ for block in func.blocks: self.add_obj(block.addr, block) def dbg_repr(self): """ The debugging representation of this CFBlanket. :return: The debugging representation of this CFBlanket. :rtype: str """ output = [] for obj in self.project.loader.all_objects: for section in obj.sections: if section.memsize == 0: continue min_addr, max_addr = section.min_addr, section.max_addr output.append("### Object %s" % repr(section)) output.append("### Range %#x-%#x" % (min_addr, max_addr)) pos = min_addr while pos < max_addr: try: addr, thing = self.floor_item(pos) output.append("%#x: %s" % (addr, repr(thing))) if thing.size == 0: pos += 1 else: pos += thing.size except KeyError: pos += 1 output.append("") return "\n".join(output) def _from_cfg(self, cfg): """ Initialize CFBlanket from a CFG instance. :param cfg: A CFG instance. :return: None """ # Let's first add all functions first for func in cfg.kb.functions.values(): self.add_function(func) self._mark_unknowns() def _mark_unknowns(self): """ Mark all unmapped regions. :return: None """ for obj in self.project.loader.all_objects: if isinstance(obj, cle.ELF): # sections? if obj.sections: for section in obj.sections: if not section.memsize or not section.vaddr: continue min_addr, max_addr = section.min_addr, section.max_addr self._mark_unknowns_core(min_addr, max_addr, obj=obj, section=section) elif obj.segments: for segment in obj.segments: if not segment.memsize: continue min_addr, max_addr = segment.min_addr, segment.max_addr self._mark_unknowns_core(min_addr, max_addr, obj=obj, segment=segment) else: # is it empty? _l.warning("Empty ELF object %s.", repr(obj)) elif isinstance(obj, cle.PE): if obj.sections: for section in obj.sections: if not section.memsize: continue min_addr, max_addr = section.min_addr, section.max_addr self._mark_unknowns_core(min_addr, max_addr, obj=obj, section=section) else: # is it empty? _l.warning("Empty PE object %s.", repr(obj)) else: min_addr, max_addr = obj.min_addr, obj.max_addr self._mark_unknowns_core(min_addr, max_addr, obj=obj) def _mark_unknowns_core(self, min_addr, max_addr, obj=None, segment=None, section=None): try: addr = self.floor_addr(min_addr) if addr < min_addr: raise KeyError except KeyError: # there is no other lower address try: next_addr = self.ceiling_addr(min_addr) if next_addr >= max_addr: raise KeyError except KeyError: next_addr = max_addr size = next_addr - min_addr if obj is None or isinstance(obj, cle.ExternObject): bytes_ = None else: try: _l.debug( "Loading bytes from object %s, section %s, segmeng %s, addresss %#x.", obj, section, segment, min_addr) bytes_ptr, _ = self.project.loader.memory.read_bytes_c( min_addr) bytes_ = self._ffi.unpack( self._ffi.cast('char*', bytes_ptr), size) # type: str except KeyError: # The address does not exist bytes_ = None self.add_obj( min_addr, Unknown(min_addr, size, bytes_=bytes_, object_=obj, segment=segment, section=section)) addr = min_addr while addr < max_addr: last_addr, last_item = self.floor_item(addr) if last_addr < min_addr: # impossible raise Exception('Impossible') if last_item.size == 0: # Make sure everything has a non-zero size last_item_size = 1 else: last_item_size = last_item.size end_addr = last_addr + last_item_size if end_addr < max_addr: try: next_addr = self.ceiling_addr(end_addr) except KeyError: next_addr = max_addr if next_addr > end_addr: # there is a gap size = next_addr - end_addr if obj is None or isinstance(obj, cle.ExternObject): bytes_ = None else: try: _l.debug( "Loading bytes from object %s, section %s, segmeng %s, addresss %#x.", obj, section, segment, next_addr) bytes_ptr, _ = self.project.loader.memory.read_bytes_c( next_addr) bytes_ = self._ffi.unpack( self._ffi.cast('char*', bytes_ptr), size) # type: str except KeyError: # The address does not exist bytes_ = None self.add_obj( end_addr, Unknown(end_addr, size, bytes_=bytes_, object_=obj, segment=segment, section=section)) addr = next_addr else: addr = max_addr
class KeyedRegion(object): """ KeyedRegion keeps a mapping between stack offsets and all variables covering that offset. It assumes no variable in this region overlap with another variable in this region. Registers and function frames can all be viewed as a keyed region. """ def __init__(self, tree=None): self._storage = AVLTree() if tree is None else tree # type: AVLTree def __contains__(self, offset): """ Test if there is at least one varaible covering the given offset. :param offset: :return: """ try: base_offset, item = self._storage.floor_item(offset) #pylint:disable=unused-variable except KeyError: return False if item.includes(offset): return True return False def __len__(self): return len(self._storage) def __iter__(self): for _, item in self._storage.items(): yield item def __eq__(self, other): if set(self._storage.keys()) != set(other._storage.keys()): return False for k, v in self._storage.iter_items(): if v != other._storage[k]: return False return True def copy(self): if not self._storage: return KeyedRegion() kr = KeyedRegion() for key, ro in self._storage.iter_items(): kr._storage[key] = ro.copy() return kr def merge(self, other, make_phi_func=None): """ Merge another KeyedRegion into this KeyedRegion. :param KeyedRegion other: The other instance to merge with. :return: None """ # TODO: is the current solution not optimal enough? for _, item in other._storage.iter_items(): # type: RegionObject for loc_and_var in item.objects: self.__store(loc_and_var, overwrite=False, make_phi_func=make_phi_func) return self def dbg_repr(self): """ Get a debugging representation of this keyed region. :return: A string of debugging output. """ keys = self._storage.keys() offset_to_vars = { } for key in sorted(keys): ro = self._storage[key] variables = [ obj.variable for obj in ro.objects ] offset_to_vars[key] = variables s = [ ] for offset, variables in offset_to_vars.iteritems(): s.append("Offset %#x: %s" % (offset, variables)) return "\n".join(s) def add_variable(self, start, variable): """ Add a variable to this region at the given offset. :param int start: :param SimVariable variable: :return: None """ self._store(start, variable, overwrite=False) def set_variable(self, start, variable): """ Add a variable to this region at the given offset, and remove all other variables that are fully covered by this variable. :param int start: :param SimVariable variable: :return: None """ self._store(start, variable, overwrite=True) def get_base_addr(self, addr): """ Get the base offset (the key we are using to index variables covering the given offset) of a specific offset. :param int addr: :return: :rtype: int or None """ try: base_addr, item = self._storage.floor_item(addr) except KeyError: return None if item.includes(addr): return base_addr return None def get_variables_by_offset(self, start): """ Find variables covering the given region offset. :param int start: :return: A list of stack variables. :rtype: set """ try: base_addr = self._storage.floor_key(start) except KeyError: return [ ] item = self._storage[base_addr] # type: RegionObject if item.includes(start): return item.variables return [ ] # # Private methods # def _store(self, start, variable, overwrite=False): """ Store a variable into the storage. :param int start: The beginning address of the variable. :param variable: The variable to store. :param bool overwrite: Whether existing variables should be overwritten or not. :return: None """ loc_and_var = LocationAndVariable(start, variable) self.__store(loc_and_var, overwrite=overwrite) def __store(self, loc_and_var, overwrite=False, make_phi_func=None): """ Store a variable into the storage. :param LocationAndVariable loc_and_var: The descriptor describing start address and the variable. :param bool overwrite: Whether existing variables should be overwritten or not. :return: None """ start = loc_and_var.start variable = loc_and_var.variable variable_size = variable.size if variable.size is not None else 1 end = start + variable_size # region items in the middle overlapping_items = list(self._storage.item_slice(start, end)) # is there a region item that begins before the start and overlaps with this variable? try: floor_key, floor_item = self._storage.floor_item(start) # type: RegionObject if floor_item.includes(start): item = (floor_key, floor_item) if item not in overlapping_items: # insert it into the beginningq overlapping_items.insert(0, (floor_key, floor_item)) except KeyError: # no there isn't pass # scan through the entire list of region items, split existing regions and insert new regions as needed to_update = { start: RegionObject(start, variable_size, { loc_and_var }) } last_end = start for _, item in overlapping_items: # type: RegionObject if item.start < start: # we need to break this item into two a, b = item.split(start) if overwrite: b.set_object(loc_and_var) else: self._add_object_or_make_phi(b, loc_and_var, make_phi_func=make_phi_func) to_update[a.start] = a to_update[b.start] = b last_end = b.end elif item.start > last_end: # there is a gap between the last item and the current item # fill in the gap new_item = RegionObject(last_end, item.start - last_end, { loc_and_var }) to_update[new_item.start] = new_item last_end = new_item.end elif item.end > end: # we need to split this item into two a, b = item.split(end) if overwrite: a.set_object(loc_and_var) else: self._add_object_or_make_phi(a, loc_and_var, make_phi_func=make_phi_func) to_update[a.start] = a to_update[b.start] = b last_end = b.end else: if overwrite: item.set_object(loc_and_var) else: self._add_object_or_make_phi(item, loc_and_var, make_phi_func=make_phi_func) to_update[loc_and_var.start] = item self._storage.update(to_update) def _is_overlapping(self, start, variable): if variable.size is not None: # make sure this variable does not overlap with any other variable end = start + variable.size try: prev_offset = self._storage.floor_key(end - 1) except KeyError: prev_offset = None if prev_offset is not None: if start <= prev_offset < end: return True prev_item = self._storage[prev_offset][0] prev_item_size = prev_item.size if prev_item.size is not None else 1 if start < prev_offset + prev_item_size < end: return True else: try: prev_offset = self._storage.floor_key(start) except KeyError: prev_offset = None if prev_offset is not None: prev_item = self._storage[prev_offset][0] prev_item_size = prev_item.size if prev_item.size is not None else 1 if prev_offset <= start < prev_offset + prev_item_size: return True return False def _add_object_or_make_phi(self, item, loc_and_var, make_phi_func=None): #pylint:disable=no-self-use if not make_phi_func or len({loc_and_var.variable} | item.variables) == 1: item.add_object(loc_and_var) else: # make a phi node item.set_object(LocationAndVariable(loc_and_var.start, make_phi_func(loc_and_var.variable, *item.variables) ) )