Repository: mandiant/capa
File: capa/ida/plugin/form.py (Python)

Code before:
                        capa.ida.helpers.inform_user_ida_ui("Cached results were generated using different capas rules")
                        logger.warning(
                            "capa is showing you cached results from a previous analysis run. Your rules have changed since and you should reanalyze the program to see new results."
                        )
                        view_status_rules = "no rules matched for cache"

                    cached_results_time = self.resdoc_cache.meta.timestamp.strftime("%Y-%m-%d %H:%M:%S")
                    new_view_status = f"capa rules: {view_status_rules}, cached results (created {cached_results_time})"
                except Exception as e:
                    logger.error("Failed to load cached capa results (error: %s).", e, exc_info=True)
                    return False
            else:
                # load results from fresh anlaysis
                self.resdoc_cache = None
                self.process_total = 0
                self.process_count = 1

                def slot_progress_feature_extraction(text):
                    """slot function to handle feature extraction progress updates"""
                    update_wait_box(f"{text} ({self.process_count} of {self.process_total})")
                    self.process_count += 1

                self.extractor.indicator.progress.connect(slot_progress_feature_extraction)

                update_wait_box("calculating analysis")

                try:
                    self.process_total += len(tuple(self.extractor.get_functions()))
                except Exception as e:
                    logger.error("Failed to calculate analysis (error: %s).", e, exc_info=True)
                    return False

                if ida_kernwin.user_cancelled():
                    logger.info("User cancelled analysis.")
                    return False

                update_wait_box("loading rules")

                self.program_analysis_ruleset_cache = self.load_capa_rules()
                if self.program_analysis_ruleset_cache is None:
                    return False

                # matching operations may update rule instances,
                # so we'll work with a local copy of the ruleset.
                ruleset = copy.deepcopy(self.program_analysis_ruleset_cache)

                if ida_kernwin.user_cancelled():
                    logger.info("User cancelled analysis.")
                    return False

                update_wait_box("extracting features")

Review comment (refactor):
I see what you're trying to do here by re-using `self.extractor` for both the program analysis view and rule generator view. But let's keep this code to keep both extractors separate as setting the slot in line `738` (right) also affects the rule generator UX.

Code after:
                    cached_results_time = self.resdoc_cache.meta.timestamp.strftime("%Y-%m-%d %H:%M:%S")
                    new_view_status = f"capa rules: {view_status_rules}, cached results (created {cached_results_time})"
                except Exception as e:
                    logger.error("Failed to load cached capa results (error: %s).", e, exc_info=True)
                    return False
            else:
                # load results from fresh anlaysis
                self.resdoc_cache = None
                self.process_total = 0
                self.process_count = 1

                def slot_progress_feature_extraction(text):
                    """slot function to handle feature extraction progress updates"""
                    update_wait_box(f"{text} ({self.process_count} of {self.process_total})")
                    self.process_count += 1

                try:
                    self.feature_extractor = CapaExplorerFeatureExtractor()
                    self.feature_extractor.indicator.progress.connect(slot_progress_feature_extraction)
                except Exception as e:
                    logger.error("Failed to initialize feature extractor (error: %s)", e, exc_info=True)
                    return False

                if ida_kernwin.user_cancelled():
                    logger.info("User cancelled analysis.")
                    return False

                update_wait_box("calculating analysis")

                try:
                    self.process_total += len(tuple(self.feature_extractor.get_functions()))
                except Exception as e:
                    logger.error("Failed to calculate analysis (error: %s).", e, exc_info=True)
                    return False

                if ida_kernwin.user_cancelled():
                    logger.info("User cancelled analysis.")
                    return False

                update_wait_box("loading rules")

                self.program_analysis_ruleset_cache = self.load_capa_rules()
                if self.program_analysis_ruleset_cache is None:
                    return False

                # matching operations may update rule instances,
                # so we'll work with a local copy of the ruleset.
                ruleset = copy.deepcopy(self.program_analysis_ruleset_cache)

                if ida_kernwin.user_cancelled():
                    logger.info("User cancelled analysis.")