Skip to content

Commit 63cca43

Browse files
committed
AST: visual representation of reaching defs
1 parent 98dc409 commit 63cca43

File tree

9 files changed

+439
-117
lines changed

9 files changed

+439
-117
lines changed

chb/app/CHVersion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
chbversion: str = "0.3.0-20250902"
1+
chbversion: str = "0.3.0-20250908"

chb/app/Function.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -525,14 +525,36 @@ def instruction(self, iaddr: str) -> Instruction:
525525
else:
526526
raise UF.CHBError("No instruction found at address " + iaddr)
527527

528-
def rdef_locations(self) -> Dict[str, List[List[str]]]:
528+
def rdef_location_partition(self) -> Dict[str, List[List[str]]]:
529+
"""Return a map of registers to partitions of their reaching definitions."""
530+
529531
result: Dict[str, List[List[str]]] = {}
530532

531533
for (iaddr, instr) in self.instructions.items():
532534
irdefs = instr.rdef_locations()
533-
for (reg, rdeflists) in irdefs.items():
535+
for (reg, rdeflist) in irdefs.items():
534536
result.setdefault(reg, [])
535-
result[reg].extend(rdeflists)
537+
for rrlist in result[reg]:
538+
if set(rrlist) == set(rdeflist):
539+
break
540+
else:
541+
result[reg].append(rdeflist)
542+
return result
543+
544+
def use_location_partition(self) -> Dict[str, List[List[str]]]:
545+
"""Return a map of registers to partitions of their use locations."""
546+
547+
result: Dict[str, List[List[str]]] = {}
548+
549+
for (iaddr, instr) in self.instructions.items():
550+
iuses = instr.use_locations()
551+
for (reg, uselist) in iuses.items():
552+
result.setdefault(reg, [])
553+
for rrlist in result[reg]:
554+
if set(rrlist) == set(uselist):
555+
break
556+
else:
557+
result[reg].append(uselist)
536558
return result
537559

538560
def lhs_types(self) -> Dict[str, Dict[str, "BCTyp"]]:
@@ -545,7 +567,6 @@ def lhs_types(self) -> Dict[str, Dict[str, "BCTyp"]]:
545567
result[iaddr] = {}
546568
for (vname, vtype) in ilhs_types.items():
547569
result[iaddr][vname] = vtype
548-
549570
return result
550571

551572
def globalrefs(self) -> Dict[str, List["GlobalReference"]]:

chb/app/Instruction.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,11 +340,16 @@ def ast_switch_condition_prov(
340340
Optional[AST.ASTExpr], Optional[AST.ASTExpr]]:
341341
raise UF.CHBError("ast-switch-condition-prov not defined")
342342

343-
def rdef_locations(self) -> Dict[str, List[List[str]]]:
343+
def rdef_locations(self) -> Dict[str, List[str]]:
344344
"""Returns for each register, which locations must be combined."""
345345

346346
return {}
347347

348+
def use_locations(self) -> Dict[str, List[str]]:
349+
"""Returns for each register defined, which locations use the definition."""
350+
351+
return {}
352+
348353
def lhs_types(self) -> Dict[str, "BCTyp"]:
349354
"""Returns a mapping from lhs assigned to its type."""
350355

chb/arm/ARMInstruction.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -411,20 +411,25 @@ def lhs_variables(
411411
def rhs_expressions(self, filter: Callable[[XXpr], bool]) -> List[XXpr]:
412412
return [x for x in self.opcode.rhs(self.xdata) if filter(x)]
413413

414-
def rdef_locations(self) -> Dict[str, List[List[str]]]:
415-
result: Dict[str, Dict[str, List[str]]] = {}
414+
def rdef_locations(self) -> Dict[str, List[str]]:
415+
result: Dict[str, List[str]] = {}
416416

417417
for rdef in self.xdata.reachingdefs:
418418
if rdef is not None:
419-
rdefvar = str(rdef.vardefuse.variable)
420-
rdeflocs = [str(s) for s in rdef.valid_deflocations]
421-
result.setdefault(rdefvar, {})
422-
for sx in rdeflocs:
423-
result[rdefvar].setdefault(sx, [])
424-
for sy in rdeflocs:
425-
if sy not in result[rdefvar][sx]:
426-
result[rdefvar][sx].append(sy)
427-
return {x:list(r.values()) for (x, r) in result.items()}
419+
rdefvar = str(rdef.variable)
420+
rdeflocs = sorted([str(s) for s in rdef.valid_deflocations])
421+
result[rdefvar] = rdeflocs
422+
return result
423+
424+
def use_locations(self) -> Dict[str, List[str]]:
425+
result: Dict[str, List[str]] = {}
426+
427+
for use in self.xdata.defuses:
428+
if use is not None:
429+
usevar = str(use.variable)
430+
uselocs = sorted([str(s) for s in use.uselocations])
431+
result[usevar] = uselocs
432+
return result
428433

429434
def lhs_types(self) -> Dict[str, "BCTyp"]:
430435
result: Dict[str, "BCTyp"] = {}

chb/cmdline/astcmds.py

Lines changed: 104 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
Any, cast, Dict, List, NoReturn, Optional, Set, Tuple, TYPE_CHECKING)
3737

3838
from chb.app.AppAccess import AppAccess
39+
from chb.app.Function import Function
3940

4041
from chb.ast.AbstractSyntaxTree import AbstractSyntaxTree
4142
from chb.ast.ASTApplicationInterface import ASTApplicationInterface
@@ -62,6 +63,7 @@
6263
from chb.userdata.UserHints import UserHints
6364

6465
import chb.util.dotutil as UD
66+
from chb.util.DotGraph import DotGraph
6567
import chb.util.fileutil as UF
6668
import chb.util.graphutil as UG
6769
from chb.util.loggingutil import chklogger, LogLevel
@@ -150,6 +152,7 @@ def buildast(args: argparse.Namespace) -> NoReturn:
150152
hide_annotations: bool = args.hide_annotations
151153
show_reachingdefs: str = args.show_reachingdefs
152154
output_reachingdefs: str = args.output_reachingdefs
155+
fileformat: str = args.format
153156
verbose: bool = args.verbose
154157
loglevel: str = args.loglevel
155158
logfilename: Optional[str] = args.logfilename
@@ -347,7 +350,7 @@ def buildast(args: argparse.Namespace) -> NoReturn:
347350
# xdata records for all instructions in the function. Locations that
348351
# have a common user are merged. Types are provided by lhs_types.
349352
astinterface.introduce_ssa_variables(
350-
f.rdef_locations(), f.register_lhs_types, f.lhs_names)
353+
f.rdef_location_partition(), f.register_lhs_types, f.lhs_names)
351354

352355
# Introduce stack variables for all stack buffers with types
353356
astinterface.introduce_stack_variables(
@@ -393,51 +396,14 @@ def buildast(args: argparse.Namespace) -> NoReturn:
393396
UC.print_error("\nSpecify a file to save the reaching defs")
394397
continue
395398

396-
rdefspec = show_reachingdefs.split(":")
397-
if len(rdefspec) != 2:
398-
UC.print_error(
399-
"\nArgument to show_reachingdefs not recognized")
400-
continue
401-
402-
useloc = rdefspec[0]
403-
register = rdefspec[1]
404-
405-
if not f.has_instruction(useloc):
406-
UC.print_status_update("Useloc: " + useloc + " not found")
407-
continue
408-
409-
tgtinstr = f.instruction(useloc)
410-
411-
if not register in f.rdef_locations():
399+
register = show_reachingdefs
400+
if not register in f.rdef_location_partition():
412401
UC.print_status_update(
413402
"Register " + register + " not found in rdeflocations")
414403
continue
415404

416-
cblock = f.containing_block(useloc)
417-
graph = UG.DirectedGraph(list(f.cfg.blocks.keys()), f.cfg.edges)
418-
rdefs = tgtinstr.reaching_definitions(register)
419-
dotpaths: List[DotRdefPath] = []
420-
graph.find_paths(f.faddr, cblock)
421-
for (i, p) in enumerate(
422-
sorted(graph.get_paths(), key=lambda p: len(p))):
423-
cfgpath = DotRdefPath(
424-
"path" + str(i),
425-
f,
426-
astinterface,
427-
p,
428-
subgraph=True,
429-
nodeprefix = str(i) +":",
430-
rdefinstrs = rdefs)
431-
dotpaths.append(cfgpath)
432-
433-
pdffilename = UD.print_dot_subgraphs(
434-
app.path,
435-
"paths",
436-
output_reachingdefs,
437-
"pdf",
438-
[dotcfg.build() for dotcfg in dotpaths])
439-
440-
UC.print_status_update("Printed " + pdffilename)
405+
print_reachingdefs(
406+
app, astinterface, output_reachingdefs, fileformat, f, register)
441407

442408
else:
443409
UC.print_error("Unable to find function " + faddr)
@@ -469,6 +435,102 @@ def buildast(args: argparse.Namespace) -> NoReturn:
469435
exit(0)
470436

471437

438+
def print_reachingdefs(
439+
app: AppAccess,
440+
astinterface: ASTInterface,
441+
filename: str,
442+
fileformat: str,
443+
f: Function,
444+
register: str) -> None:
445+
dotpaths: List[Tuple[DotRdefPath, str, str]] = []
446+
regspill = register + "_spill"
447+
for (iaddr, instr) in f.instructions.items():
448+
if register in instr.rdef_locations():
449+
register_o = app.bdictionary.register_by_name(register)
450+
cblock = f.containing_block(iaddr)
451+
rdefs = instr.reaching_definitions(register)
452+
for rdef in rdefs:
453+
if rdef == "init":
454+
if regspill in instr.annotation:
455+
continue
456+
rdblock = f.faddr
457+
graph = UG.DirectedGraph(list(f.cfg.blocks.keys()), f.cfg.edges)
458+
graph.find_paths(rdblock, cblock)
459+
for (i, p) in enumerate(
460+
sorted(graph.get_paths(), key=lambda p: len(p))):
461+
p = ["init"] + p
462+
cfgpath = DotRdefPath(
463+
"path_" + str(rdef) + "_" + str(iaddr) + "_" + str(i),
464+
f,
465+
astinterface,
466+
p,
467+
register_o,
468+
subgraph=True,
469+
nodeprefix = str(iaddr) + str(rdef) + str(i) + ":",
470+
rdefinstrs = rdefs,
471+
useinstrs=[iaddr])
472+
dotpaths.append((cfgpath, rdef, iaddr))
473+
else:
474+
rdblock = f.containing_block(rdef)
475+
graph = UG.DirectedGraph(list(f.cfg.blocks.keys()), f.cfg.edges)
476+
graph.find_paths(rdblock, cblock)
477+
for (i, p) in enumerate(
478+
sorted(graph.get_paths(), key=lambda p: len(p))):
479+
cfgpath = DotRdefPath(
480+
"path_" + str(rdef) + "_" + str(iaddr) + "_" + str(i),
481+
f,
482+
astinterface,
483+
p,
484+
subgraph=True,
485+
nodeprefix = str(iaddr) + str(rdef) + str(i) + ":",
486+
rdefinstrs = rdefs,
487+
useinstrs=[iaddr])
488+
dotpaths.append((cfgpath, rdef, iaddr))
489+
490+
possibly_spurious_rdefs: List[Tuple[str, str]] = []
491+
legitimate_rdefs: List[Tuple[str, str]] = []
492+
dotgraphs: List[Tuple[DotRdefPath, DotGraph, str, str]] = []
493+
494+
for (dotcfg, rdef, iaddr) in dotpaths:
495+
dotgr = dotcfg.build()
496+
if dotgr is not None:
497+
if dotcfg.is_potentially_spurious():
498+
possibly_spurious_rdefs.append((rdef, iaddr))
499+
else:
500+
legitimate_rdefs.append((rdef, iaddr))
501+
dotgraphs.append((dotcfg, dotgr, rdef, iaddr))
502+
503+
printgraphs: List[DotGraph] = []
504+
for (dotcfg, dg, rdef, iaddr) in dotgraphs:
505+
if dotcfg.is_potentially_spurious():
506+
if (rdef, iaddr) in legitimate_rdefs:
507+
pass
508+
else:
509+
printgraphs.append(dg)
510+
else:
511+
printgraphs.append(dg)
512+
513+
pdffilename = UD.print_dot_subgraphs(
514+
app.path,
515+
"paths",
516+
filename,
517+
fileformat,
518+
printgraphs)
519+
520+
UC.print_status_update("Printed " + pdffilename)
521+
if len(possibly_spurious_rdefs) > 0:
522+
print("Possibly spurious reachingdefs to be removed: ")
523+
print("~" * 80)
524+
for (rdef, iaddr) in set(possibly_spurious_rdefs):
525+
if not (rdef, iaddr) in legitimate_rdefs:
526+
print(" rdefloc: " + rdef + "; useloc: " + iaddr)
527+
print("~" * 80)
528+
529+
# print("\nLegitimate reaching defs:")
530+
# for (rdef, iaddr) in set(legitimate_rdefs):
531+
# print(" rdefloc: " + rdef + "; useloc: " + iaddr)
532+
533+
472534
def showast(args: argparse.Namespace) -> NoReturn:
473535
print("still under construction ..")
474536
exit(1)

chb/cmdline/chkx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,10 +744,15 @@ def parse() -> argparse.Namespace:
744744
action="store_true")
745745
buildast.add_argument(
746746
"--show_reachingdefs",
747-
help="create a dot file for the reaching defs of <addr>:<reg>")
747+
help="create a dot file for the reaching defs of <reg>")
748748
buildast.add_argument(
749749
"--output_reachingdefs",
750750
help="name of output file (without extension) to store dot/pdf file of reachingdefs")
751+
buildast.add_argument(
752+
"--format",
753+
choices=["pdf", "png"],
754+
default="pdf",
755+
help="format for the graph file generated from dot")
751756
buildast.add_argument(
752757
"--loglevel", "-log",
753758
choices=UL.LogLevel.options(),

0 commit comments

Comments
 (0)