示例#1
0
    def _make_func_occur_item(self, function_context, occur_num):
        """
        Build a tree item for function occurrence (level-2)
        @param function_context: a dbFunction_Context object
        @param occur_num: occurrence number
        @return: QStandradItemModel item for the function occurrence
        """
        func_occur_txt = "Occur %s" % str(occur_num)
        item_func_context = QtGui.QStandardItem(func_occur_txt)
        item_func_context.setColumnCount(5)
        item_func_context.setEditable(False)
        item_func_context.setData(function_context,
                                  role=DIE.UI.FunctionContext_Role)
        item_func_context.setData(
            id(function_context),
            role=DIE.UI.ContextId_Role)  # Used for module look-ups
        item_func_context.setData(self._make_thread_id_data(
            function_context.thread_id),
                                  role=DIE.UI.ThreadId_Role)

        item_list = [
            item_func_context,
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem()
        ]

        return item_list
示例#2
0
    def __init__(self, *args, **kwargs):
        super(Popup, self).__init__(*args, **kwargs)

        pm = QtGui.QPixmap(IDABUDDY_AVATAR_PATH)
        transform = QtGui.QTransform()
        transform.scale(0.5, 0.5)
        self.pm = pm.transformed(transform)

        self.slide = Slide(self)

        self.image_label = QtWidgets.QLabel(self.slide)
        self.image_label.setPixmap(self.pm)

        self.image_label.setFixedSize(self.pm.size())
        self.image_label.setAlignment(QtCore.Qt.AlignTop)
        self.slide.initialize()
        self.talk_bubble = TalkBubble(self)

        self.talk_bubble.move(TALKBUBBLE_X_MOVE, TALKBUBBLE_Y_MOVE)
        self.talk_bubble.hide()
        self.slide.move(size_to_point(self.talk_bubble.size()))

        self.setFixedSize(self.talk_bubble.size() + self.slide.size() +
                          get_extra_size())

        connect_method_to_signal(self.talk_bubble, 'linkActivated(QString)',
                                 self.linkActivatedHandler)
        self._handlers = {}
        self._default_handler = None
示例#3
0
    def _make_function_item(self, function):
        """
        Build a tree item for a function name (level-0)
        @param function: dbFunction object
        @return: QStandradItemModel item for the function
        """
        function_txt = "%s" % function.function_name

        item_function = QtGui.QStandardItem(self.die_icons.icon_function,
                                            function_txt)
        item_function.setData(function, role=DIE.UI.Function_Role)

        function_count = self.die_db.count_function_occurs(function)
        item_function_count = QtGui.QStandardItem(str(function_count))

        item_function_count.setEditable(False)
        item_function.setEditable(False)

        item_list = [
            item_function, item_function_count,
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem()
        ]

        return item_list
示例#4
0
    def _make_value_item(self, value):
        """
        Make a value model item
        @param value: dbParsed_Value object
        @return: a list of items for this row.
        """
        null_item = QtGui.QStandardItem()
        null_item.setEditable(False)
        null_item.setData(value.type, role=DIE.UI.ValueType_Role)
        null_item.setData(value.__hash__(), role=DIE.UI.Value_Role)

        item_value_score = QtGui.QStandardItem(str(value.score))
        item_value_score.setEditable(False)

        item_value_data = QtGui.QStandardItem(value.data)
        ea_list = self.die_db.get_parsed_value_contexts(value)
        item_value_data.setData(ea_list, role=DIE.UI.ContextList_Role)
        item_value_data.setEditable(False)

        item_value_desc = QtGui.QStandardItem(value.description)
        item_value_desc.setEditable(False)

        item_value_raw = QtGui.QStandardItem(value.raw)
        item_value_raw.setEditable(False)

        return [
            null_item, item_value_score, item_value_data, item_value_desc,
            item_value_raw
        ]
示例#5
0
    def _make_model_headers(self, model):
        """
        Set the model horizontal header data
        @param model: the QStandardItemModel which headers should be set
        """
        ### Function Header
        item_header = QtGui.QStandardItem("Function")
        item_header.setToolTip("Function Name")
        model.setHorizontalHeaderItem(0, item_header)

        ### Call number header
        item_header = QtGui.QStandardItem("#")
        item_header.setToolTip("Number of calls preformed to this function")
        model.setHorizontalHeaderItem(1, item_header)

        ### Indirect Header
        item_header = QtGui.QStandardItem("I")
        item_header.setToolTip("Indirect Call")
        model.setHorizontalHeaderItem(2, item_header)

        ### Indirect Header
        item_header = QtGui.QStandardItem("N")
        item_header.setToolTip("New Function")
        model.setHorizontalHeaderItem(3, item_header)

        ### Indirect Header
        item_header = QtGui.QStandardItem("Type")
        item_header.setToolTip("Argument Type")
        model.setHorizontalHeaderItem(4, item_header)

        ### New Function Header
        item_header = QtGui.QStandardItem("Name")
        item_header.setToolTip("Argument Name")
        model.setHorizontalHeaderItem(5, item_header)

        ### Call Value Icon Header
        item_header = QtGui.QStandardItem("")
        model.setHorizontalHeaderItem(6, item_header)

        ### Call Value Header
        item_header = QtGui.QStandardItem("Call Value")
        item_header.setToolTip("Argument`s value on function call")
        model.setHorizontalHeaderItem(7, item_header)

        ### Return Value Icon Header
        item_header = QtGui.QStandardItem("")
        model.setHorizontalHeaderItem(8, item_header)

        ### Return Value Header
        item_header = QtGui.QStandardItem("Return Value")
        item_header.setToolTip("Argument`s value on function return")
        model.setHorizontalHeaderItem(9, item_header)
示例#6
0
    def _make_function_ea_item(self, function_context):
        """
        Build a tree item for a function_ea node (level-1)
        @param function_context: a dbFunction_Context object
        @return: QStandradItemModel item for the function context
        """
        calling_function_start = None
        with ignored(sark.exceptions.SarkNoFunction):
            calling_function_start = sark.Function(
                function_context.calling_ea).startEA

        if calling_function_start is not None:
            call_offset = function_context.calling_ea - calling_function_start
            func_ea_txt = "%s+%s" % (function_context.calling_func_name,
                                     hex(call_offset))
        else:
            func_ea_txt = "[%s]:%s" % (function_context.calling_func_name,
                                       hex(function_context.calling_ea))

        item_func_context_ea = QtGui.QStandardItem(func_ea_txt)
        item_func_context_ea.setEditable(False)
        item_func_context_ea.setData(hex(function_context.calling_ea),
                                     role=QtCore.Qt.ToolTipRole)
        item_func_context_ea.setData(function_context,
                                     role=DIE.UI.FunctionContext_Role)
        item_func_context_ea.setData(
            id(function_context),
            role=DIE.UI.ContextId_Role)  # Used for module look-ups

        item_func_is_indirect = QtGui.QStandardItem()
        item_func_is_indirect.setEditable(False)
        if function_context.is_indirect:
            item_func_is_indirect.setIcon(self.die_icons.icon_v)

        item_func_is_new = QtGui.QStandardItem()
        item_func_is_new.setEditable(False)
        if function_context.is_new_func:
            item_func_is_new.setIcon(self.die_icons.icon_v)

        item_list = [
            item_func_context_ea,
            QtGui.QStandardItem(), item_func_is_indirect, item_func_is_new,
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem(),
            QtGui.QStandardItem()
        ]

        return item_list
示例#7
0
    def _make_nonexec_function_time(self, function_name):
        """
        Build a tree item for a function name (for a non-executed function)
        @type: String
        @param function_name: Function name
        @return:
        """

        item_function = QtGui.QStandardItem(self.die_icons.icon_function,
                                            function_name)
        item_function_count = QtGui.QStandardItem("0")

        item_function_count.setEditable(False)
        item_function.setEditable(False)

        item_list = [item_function, item_function_count]

        return item_list
示例#8
0
    def _make_value_type_item(self, type):
        """
        Make a value item type
        @param type: item type
        """

        item_value_type = QtGui.QStandardItem(type)
        item_value_type.setEditable(False)

        return [item_value_type]
示例#9
0
    def highlight_item(self, item):
        """
        Highlight a single item
        @param item: module item
        """
        try:
            item.setBackground(QtGui.QColor('yellow'))
            cur_font = item.font()
            cur_font.setBold(True)
            item.setFont(cur_font)

        except Exception as ex:
            idaapi.msg("Error while highlighting item: %s\n" % ex)
示例#10
0
 def load_icon(self, icon_path):
     """
     Load a single icon
     @param icon_path: full path to the icon file
     @return: the loaded icon object or None
     """
     icon_path = os.path.join(os.path.dirname(__file__), "..", "icons", icon_path)
     try:
         if os.path.exists(icon_path):
             return QtGui.QIcon(icon_path)
         else:
             return None
     except:
         return None
示例#11
0
    def initUI(self):
        config = DIE.Lib.DieConfig.get_config()
        self.setFixedSize(400, 330)
        self.setWindowTitle("About DIE")

        image = QtGui.QImage(os.path.join(config.icons_path, "logo.png"))
        pixmap = QtGui.QPixmap.fromImage(image)

        logo = QtWidgets.QLabel(self)
        logo.setFixedSize(pixmap.size())
        logo.move(0.5 * (self.width() - logo.width()), 20)
        logo.setPixmap(pixmap)

        title = QtWidgets.QLabel("DIE", self)
        title.setAlignment(QtCore.Qt.AlignCenter)
        font = title.font()
        font.setPointSize(16)
        font.setBold(True)
        title.setFont(font)
        title.setFixedWidth(400)
        title.move(0, logo.height() + logo.y() + 20)

        subtitle = QtWidgets.QLabel("Dynamic IDA Enrichment framework", self)
        font = subtitle.font()
        font.setPointSize(14)
        subtitle.setFont(font)
        subtitle.setAlignment(QtCore.Qt.AlignCenter)
        subtitle.setFixedWidth(400)
        subtitle.move(0, title.height() + title.y() + 10)

        version = QtWidgets.QLabel("Version 0.1", self)
        font = subtitle.font()
        font.setPointSize(12)
        version.setFont(font)
        version.setAlignment(QtCore.Qt.AlignCenter)
        version.setFixedWidth(400)
        version.move(0, subtitle.height() + subtitle.y() + 30)

        author = QtWidgets.QLabel(
            "Written by Yaniv Balmas @ynvb - Check Point Software Technologies",
            self)
        font = subtitle.font()
        font.setPointSize(12)
        author.setFont(font)
        author.setAlignment(QtCore.Qt.AlignCenter)
        author.setFixedWidth(400)
        author.move(0, version.height() + version.y())

        self.show()
示例#12
0
    def OnCreate(self, form):
        """
        Called when the view is created
        """
        self.die_db = DIE.Lib.DIEDb.get_db()
        self.function_view = DIE.UI.FunctionViewEx.get_view()

        # Get parent widget
        self.parent = form_to_widget(form)

        self.valueModel = QtGui.QStandardItemModel()
        self.valueTreeView = QtWidgets.QTreeView()
        self.valueTreeView.setExpandsOnDoubleClick(False)

        self.valueTreeView.doubleClicked.connect(self.itemDoubleClickSlot)

        self._model_builder(self.valueModel)
        self.valueTreeView.setModel(self.valueModel)

        # Toolbar
        self.value_toolbar = QtWidgets.QToolBar()

        # Value type combobox
        type_list = []
        if self.die_db:
            type_list = self.die_db.get_all_value_types()
            type_list.insert(0, "All Values")

        self.value_type_combo = QtWidgets.QComboBox()
        self.value_type_combo.addItems(type_list)
        self.value_type_combo.activated[str].connect(
            self.on_value_type_combobox_change)

        self.value_type_label = QtWidgets.QLabel("Value Type:  ")
        self.value_toolbar.addWidget(self.value_type_label)
        self.value_toolbar.addWidget(self.value_type_combo)

        # Layout
        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.value_toolbar)
        layout.addWidget(self.valueTreeView)

        self.parent.setLayout(layout)
示例#13
0
    def clear_highlights(self):
        """
        Clear all highlighted items
        @return:
        """
        try:
            self.functionTreeView.collapseAll()

            for persistent_index in self.highligthed_items:
                if persistent_index.isValid():
                    item = self.functionModel.itemFromIndex(persistent_index)
                    item.setBackground(QtGui.QColor('white'))
                    cur_font = item.font()
                    cur_font.setBold(False)
                    item.setFont(cur_font)

            self.highligthed_items = []

        except Exception as ex:
            idaapi.msg("Error while clearing highlights: %s\n" % ex)
示例#14
0
    def OnCreate(self, form):
        """
        Called when the plugin form is created
        """
        self.value_view = DIE.UI.ValueViewEx.get_view()
        self.bp_handler = DIE.Lib.BpHandler.get_bp_handler()
        self.die_icons = DIE.UI.Die_Icons.get_die_icons()
        self.die_db = DIE.Lib.DIEDb.get_db()

        # Get parent widget
        self.parent = form_to_widget(form)

        self.functionModel = QtGui.QStandardItemModel()
        self.functionTreeView = QtWidgets.QTreeView()
        self.functionTreeView.setExpandsOnDoubleClick(False)
        #self.functionTreeView.setSortingEnabled(True)

        delegate = TreeViewDelegate(self.functionTreeView)
        self.functionTreeView.setItemDelegate(delegate)

        self.functionTreeView.doubleClicked.connect(self.itemDoubleClickSlot)

        self._model_builder(self.functionModel)
        self.functionTreeView.setModel(self.functionModel)

        self.functionTreeView.setColumnWidth(0, 200)
        self.functionTreeView.setColumnWidth(1, 20)
        self.functionTreeView.setColumnWidth(2, 20)
        self.functionTreeView.setColumnWidth(3, 20)
        self.functionTreeView.setColumnWidth(4, 250)
        self.functionTreeView.setColumnWidth(5, 100)
        self.functionTreeView.setColumnWidth(6, 20)
        self.functionTreeView.setColumnWidth(7, 450)
        self.functionTreeView.setColumnWidth(8, 20)
        self.functionTreeView.setColumnWidth(9, 450)

        # Context menus
        self.functionTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.functionTreeView.customContextMenuRequested.connect(
            self.onCustomContextMenu)

        # Actions
        self.context_menu_param = None  # Parameter to be passed to context menu slots

        action_exclude_func = QtWidgets.QAction(
            "Exclude Function",
            self.functionTreeView,
            triggered=lambda: self.on_exclude_func(self.context_menu_param))
        action_exclude_func_adrs = QtWidgets.QAction(
            "Exclude All Function Calls",
            self.functionTreeView,
            triggered=lambda: self.on_exclude_func_adrs(self.context_menu_param
                                                        ))
        action_exclude_ea = QtWidgets.QAction(
            "Exclude Address",
            self.functionTreeView,
            triggered=lambda: self.on_exclude_ea(self.context_menu_param))
        action_exclude_library = QtWidgets.QAction(
            "Exclude Library",
            self.functionTreeView,
            triggered=lambda: self.on_exclude_library(self.context_menu_param))
        action_value_detail = QtWidgets.QAction(
            "Inspect Value Details",
            self.functionTreeView,
            triggered=lambda: self.on_value_detail(self.context_menu_param))

        action_show_callgraph = QtWidgets.QAction(
            "Show Call-Graph",
            self.functionTreeView,
            triggered=lambda: self.on_show_callgraph(self.context_menu_param))

        # Function ContextMenu
        self.function_context_menu = QtWidgets.QMenu(self.functionTreeView)
        self.function_context_menu.addAction(action_exclude_func)
        self.function_context_menu.addAction(action_exclude_library)
        self.function_context_menu.addAction(action_exclude_func_adrs)

        # Function ea ContextMenu
        self.ea_context_menu = QtWidgets.QMenu(self.functionTreeView)
        self.ea_context_menu.addAction(action_exclude_ea)
        self.ea_context_menu.addAction(action_show_callgraph)

        # Argument value ContextMenu
        self.value_context_menu = QtWidgets.QMenu(self.functionTreeView)
        self.value_context_menu.addAction(action_value_detail)

        # Therad ComboBox
        threads = []
        if self.die_db is not None:
            threads = self.die_db.get_thread_list()

        thread_id_list = []
        thread_id_list.append("All Threads")
        for thread in threads:
            thread_id_list.append(str(thread.thread_num))

        self.thread_id_combo = QtWidgets.QComboBox()
        self.thread_id_combo.addItems(thread_id_list)
        self.thread_id_combo.activated[str].connect(
            self.on_thread_combobox_change)

        self.thread_id_label = QtWidgets.QLabel("Thread:  ")

        # Toolbar
        self.function_toolbar = QtWidgets.QToolBar()
        self.function_toolbar.addWidget(self.thread_id_label)
        self.function_toolbar.addWidget(self.thread_id_combo)

        # Grid
        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.function_toolbar)
        layout.addWidget(self.functionTreeView)

        self.parent.setLayout(layout)
示例#15
0
    def _add_model_arg_value(self,
                             parent,
                             call_value,
                             ret_value,
                             arg_name,
                             arg_type,
                             nest_depth=0):
        """
        Add a debug value
        @param parent:
        @param call_value:
        @param ret_value:
        @param arg_name:
        @param arg_type:
        @return:
        """
        arg_count = parent.rowCount()
        this_row_item = QtGui.QStandardItem("")
        this_row_item.setData(
            parent.data(role=DIE.UI.ThreadId_Role),
            role=DIE.UI.ThreadId_Role)  # Inherit thread data from parent

        # Set indentation for argument types (for nested values)
        arg_ident = "  " * nest_depth
        arg_ident_type = arg_ident + arg_type

        item_parsed_val_flag_call = QtGui.QStandardItem()
        item_parsed_val_call = QtGui.QStandardItem()
        item_parsed_val_flag_ret = QtGui.QStandardItem()
        item_parsed_val_ret = QtGui.QStandardItem()

        # Get Call Value
        if call_value is not None:
            parsed_vals = self.die_db.get_parsed_values(call_value)
            this_row_item.setData(parsed_vals, role=DIE.UI.CallValue_Role)

            if parsed_vals is not None and len(parsed_vals) > 0:
                is_guessed, best_val = self.die_db.get_best_parsed_val(
                    parsed_vals)
                item_parsed_val_call = QtGui.QStandardItem(best_val.data)
                if is_guessed:
                    item_parsed_val_flag_call.setIcon(
                        self.die_icons.icon_question)

                if len(parsed_vals
                       ) > 1:  # If more the 1 item, show a combo-box
                    item_parsed_val_call.setData(parsed_vals,
                                                 role=DIE.UI.ParsedValuesRole)
                    item_parsed_val_flag_call.setIcon(self.die_icons.icon_more)
                else:
                    item_parsed_val_call.setData(parsed_vals[0],
                                                 role=DIE.UI.ParsedValueRole)

            else:
                parsed_val_data = "NULL"

                if call_value.derref_depth == 0:
                    parsed_val_data = "!MAX_DEREF!"

                if call_value.raw_value is not None:
                    parsed_val_data = hex(call_value.raw_value)

                if len(call_value.nested_values
                       ) > 0 or call_value.reference_flink is not None:
                    parsed_val_data = ""

                item_parsed_val_call = QtGui.QStandardItem(parsed_val_data)

        # Get return value
        if ret_value is not None:
            parsed_vals = self.die_db.get_parsed_values(ret_value)
            this_row_item.setData(parsed_vals, role=DIE.UI.RetValue_Role)

            # If len(parsed_vals)>1 create a combobox delegate.
            if parsed_vals:
                is_guessed, best_val = self.die_db.get_best_parsed_val(
                    parsed_vals)
                item_parsed_val_ret = QtGui.QStandardItem(best_val.data)
                if is_guessed:
                    item_parsed_val_flag_ret.setIcon(
                        self.die_icons.icon_question)

                if len(parsed_vals
                       ) > 1:  # If more the 1 item, show a combo-box
                    item_parsed_val_ret.setData(parsed_vals,
                                                role=DIE.UI.ParsedValuesRole)
                    item_parsed_val_flag_ret.setIcon(self.die_icons.icon_more)
                else:
                    item_parsed_val_ret.setData(parsed_vals[0],
                                                role=DIE.UI.ParsedValueRole)
            else:
                parsed_val_data = "NULL"

                if ret_value.derref_depth == 0:
                    parsed_val_data = "!MAX_DEREF!"

                if ret_value.raw_value is not None:
                    parsed_val_data = hex(ret_value.raw_value)

                if ret_value.nested_values or ret_value.reference_flink is not None:
                    parsed_val_data = ""

                item_parsed_val_ret = QtGui.QStandardItem(parsed_val_data)

            parent.setChild(arg_count, 0, this_row_item)
            parent.setChild(arg_count, 1, QtGui.QStandardItem())
            parent.setChild(arg_count, 2, QtGui.QStandardItem())
            parent.setChild(arg_count, 3, QtGui.QStandardItem())
            parent.setChild(arg_count, 4, QtGui.QStandardItem(arg_ident_type))
            parent.setChild(arg_count, 5, QtGui.QStandardItem(arg_name))

            parent.setChild(arg_count, 6, item_parsed_val_flag_call)
            parent.setChild(arg_count, 7, item_parsed_val_call)
            parent.setChild(arg_count, 8, item_parsed_val_flag_ret)
            parent.setChild(arg_count, 9, item_parsed_val_ret)

        # If current object contains reference values, add them to the module
        self._add_model_arg_ref(this_row_item, call_value, ret_value,
                                nest_depth)

        # If current object is a container object, Add its members to the module
        self._add_model_container_members(this_row_item, call_value, ret_value,
                                          nest_depth)
示例#16
0
    def _model_builder(self, model):
        """
        Build the function model.
        @param model: QStandardItemModel object
        """
        model.clear()  # Clear the model
        root_node = model.invisibleRootItem()

        self._make_model_headers(model)

        if self.die_db is None:
            return

        # Add db functions to the model
        for function in self.die_db.get_functions():
            item_list_func = self._make_function_item(function)

            if function.is_lib_func:  # Color library function
                for tmp_item in item_list_func:
                    tmp_item.setBackground(QtGui.QColor(184, 223, 220))

            item_function = item_list_func[0]
            root_node.appendRow(item_list_func)

            # Add function contexts ea\occurrences for the current function
            func_context_dict = self.die_db.get_function_context_dict(function)

            for function_context_ea in func_context_dict:
                function_context_list = func_context_dict[function_context_ea]
                if not len(function_context_list) > 0:
                    continue

                item_func_context_list = self._make_function_ea_item(
                    function_context_list[0])
                item_func_context_ea = item_func_context_list[0]
                item_function.appendRow(item_func_context_list)

                occurrence_num = 0
                for function_context in function_context_list:
                    item_func_context_list = self._make_func_occur_item(
                        function_context, occurrence_num)
                    item_func_context = item_func_context_list[0]
                    item_func_context_ea.appendRow(item_func_context_list)

                    self._insert_thread_data(item_function,
                                             function_context.thread_id)
                    self._insert_thread_data(item_func_context_ea,
                                             function_context.thread_id)

                    # Add function arguments to each context
                    current_call_values = self.die_db.get_call_values(
                        function_context)
                    current_ret_values = self.die_db.get_return_values(
                        function_context)
                    curret_ret_arg_value = self.die_db.get_return_arg_value(
                        function_context)

                    for arg_index in xrange(0, function.arg_num):
                        try:
                            current_arg = self.die_db.get_function_arg(
                                function, arg_index)
                            self._add_model_arg_value(
                                item_func_context,
                                current_call_values[arg_index],
                                current_ret_values[arg_index],
                                current_arg.name, current_arg.type)
                        except IndexError:
                            break

                    ret_arg = self.die_db.get_function_arg(function, -1)
                    if ret_arg is None:
                        ret_arg_type = "VOID"
                    else:
                        ret_arg_type = ret_arg.type

                    # Add return argument
                    self._add_model_arg_value(item_func_context, None,
                                              curret_ret_arg_value, "ret_arg",
                                              ret_arg_type)

                    # Increment occurrence counter
                    occurrence_num += 1