def next_file_to_process(self): """Return the next file to process, None if we're done""" if len(self.mains_to_reduce) > 0: return self.mains_to_reduce.pop() if len(self.ads_dict) == 0: if len(self.bodies_to_reduce) == 0: # We're done! return None else: return self.bodies_to_reduce[0] else: # look through all .ads files to find one on which # no one depends. for candidate in self.ads_dict: if len(self.ads_dict[candidate]) == 0: # find the body corresponding to this candidate bod = candidate[:-1] + "b" if bod in self.bodies_to_reduce: return bod return candidate # if we reach here, there might be an issue log("circular dependency left over") for c in self.ads_dict: print(c) for dep in self.ads_dict[c]: print(f" {dep}") return None
def attempt_delete(self, file): """attempt deletion of f""" buf = Buffer(file) os.remove(file) if self.run_predicate(): log("... yay, deleted \o/") else: # put the file back buf.save() log("... didn't work.")
def run_predicate(self, print_if_error=False): """Run predicate and return True iff predicate returned 0.""" if self.script.endswith(".sh"): cmd = ["bash", self.script] else: cmd = [self.script] out = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) status = out.returncode == 0 if print_if_error and not status: log(out.stdout.decode() + "\n" + out.stderr.decode()) return status
def sort_ads_files(self): """Sort .ads files into a tree structure, allowing to process the leaf nodes first""" ads_dict = {} # A dict showing dependencies of als # keys: full path to .ads files # values: set of files that depend on this # First, add them all for x in self.resolver.files: full = self.resolver.files[x] if full.endswith(".ads") and os.path.exists(full): ads_dict[full] = set() # Now iterate on all of them total = len(ads_dict) count = 0 for x in ads_dict: count += 1 log(f"\t{count}/{total}: analyzing {x}") unit = self.context.get_from_file(x) # Iterate on all the use clauses root = unit.root if root is None: log(f"??? cannot find a root node for {x}") self.attempt_delete(x) else: for w in root.findall(lambda x: x.is_a(lal.WithClause)): if not w.children[0].is_a(lal.LimitedPresent): # find the last id in w ids = w.findall(lambda x: x.is_a(lal.Identifier)) if ids is not None: id = ids[-1] decl = id.p_referenced_defining_name() if decl is not None: if decl.unit.filename in ads_dict: ads_dict[decl.unit.filename].add(x) self.ads_dict = ads_dict # Find all bodies that are mains, so we can process them first. # TODO: migrate this to the gpr2 API when it exists. # Until then, duct tape: for x in self.bodies_to_reduce: spec = x[:-1] + "s" if spec not in self.ads_dict: self.mains_to_reduce.add(x)
def dichototree(chunks_tree, predicate, save): """Dichotomize the tree, first attempting the topmost level, then descending the exploration as levels fail. """ to_test = list(chunks_tree.children) level = 0 while to_test: level += 1 actioned, not_actioned = dichotomize(to_test, predicate, save) log(f"{level * ' '} level {level}: {len(actioned)} actioned, " + f"{len(not_actioned)} not actioned") to_test = [] for x in not_actioned: for c in x.children: to_test.append(c)
def run(self): """Run self: reduce the project as much as possible""" # Before running any modification, run the predicate, # as a sanity check. if not self.run_predicate(True): log("The predicate returned nonzero") return # We've passed the sanity check, time to reduce! # Attempt to remove all files in the project before doing any # reduction: this might save time by deleting files we would have tried # to reduce. if BRUTEFORCE_DELETE: log("=> Removing any unused files") self.attempt_delete_all( [self.resolver.files[name] for name in self.resolver.files]) # Prepare the list of files to reduce. First the main file. if self.single_file: candidate = self.single_file # Only add the single file if candidate.endswith(".adb"): self.bodies_to_reduce.append(candidate) else: self.ads_dict[candidate] = [] candidate = self.single_file else: # Add all the bodies for x in self.resolver.files: if x.endswith(".adb"): self.bodies_to_reduce.append(self.resolver.files[x]) # Add all the specs self.sort_ads_files() candidate = self.next_file_to_process() while candidate is not None: self.reduce_file(candidate) candidate = self.next_file_to_process()
def reduce_file(self, file): """Reduce one given file as much as possible""" self.mark_as_processed(file) # Skip some cases if "rts-" in file: log(f"SKIPPING {file}: looks like a runtime file") return if not os.access(file, os.W_OK): log(f"SKIPPING {file}: not writable") return log(f"*** Reducing {file}") # Save the file to an '.orig' copy buf = Buffer(file) buf.save(file + ".orig") try: chars_removed = self.apply_strategies_on_file(file, buf) except: # Catch any exception occurring during the application of # strategies, and save the buf to a .crash file, to # help post-mortem analysis. chars_removed = 0 buf.save(file + ".crash") raise # Print some stats log(f"done reducing {file} ({chars_removed} characters removed)") GUI.add_chars_removed(chars_removed) # Cautious? if CAUTIOUS_MODE: if not self.run_predicate(): log(CAUTIOUS_MODE_HELP) sys.exit(1) # Move on to the next files if self.follow_closure: # First let's check if we are processing a .adb that has a .ads if file.endswith(".adb"): ads = file[:-1] + "s" if os.path.exists(ads): # this exists, it's the natural next one to check self.ads_dict[ads] = [] return self.context = lal.AnalysisContext( unit_provider=self.unit_provider) unit = self.context.get_from_file(file) root = unit.root if root is not None: # Iterate through all "with" statements for w in root.findall(lambda x: x.is_a(lal.WithClause)): # find the last id in w ids = w.findall(lambda x: x.is_a(lal.Identifier)) if ids is not None: id = ids[-1] decl = id.p_referenced_defining_name() # find the definition if decl is not None: file_to_add = decl.unit.filename # Only process files that are in the project closure if self.resolver.belongs_to_project(file_to_add): if file_to_add.endswith(".ads"): # if it's a .ads we want to empty, empty # the .adb first, it will go faster adb = file_to_add[:-1] + "b" if os.path.exists(adb): self.bodies_to_reduce.append(adb) self.ads_dict[file_to_add] = [] else: self.bodies_to_reduce.append(file_to_add)
def apply_strategies_on_file(self, file, buf) -> int: """Apply all the strategies on the given buf. Return the number of characters removed. """ count = buf.count_chars() if REMOVE_TABS: log("=> Removing tabs") buf.strip_tabs() if CAUTIOUS_MODE and buf.count_chars() < count: # In cautious mode, if we actually did # remove some tabs, run the predicate as a check. if not self.run_predicate(): log(f"The issue is gone after stripping TABs in {file}") log("adareducer cannot help in this case.") sys.exit(1) unit = self.context.get_from_file(file) if unit is None or unit.root is None: log(f"??? cannot find a root node for {file}") self.attempt_delete(file) return 0 if EMPTY_OUT_BODIES_BRUTE_FORCE: log("=> Emptying out bodies (brute force)") HollowOutSubprograms().run_on_file(unit, buf.lines, self.run_predicate, lambda: buf.save()) # If there are bodies left, remove statements from them if EMPTY_OUT_BODIES_STATEMENTS: self.context = lal.AnalysisContext( unit_provider=self.unit_provider) log("=> Emptying out bodies (statement by statement)") buf = Buffer(file) unit = self.context.get_from_file(file) RemoveStatements().run_on_file(unit, buf.lines, self.run_predicate, lambda: buf.save()) # Let's try removing aspects if REMOVE_ASPECTS: self.context = lal.AnalysisContext( unit_provider=self.unit_provider) log("=> Removing aspects") RemoveAspects().run_on_file(self.context, file, self.run_predicate) # Remove subprograms if REMOVE_SUBPROGRAMS: self.context = lal.AnalysisContext( unit_provider=self.unit_provider) log("=> Removing subprograms") try: RemoveSubprograms().run_on_file(self.context, file, self.run_predicate) except lal.PropertyError: # retry with a new context... self.context = lal.AnalysisContext( unit_provider=self.unit_provider) RemoveSubprograms().run_on_file(self.context, file, self.run_predicate) # Let's try removing packages if REMOVE_PACKAGES: self.context = lal.AnalysisContext( unit_provider=self.unit_provider) log("=> Removing packages") RemovePackages().run_on_file(self.context, file, self.run_predicate) # Next remove the imports that we can remove if REMOVE_IMPORTS: log("=> Removing imports") RemoveImports().run_on_file(self.context, file, self.run_predicate) # Remove trivias if REMOVE_TRIVIAS: log("=> Removing blank lines and comments") RemoveTrivias().run_on_file(file, self.run_predicate) # Attempt to delete the file if it's empty-ish deletion_successful = False if ATTEMPT_DELETE: log("=> Attempting to delete") deletion_successful = DeleteEmptyUnits().run_on_file( self.context, file, self.run_predicate) # Fin if deletion_successful: log(" File deleted! \o/") chars_removed = count else: buf = Buffer(file) chars_removed = count - buf.count_chars() return chars_removed