we are moving form post dominators.
This commit is contained in:
24
README.md
24
README.md
@@ -1,12 +1,14 @@
|
||||
# Final Project
|
||||
---
|
||||
### Things
|
||||
1. Forward Slice => All statements that could be affected if you change a given statement.Think of it like ripple effects in water:
|
||||
|
||||
1. To run the program please run the script:
|
||||
```bash
|
||||
chmod +x build.sh
|
||||
./build.sh
|
||||
```
|
||||
2. To run the program with bash run the command:
|
||||
```bash
|
||||
bash build.sh
|
||||
```
|
||||
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
# Show all dependencies for a file
|
||||
./run.sh examples/Example1.java
|
||||
|
||||
# Analyze impact of line 3
|
||||
./run.sh examples/Example1.java 3
|
||||
```
|
||||
|
||||
43
build.sh
43
build.sh
@@ -1,43 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
GREEN="\033[32m"
|
||||
RESET="\033[0m"
|
||||
echo ""
|
||||
echo "==== CPSC 449 Project ===="
|
||||
|
||||
# Create lib directory
|
||||
mkdir -p bin
|
||||
|
||||
echo "1. Checking dependencies"
|
||||
|
||||
if [ ! -f "lib/antlr-4.9.3-complete.jar" ]; then
|
||||
echo " Please Download ANTLR"
|
||||
fi
|
||||
|
||||
if [ ! -f "lib/jgrapht-core-1.5.1.jar" ]; then
|
||||
echo " Please Download JGraphT"
|
||||
fi
|
||||
|
||||
if [ ! -f "lib/jgrapht-io-1.5.1.jar" ]; then
|
||||
echo " Please Download JGraphT IO"
|
||||
fi
|
||||
|
||||
echo " [x] All dependencies acounted for"
|
||||
|
||||
CP="lib/antlr-4.9.3-complete.jar:lib/jgrapht-core-1.5.1.jar:lib/jgrapht-io-1.5.1.jar"
|
||||
|
||||
echo "2. Compiling CFG packages"
|
||||
javac -d bin -cp "$CP" src/org/lsmr/cfg/*.java
|
||||
echo " [x] CFG compiled"
|
||||
|
||||
echo "3. Compiling PDG packages"
|
||||
javac -d bin -cp "$CP:bin" src/pdg/*.java
|
||||
echo " [x] PDG compiled"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}==== Compilation Complete! ====${RESET}"
|
||||
echo -e "${GREEN}Compiled classes are in: bin/${RESET}"
|
||||
echo ""
|
||||
echo -e "${GREEN}>> To run this program:${RESET}"
|
||||
echo -e "${GREEN} java -cp bin:$CP YourMainClass${RESET}"
|
||||
echo ""
|
||||
BIN
lib/.DS_Store
vendored
Normal file
BIN
lib/.DS_Store
vendored
Normal file
Binary file not shown.
27
run.sh
Executable file
27
run.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Usage: ./run.sh <file> [line]"
|
||||
echo "Example: ./run.sh examples/Example1.java 3"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for antlr jar
|
||||
if [ ! -f "lib/antlr-4.13.2-complete.jar" ]; then
|
||||
echo "ERROR: Put antlr-4.13.2-complete.jar in lib/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build if needed be
|
||||
if [ ! -d "bin/pdg" ]; then
|
||||
echo "Building..."
|
||||
mkdir -p bin
|
||||
CP="lib/antlr-4.13.2-complete.jar"
|
||||
javac -d bin -cp "$CP" src/org/lsmr/cfg/*.java || exit 1
|
||||
javac -d bin -cp "$CP:bin" src/pdg/PDG.java || exit 1
|
||||
javac -d bin -cp "$CP:bin" src/CFGBuilder.java src/PDGTool.java || exit 1
|
||||
echo "Build complete!"
|
||||
fi
|
||||
|
||||
# Run
|
||||
java -cp "bin:lib/antlr-4.13.2-complete.jar" PDGTool "$@"
|
||||
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
130
src/CFGBuilder.java
Normal file
130
src/CFGBuilder.java
Normal file
@@ -0,0 +1,130 @@
|
||||
import org.lsmr.cfg.*;
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.tree.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class CFGBuilder {
|
||||
|
||||
private Map<Node, Integer> nodeToLine = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Build CFG from a Java file
|
||||
*/
|
||||
public ControlFlowGraph buildFromFile(String filename) throws IOException {
|
||||
System.out.println("\n\tBuilding CFG from " + filename);
|
||||
|
||||
// Parse the file
|
||||
CharStream input = CharStreams.fromFileName(filename);
|
||||
Java1_4Lexer lexer = new Java1_4Lexer(input);
|
||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
Java1_4Parser parser = new Java1_4Parser(tokens);
|
||||
|
||||
parser.removeErrorListeners();
|
||||
|
||||
ParseTree tree = parser.compilationUnit();
|
||||
|
||||
// build CFG using professor's NodeBuilder
|
||||
NodeBuilder builder = new NodeBuilder();
|
||||
builder.visit(tree);
|
||||
|
||||
List<ControlFlowGraph> cfgs = builder.getCFGs();
|
||||
|
||||
if (cfgs.isEmpty()) {
|
||||
throw new RuntimeException("No methods found in file");
|
||||
}
|
||||
|
||||
if (cfgs.size() > 1) {
|
||||
System.out.println("Note: File has " + cfgs.size() + " methods, using first method");
|
||||
}
|
||||
|
||||
ControlFlowGraph cfg = cfgs.get(0);
|
||||
|
||||
extractLineNumbers(cfg, tree);
|
||||
|
||||
System.out.println("CFG built: " + cfg.nodes().size() + " nodes, " + cfg.edges().size() + " edges");
|
||||
|
||||
return cfg;
|
||||
}
|
||||
private void extractLineNumbers(ControlFlowGraph cfg, ParseTree tree) {
|
||||
Map<String, Integer> labelToLine = new HashMap<>();
|
||||
collectLineNumbers(tree, labelToLine);
|
||||
|
||||
for (Node node : cfg.nodes()) {
|
||||
String label = node.label();
|
||||
Integer line = labelToLine.get(label);
|
||||
|
||||
if (line == null) {
|
||||
for (Map.Entry<String, Integer> entry : labelToLine.entrySet()) {
|
||||
if (label.contains(entry.getKey()) || entry.getKey().contains(label)) {
|
||||
line = entry.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeToLine.put(node, line != null ? line : -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void collectLineNumbers(ParseTree tree, Map<String, Integer> map) {
|
||||
if (tree instanceof ParserRuleContext) {
|
||||
ParserRuleContext ctx = (ParserRuleContext) tree;
|
||||
if (ctx.start != null) {
|
||||
String text = ctx.getText();
|
||||
if (text != null && text.length() < 200 && text.length() > 0) {
|
||||
map.put(text, ctx.start.getLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < tree.getChildCount(); i++) {
|
||||
collectLineNumbers(tree.getChild(i), map);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getLineNumber(Node node) {
|
||||
return nodeToLine.getOrDefault(node, -1);
|
||||
}
|
||||
|
||||
public List<Integer> getAllLineNumbers() {
|
||||
Set<Integer> lines = new TreeSet<>();
|
||||
for (Integer line : nodeToLine.values()) {
|
||||
if (line > 0) lines.add(line);
|
||||
}
|
||||
return new ArrayList<>(lines);
|
||||
}
|
||||
|
||||
public List<Node> findNodesAtLine(ControlFlowGraph cfg, int lineNumber) {
|
||||
List<Node> result = new ArrayList<>();
|
||||
for (Node node : cfg.nodes()) {
|
||||
if (getLineNumber(node) == lineNumber) {
|
||||
result.add(node);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void printLineMapping(ControlFlowGraph cfg) {
|
||||
System.out.println("\n Line Number Mapping");
|
||||
Map<Integer, List<String>> lineToNodes = new TreeMap<>();
|
||||
|
||||
for (Node node : cfg.nodes()) {
|
||||
int line = getLineNumber(node);
|
||||
if (line > 0) {
|
||||
lineToNodes.putIfAbsent(line, new ArrayList<>());
|
||||
lineToNodes.get(line).add(node.label());
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, List<String>> entry : lineToNodes.entrySet()) {
|
||||
System.out.println("Line " + entry.getKey() + ":");
|
||||
for (String label : entry.getValue()) {
|
||||
System.out.println(" " + label);
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
106
src/PDGTool.java
Normal file
106
src/PDGTool.java
Normal file
@@ -0,0 +1,106 @@
|
||||
import org.lsmr.cfg.*;
|
||||
import pdg.PDG;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* PDG Tool: Change Impact Analysis
|
||||
* Usage: java PDGTool <java-file> [line-number]
|
||||
*/
|
||||
public class PDGTool {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: java PDGTool <java-file> [line-number]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String filename = args[0];
|
||||
Integer targetLine = args.length > 1 ? Integer.parseInt(args[1]) : null;
|
||||
|
||||
try {
|
||||
analyzePDG(filename, targetLine);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void analyzePDG(String filename, Integer targetLine) throws IOException {
|
||||
System.out.println("PDG Analysis Tool");
|
||||
System.out.println("File: " + filename);
|
||||
if (targetLine != null) {
|
||||
System.out.println("Target line: " + targetLine);
|
||||
}
|
||||
|
||||
// Step 1: Build CFG
|
||||
CFGBuilder cfgBuilder = new CFGBuilder();
|
||||
ControlFlowGraph cfg = cfgBuilder.buildFromFile(filename);
|
||||
|
||||
// Step 2: Build PDG
|
||||
PDG pdg = new PDG(cfg);
|
||||
|
||||
// Step 3: Show results
|
||||
System.out.println("\n=== Available Lines ===");
|
||||
List<Integer> lines = cfgBuilder.getAllLineNumbers();
|
||||
System.out.println("Lines with statements: " + lines);
|
||||
|
||||
// If specific line requested, compute impact
|
||||
if (targetLine != null) {
|
||||
computeImpact(cfg, pdg, cfgBuilder, targetLine);
|
||||
} else {
|
||||
// Show all dependencies
|
||||
System.out.println("\n=== All Dependencies ===");
|
||||
pdg.printPDG();
|
||||
cfgBuilder.printLineMapping(cfg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void computeImpact(ControlFlowGraph cfg, PDG pdg,
|
||||
CFGBuilder cfgBuilder, int targetLine) {
|
||||
System.out.println("\n=== Impact Analysis for Line " + targetLine + " ===");
|
||||
|
||||
// Find nodes at target line
|
||||
List<Node> nodesAtLine = cfgBuilder.findNodesAtLine(cfg, targetLine);
|
||||
|
||||
if (nodesAtLine.isEmpty()) {
|
||||
System.out.println("WARNING: No executable statement at line " + targetLine);
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("\nStatement(s) at line " + targetLine + ":");
|
||||
for (Node node : nodesAtLine) {
|
||||
System.out.println(" " + node.label());
|
||||
}
|
||||
|
||||
// Compute forward slice
|
||||
Set<String> allImpacted = new HashSet<>();
|
||||
for (Node node : nodesAtLine) {
|
||||
Set<String> impacted = pdg.computeForwardSlice(node.label());
|
||||
allImpacted.addAll(impacted);
|
||||
}
|
||||
|
||||
// Convert to line numbers
|
||||
Set<Integer> impactedLines = new TreeSet<>();
|
||||
for (Node node : cfg.nodes()) {
|
||||
if (allImpacted.contains(node.label())) {
|
||||
int line = cfgBuilder.getLineNumber(node);
|
||||
if (line > 0) {
|
||||
impactedLines.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\n=== IMPACTED LINES ===");
|
||||
if (impactedLines.isEmpty()) {
|
||||
System.out.println(" (none)");
|
||||
} else {
|
||||
for (int line : impactedLines) {
|
||||
System.out.println(" Line " + line);
|
||||
}
|
||||
System.out.println("\nTotal: " + impactedLines.size() + " lines impacted");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import org.lsmr.cfg.ControlFlowGraph;
|
||||
import org.lsmr.cfg.Node;
|
||||
import org.lsmr.cfg.Edge;
|
||||
import pdg.PDG;
|
||||
import java.util.Set;
|
||||
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== PDG Test ===\n");
|
||||
|
||||
// Create a simple CFG manually
|
||||
ControlFlowGraph cfg = new ControlFlowGraph("testMethod");
|
||||
|
||||
// Create nodes
|
||||
Node n1 = cfg.buildNode("int x = 5");
|
||||
Node n2 = cfg.buildNode("int y = x + 10");
|
||||
Node n3 = cfg.buildNode("int z = y * 2");
|
||||
Node n4 = cfg.buildNode("System.out.println(z)");
|
||||
|
||||
// Connect nodes: entry -> n1 -> n2 -> n3 -> n4 -> exit
|
||||
cfg.buildEdge(cfg.entry, n1, Edge.EdgeLabel.BLANK);
|
||||
cfg.buildEdge(n1, n2, Edge.EdgeLabel.BLANK);
|
||||
cfg.buildEdge(n2, n3, Edge.EdgeLabel.BLANK);
|
||||
cfg.buildEdge(n3, n4, Edge.EdgeLabel.BLANK);
|
||||
cfg.buildEdge(n4, cfg.normalExit, Edge.EdgeLabel.BLANK);
|
||||
|
||||
System.out.println("CFG created with " + cfg.nodes().size() + " nodes");
|
||||
|
||||
// Create PDG from CFG
|
||||
PDG pdg = new PDG(cfg);
|
||||
|
||||
// Print CFG structure
|
||||
pdg.printCFG();
|
||||
|
||||
// Print PDG structure
|
||||
pdg.printPDG();
|
||||
|
||||
// Test impact analysis
|
||||
System.out.println("\n=== Impact Analysis ===");
|
||||
|
||||
String[] testNodes = {"int x = 5", "int y = x + 10", "int z = y * 2"};
|
||||
|
||||
for (String nodeLabel : testNodes) {
|
||||
Set<String> impacted = pdg.computeForwardSlice(nodeLabel);
|
||||
System.out.println("\nChanging '" + nodeLabel + "' impacts:");
|
||||
for (String label : impacted) {
|
||||
if (!label.startsWith("*")) { // Skip special nodes
|
||||
System.out.println(" - " + label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\n=== Test Complete ===");
|
||||
}
|
||||
}
|
||||
BIN
src/org/.DS_Store
vendored
BIN
src/org/.DS_Store
vendored
Binary file not shown.
BIN
src/org/lsmr/.DS_Store
vendored
BIN
src/org/lsmr/.DS_Store
vendored
Binary file not shown.
BIN
src/org/lsmr/cfg/.DS_Store
vendored
Normal file
BIN
src/org/lsmr/cfg/.DS_Store
vendored
Normal file
Binary file not shown.
587
src/pdg/PDG.java
587
src/pdg/PDG.java
@@ -1,454 +1,309 @@
|
||||
// program Dependence Graph implementation using the Profs CFG.
|
||||
// combines control dependence and data dependence.
|
||||
package pdg;
|
||||
|
||||
import org.lsmr.cfg.ControlFlowGraph;
|
||||
import org.lsmr.cfg.Node;
|
||||
import org.lsmr.cfg.Edge;
|
||||
import org.jgrapht.Graph;
|
||||
import org.jgrapht.graph.DefaultDirectedGraph;
|
||||
|
||||
import org.lsmr.cfg.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Simple Program Dependence Graph (PDG)
|
||||
* Computes control and data dependencies for a CFG
|
||||
*/
|
||||
public class PDG {
|
||||
private ControlFlowGraph cfg;
|
||||
private Graph<PDGNode, PDGEdge> pdg;
|
||||
private Map<Node, PDGNode> cfgToPdgMap;
|
||||
|
||||
// Analysis results
|
||||
private Map<Node, Set<Node>> postDominators;
|
||||
private Map<Node, Set<Node>> controlDependence;
|
||||
private Map<Node, Set<Node>> dataDependence;
|
||||
private ControlFlowGraph cfg;
|
||||
private Map<String, Set<String>> controlDeps;
|
||||
private Map<String, Set<String>> dataDeps;
|
||||
|
||||
public PDG(ControlFlowGraph cfg) {
|
||||
this.cfg = cfg;
|
||||
this.pdg = new DefaultDirectedGraph<>(PDGEdge.class);
|
||||
this.cfgToPdgMap = new HashMap<>();
|
||||
this.postDominators = new HashMap<>();
|
||||
this.controlDependence = new HashMap<>();
|
||||
this.dataDependence = new HashMap<>();
|
||||
this.controlDeps = new HashMap<>();
|
||||
this.dataDeps = new HashMap<>();
|
||||
|
||||
buildPDG();
|
||||
System.out.println("Building PDG...");
|
||||
computeControlDependencies();
|
||||
computeDataDependencies();
|
||||
System.out.println("PDG complete!");
|
||||
}
|
||||
|
||||
// build the complete PDG.
|
||||
private void buildPDG() {
|
||||
// Step 1: create PDG nodes from CFG nodes
|
||||
createPDGNodes();
|
||||
// Step 2: compute control dependence
|
||||
computeControlDependence();
|
||||
// Step 3: compute data dependence
|
||||
computeDataDependence();
|
||||
// Step 4: add edges to PDG
|
||||
addPDGEdges();
|
||||
}
|
||||
|
||||
// Create a PDG node for each CFG node.
|
||||
private void createPDGNodes() {
|
||||
for (Node cfgNode : cfg.nodes()) {
|
||||
PDGNode pdgNode = new PDGNode(cfgNode);
|
||||
pdg.addVertex(pdgNode);
|
||||
cfgToPdgMap.put(cfgNode, pdgNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute control dependence using post-dominator analysis.
|
||||
// Source: https://pages.cs.wisc.edu/~fischer/cs701.f14/lectures/L6.4up.pdf
|
||||
private void computeControlDependence() {
|
||||
// If, compute post-dominators
|
||||
computePostDominators();
|
||||
// Then, compute control dependence
|
||||
for (Node node : cfg.nodes()) {
|
||||
controlDependence.put(node, new HashSet<>());
|
||||
}
|
||||
|
||||
// for each edge X -> Y in the CFG
|
||||
for (Node x : cfg.nodes()) {
|
||||
for (Edge edge : x.outEdges()) {
|
||||
Node y = edge.target();
|
||||
if (y == null) continue;
|
||||
// find all nodes that are control-dependent on X via this edge
|
||||
for (Node node : cfg.nodes()) {
|
||||
// Node is control-dependent on X if:
|
||||
// 1. Node post-dominates Y (the successor)
|
||||
// 2. Node does NOT post-dominate X
|
||||
if (postDominates(node, y) && !postDominates(node, x)) {
|
||||
controlDependence.get(x).add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compute post-dominators using fixed-point algorithm.
|
||||
// node Y post-dominates X if all paths from X to exit go through Y.
|
||||
private void computePostDominators() {
|
||||
List<Node> allNodes = cfg.nodes();
|
||||
Node exitNode = cfg.normalExit;
|
||||
for (Node node : allNodes) {
|
||||
postDominators.put(node, new HashSet<>(allNodes));
|
||||
}
|
||||
|
||||
if (exitNode != null) {
|
||||
postDominators.put(exitNode, new HashSet<>(Arrays.asList(exitNode)));
|
||||
}
|
||||
|
||||
boolean changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
|
||||
for (Node node : allNodes) {
|
||||
if (node.equals(exitNode)) continue;
|
||||
|
||||
Set<Node> newPostDom = new HashSet<>();
|
||||
newPostDom.add(node); // Node post-dominates itself
|
||||
|
||||
Set<Edge> successorEdges = node.outEdges();
|
||||
if (!successorEdges.isEmpty()) {
|
||||
boolean first = true;
|
||||
for (Edge edge : successorEdges) {
|
||||
Node successor = edge.target();
|
||||
if (successor == null) continue;
|
||||
|
||||
if (first) {
|
||||
newPostDom.addAll(postDominators.get(successor));
|
||||
first = false;
|
||||
} else {
|
||||
newPostDom.retainAll(postDominators.get(successor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!newPostDom.equals(postDominators.get(node))) {
|
||||
postDominators.put(node, newPostDom);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean postDominates(Node dominator, Node node) {
|
||||
Set<Node> postDoms = postDominators.get(node);
|
||||
return postDoms != null && postDoms.contains(dominator);
|
||||
}
|
||||
|
||||
private void computeDataDependence() {
|
||||
Map<Node, Set<String>> defs = new HashMap<>();
|
||||
Map<Node, Set<String>> uses = new HashMap<>();
|
||||
/**
|
||||
* Compute control dependencies using simple reachability
|
||||
*/
|
||||
private void computeControlDependencies() {
|
||||
System.out.println("\tComputing control dependencies...");
|
||||
|
||||
for (Node node : cfg.nodes()) {
|
||||
defs.put(node, extractDefs(node));
|
||||
uses.put(node, extractUses(node));
|
||||
PDGNode pdgNode = cfgToPdgMap.get(node);
|
||||
pdgNode.setDefs(defs.get(node));
|
||||
pdgNode.setUses(uses.get(node));
|
||||
Set<String> deps = new HashSet<>();
|
||||
|
||||
for (Edge inEdge : node.inEdges()) {
|
||||
Node pred = inEdge.source();
|
||||
|
||||
if (pred.outEdges().size() > 1) {
|
||||
deps.add(pred.label());
|
||||
|
||||
if (controlDeps.containsKey(pred.label())) {
|
||||
deps.addAll(controlDeps.get(pred.label()));
|
||||
}
|
||||
Map<Node, Set<Definition>> reachingDefs = computeReachingDefinitions(defs);
|
||||
}
|
||||
}
|
||||
|
||||
controlDeps.put(node.label(), deps);
|
||||
}
|
||||
|
||||
System.out.println("\t\tFound " + countDependencies(controlDeps) + " control dependencies");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute data dependencies using reaching definitions
|
||||
*/
|
||||
private void computeDataDependencies() {
|
||||
System.out.println(" Computing data dependencies...");
|
||||
|
||||
// 1. Find definitions and uses for each node
|
||||
Map<String, Set<String>> defs = new HashMap<>();
|
||||
Map<String, Set<String>> uses = new HashMap<>();
|
||||
|
||||
for (Node node : cfg.nodes()) {
|
||||
dataDependence.put(node, new HashSet<>());
|
||||
}
|
||||
for (Node use : cfg.nodes()) {
|
||||
Set<String> usedVars = uses.get(use);
|
||||
Set<Definition> reaching = reachingDefs.get(use);
|
||||
|
||||
for (Definition def : reaching) {
|
||||
if (usedVars.contains(def.variable)) {
|
||||
dataDependence.get(def.node).add(use);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract variable definitions from a CFG node.
|
||||
private Set<String> extractDefs(Node node) {
|
||||
Set<String> defs = new HashSet<>();
|
||||
String label = node.label();
|
||||
|
||||
if (label.startsWith("*")) { // Skip special nodes
|
||||
return defs;
|
||||
defs.put(label, extractDefs(label));
|
||||
uses.put(label, extractUses(label));
|
||||
}
|
||||
|
||||
if (label.contains("=") && !label.contains("==")) { // Look for assignments: variable = ...
|
||||
try {
|
||||
String[] parts = label.split("=");
|
||||
if (parts.length >= 2) {
|
||||
String varPart = parts[0].trim();
|
||||
// 2. Compute reaching definitions
|
||||
Map<String, Map<String, Set<String>>> reaching = computeReachingDefinitions(defs);
|
||||
|
||||
// handle "int v" or just "v"
|
||||
String[] tokens = varPart.split("\\s+");
|
||||
String varName = tokens[tokens.length - 1].replaceAll("[^a-zA-Z0-9_]", "");
|
||||
|
||||
if (varName.length() > 0 && Character.isJavaIdentifierStart(varName.charAt(0))) {
|
||||
defs.add(varName);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// parsing failed, ignore
|
||||
}
|
||||
}
|
||||
|
||||
return defs;
|
||||
}
|
||||
|
||||
// Extract variable uses from a CFG node.
|
||||
private Set<String> extractUses(Node node) {
|
||||
Set<String> uses = new HashSet<>();
|
||||
// 3. Build data dependencies
|
||||
for (Node node : cfg.nodes()) {
|
||||
String label = node.label();
|
||||
Set<String> deps = new HashSet<>();
|
||||
|
||||
// Skip special nodes
|
||||
if (label.startsWith("*")) {
|
||||
return uses;
|
||||
for (String var : uses.get(label)) {
|
||||
if (reaching.containsKey(label) && reaching.get(label).containsKey(var)) {
|
||||
deps.addAll(reaching.get(label).get(var));
|
||||
}
|
||||
}
|
||||
|
||||
// For assignments, extract from right-hand side
|
||||
if (label.contains("=") && !label.contains("==")) {
|
||||
String[] parts = label.split("=", 2);
|
||||
if (parts.length >= 2) {
|
||||
String rhs = parts[1].trim().replace(";", "");
|
||||
uses.addAll(extractVariablesFromExpression(rhs));
|
||||
}
|
||||
} else {
|
||||
// For other statements, extract all variables
|
||||
uses.addAll(extractVariablesFromExpression(label));
|
||||
dataDeps.put(label, deps);
|
||||
}
|
||||
|
||||
return uses;
|
||||
System.out.println(" Found " + countDependencies(dataDeps) + " data dependencies");
|
||||
}
|
||||
|
||||
// Extract variable names from an expression.
|
||||
private Set<String> extractVariablesFromExpression(String expr) {
|
||||
/**
|
||||
* Extract variable definitions from a statement simple pattern matching for Java 1.4
|
||||
*/
|
||||
private Set<String> extractDefs(String statement) {
|
||||
Set<String> vars = new HashSet<>();
|
||||
|
||||
// Remove common operators and split
|
||||
String cleaned = expr.replaceAll("[+\\-*/()\\[\\]{}<>=!&|;,.]", " ");
|
||||
String[] tokens = cleaned.split("\\s+");
|
||||
// Skip special nodes
|
||||
if (statement.startsWith("*")) return vars;
|
||||
|
||||
for (String token : tokens) {
|
||||
token = token.trim();
|
||||
if (token.isEmpty()) continue;
|
||||
if (statement.contains("=") && !statement.contains("==")) {
|
||||
String[] parts = statement.split("=");
|
||||
if (parts.length > 0) {
|
||||
String lhs = parts[0].trim();
|
||||
String[] tokens = lhs.split("\\s+");
|
||||
if (tokens.length > 0) {
|
||||
String varName = tokens[tokens.length - 1];
|
||||
varName = varName.replaceAll("[\\[\\].();,]", "");
|
||||
if (!varName.isEmpty() && Character.isJavaIdentifierStart(varName.charAt(0))) {
|
||||
vars.add(varName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a valid identifier (not a number, not a keyword)
|
||||
if (isValidIdentifier(token) && !isKeyword(token)) {
|
||||
vars.add(token);
|
||||
if (statement.contains("for") && statement.contains("(")) {
|
||||
String forPart = statement.substring(statement.indexOf("("));
|
||||
String[] forParts = forPart.split(";");
|
||||
if (forParts.length >= 3) {
|
||||
if (forParts[0].contains("=")) {
|
||||
String[] initParts = forParts[0].split("=");
|
||||
if (initParts.length > 0) {
|
||||
String var = initParts[0].trim().replaceAll("[^a-zA-Z0-9_]", "");
|
||||
if (!var.isEmpty()) vars.add(var);
|
||||
}
|
||||
}
|
||||
String update = forParts[forParts.length - 1];
|
||||
if (update.contains("++") || update.contains("--")) {
|
||||
String var = update.replaceAll("[^a-zA-Z0-9_]", "");
|
||||
if (!var.isEmpty()) vars.add(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
private boolean isValidIdentifier(String s) {
|
||||
if (s == null || s.isEmpty()) return false;
|
||||
/**
|
||||
* Extract variable uses from a statement
|
||||
*/
|
||||
private Set<String> extractUses(String statement) {
|
||||
Set<String> vars = new HashSet<>();
|
||||
|
||||
// Check if it's a number
|
||||
try {
|
||||
Integer.parseInt(s);
|
||||
return false;
|
||||
} catch (NumberFormatException e) {
|
||||
// Not a number, continue
|
||||
if (statement.startsWith("*")) return vars;
|
||||
String[] tokens = statement.split("[\\s+\\-*/=<>!&|(){}\\[\\];,.]");
|
||||
for (String token : tokens) {
|
||||
token = token.trim();
|
||||
if (!token.isEmpty() &&
|
||||
Character.isJavaIdentifierStart(token.charAt(0)) &&
|
||||
!isKeyword(token) &&
|
||||
!token.matches("\\d+")) { r
|
||||
vars.add(token);
|
||||
}
|
||||
}
|
||||
vars.removeAll(extractDefs(statement));
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
if (!Character.isJavaIdentifierStart(s.charAt(0))) return false;
|
||||
for (int i = 1; i < s.length(); i++) {
|
||||
if (!Character.isJavaIdentifierPart(s.charAt(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isKeyword(String s) {
|
||||
/**
|
||||
* Check if a word is a Java keyword
|
||||
*/
|
||||
private boolean isKeyword(String word) {
|
||||
Set<String> keywords = new HashSet<>(Arrays.asList(
|
||||
"if", "else", "while", "for", "return", "int", "boolean", "void", "class",
|
||||
"public", "private", "static", "new", "this", "true", "false", "null",
|
||||
"break", "continue", "switch", "case", "default", "try", "catch", "finally",
|
||||
"throw", "throws", "extends", "implements", "abstract", "final", "native",
|
||||
"synchronized", "transient", "volatile", "strictfp", "package", "import"
|
||||
"if", "else", "while", "for", "do", "return", "break", "continue",
|
||||
"int", "double", "float", "char", "boolean", "void", "String",
|
||||
"new", "null", "true", "false", "class", "public", "private",
|
||||
"static", "final", "this", "super", "try", "catch", "throw", "throws"
|
||||
));
|
||||
return keywords.contains(s);
|
||||
return keywords.contains(word);
|
||||
}
|
||||
|
||||
// Compute reaching definitions using fixed-point analysis.
|
||||
private Map<Node, Set<Definition>> computeReachingDefinitions(Map<Node, Set<String>> defs) {
|
||||
Map<Node, Set<Definition>> reachingDefs = new HashMap<>();
|
||||
/**
|
||||
* Compute reaching definitions using iterative data flow analysis
|
||||
*/
|
||||
private Map<String, Map<String, Set<String>>> computeReachingDefinitions(
|
||||
Map<String, Set<String>> defs) {
|
||||
|
||||
// Initialize
|
||||
Map<String, Map<String, Set<String>>> reaching = new HashMap<>();
|
||||
for (Node node : cfg.nodes()) {
|
||||
reachingDefs.put(node, new HashSet<>());
|
||||
reaching.put(node.label(), new HashMap<>());
|
||||
}
|
||||
|
||||
// Fixed-point iteration
|
||||
boolean changed = true;
|
||||
while (changed) {
|
||||
int iterations = 0;
|
||||
while (changed && iterations < 100) {
|
||||
changed = false;
|
||||
iterations++;
|
||||
|
||||
for (Node node : cfg.nodes()) {
|
||||
Set<Definition> newReaching = new HashSet<>();
|
||||
|
||||
// Union of reaching definitions from all predecessors
|
||||
Set<Edge> inEdges = node.inEdges();
|
||||
for (Edge edge : inEdges) {
|
||||
Node pred = edge.source();
|
||||
Set<Definition> predReaching = new HashSet<>(reachingDefs.get(pred));
|
||||
|
||||
// Kill definitions of variables defined in pred
|
||||
Set<String> predDefs = defs.get(pred);
|
||||
predReaching.removeIf(def -> predDefs.contains(def.variable));
|
||||
|
||||
// Gen: Add new definitions from pred
|
||||
for (String var : predDefs) {
|
||||
predReaching.add(new Definition(pred, var));
|
||||
String label = node.label();
|
||||
Map<String, Set<String>> oldReaching = new HashMap<>(reaching.get(label));
|
||||
Map<String, Set<String>> newReaching = new HashMap<>();
|
||||
for (Edge inEdge : node.inEdges()) {
|
||||
Node pred = inEdge.source();
|
||||
String predLabel = pred.label();
|
||||
if (reaching.containsKey(predLabel)) {
|
||||
for (Map.Entry<String, Set<String>> entry : reaching.get(predLabel).entrySet()) {
|
||||
String var = entry.getKey();
|
||||
newReaching.putIfAbsent(var, new HashSet<>());
|
||||
newReaching.get(var).addAll(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
newReaching.addAll(predReaching);
|
||||
// Add predecessor's definitions
|
||||
for (String var : defs.get(predLabel)) {
|
||||
newReaching.putIfAbsent(var, new HashSet<>());
|
||||
newReaching.get(var).clear();
|
||||
newReaching.get(var).add(predLabel);
|
||||
}
|
||||
}
|
||||
|
||||
if (!newReaching.equals(reachingDefs.get(node))) {
|
||||
reachingDefs.put(node, newReaching);
|
||||
reaching.put(label, newReaching);
|
||||
|
||||
if (!mapsEqual(oldReaching, newReaching)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reachingDefs;
|
||||
return reaching;
|
||||
}
|
||||
|
||||
// Add control and data dependence edges to PDG.
|
||||
private void addPDGEdges() {
|
||||
// Add control dependence edges
|
||||
for (Node from : controlDependence.keySet()) {
|
||||
PDGNode fromPDG = cfgToPdgMap.get(from);
|
||||
for (Node to : controlDependence.get(from)) {
|
||||
PDGNode toPDG = cfgToPdgMap.get(to);
|
||||
PDGEdge edge = new PDGEdge(PDGEdge.EdgeType.CONTROL);
|
||||
try {
|
||||
pdg.addEdge(fromPDG, toPDG, edge);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Edge might already exist
|
||||
}
|
||||
private boolean mapsEqual(Map<String, Set<String>> m1, Map<String, Set<String>> m2) {
|
||||
if (m1.size() != m2.size()) return false;
|
||||
for (String key : m1.keySet()) {
|
||||
if (!m2.containsKey(key)) return false;
|
||||
if (!m1.get(key).equals(m2.get(key))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add data dependence edges
|
||||
for (Node from : dataDependence.keySet()) {
|
||||
PDGNode fromPDG = cfgToPdgMap.get(from);
|
||||
for (Node to : dataDependence.get(from)) {
|
||||
PDGNode toPDG = cfgToPdgMap.get(to);
|
||||
PDGEdge edge = new PDGEdge(PDGEdge.EdgeType.DATA);
|
||||
try {
|
||||
pdg.addEdge(fromPDG, toPDG, edge);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Edge might already exist
|
||||
}
|
||||
}
|
||||
private int countDependencies(Map<String, Set<String>> deps) {
|
||||
int count = 0;
|
||||
for (Set<String> set : deps.values()) {
|
||||
count += set.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
public Set<String> getDependencies(String nodeLabel) {
|
||||
Set<String> all = new HashSet<>();
|
||||
if (controlDeps.containsKey(nodeLabel)) {
|
||||
all.addAll(controlDeps.get(nodeLabel));
|
||||
}
|
||||
if (dataDeps.containsKey(nodeLabel)) {
|
||||
all.addAll(dataDeps.get(nodeLabel));
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute forward slice from a given node label.
|
||||
* Returns all nodes that could be impacted by a change to the given node.
|
||||
**/
|
||||
public Set<String> computeForwardSlice(String nodeLabel) {
|
||||
Node startCFGNode = cfg.findNode(nodeLabel); // Find the CFG node with this label
|
||||
if (startCFGNode == null) {
|
||||
return Collections.emptySet();
|
||||
Set<String> slice = new HashSet<>();
|
||||
Queue<String> worklist = new LinkedList<>();
|
||||
|
||||
slice.add(nodeLabel);
|
||||
worklist.add(nodeLabel);
|
||||
while (!worklist.isEmpty()) {
|
||||
String current = worklist.poll();
|
||||
for (String node : getAllNodes()) {
|
||||
if (!slice.contains(node)) {
|
||||
Set<String> deps = getDependencies(node);
|
||||
if (deps.contains(current)) {
|
||||
slice.add(node);
|
||||
worklist.add(node);
|
||||
}
|
||||
return computeForwardSliceFromNode(startCFGNode);
|
||||
}
|
||||
|
||||
// Compute forward slice from a given node.
|
||||
private Set<String> computeForwardSliceFromNode(Node startNode) {
|
||||
PDGNode startPDGNode = cfgToPdgMap.get(startNode);
|
||||
Set<String> impactedLabels = new HashSet<>();
|
||||
Set<PDGNode> visited = new HashSet<>();
|
||||
Queue<PDGNode> queue = new LinkedList<>();
|
||||
|
||||
queue.add(startPDGNode);
|
||||
visited.add(startPDGNode);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
PDGNode current = queue.poll();
|
||||
impactedLabels.add(current.getCfgNode().label());
|
||||
|
||||
// Follow all outgoing edges (both control and data)
|
||||
Set<PDGEdge> outEdges = pdg.outgoingEdgesOf(current);
|
||||
for (PDGEdge edge : outEdges) {
|
||||
PDGNode target = pdg.getEdgeTarget(edge);
|
||||
if (!visited.contains(target)) {
|
||||
visited.add(target);
|
||||
queue.add(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return impactedLabels;
|
||||
return slice;
|
||||
}
|
||||
|
||||
// Print the PDG structure.
|
||||
public void printPDG() {
|
||||
System.out.println("\n=== PDG Structure ===");
|
||||
|
||||
List<PDGNode> nodes = new ArrayList<>(pdg.vertexSet());
|
||||
nodes.sort(Comparator.comparing(n -> n.getCfgNode().label()));
|
||||
|
||||
for (PDGNode node : nodes) {
|
||||
System.out.print("Node: " + node.getCfgNode().label());
|
||||
System.out.print(" [Defs: " + node.getDefs() + ", Uses: " + node.getUses() + "]");
|
||||
System.out.println();
|
||||
|
||||
Set<PDGEdge> outEdges = pdg.outgoingEdgesOf(node);
|
||||
for (PDGEdge edge : outEdges) {
|
||||
PDGNode target = pdg.getEdgeTarget(edge);
|
||||
System.out.println(" -> " + edge.getType() + " dep on: " + target.getCfgNode().label());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print CFG structure for debugging.
|
||||
public void printCFG() {
|
||||
System.out.println("\n=== CFG Structure ===");
|
||||
System.out.println("Entry: " + cfg.entry.label());
|
||||
System.out.println("Normal Exit: " + cfg.normalExit.label());
|
||||
System.out.println("Abrupt Exit: " + cfg.abruptExit.label());
|
||||
System.out.println("\nNodes:");
|
||||
|
||||
private Set<String> getAllNodes() {
|
||||
Set<String> nodes = new HashSet<>();
|
||||
for (Node node : cfg.nodes()) {
|
||||
System.out.println(" " + node.label());
|
||||
for (Edge edge : node.outEdges()) {
|
||||
String target = edge.target() != null ? edge.target().label() : "null";
|
||||
System.out.println(" -> " + edge.label() + " to " + target);
|
||||
nodes.add(node.label());
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public void printPDG() {
|
||||
System.out.println("CONTROL DEPENDENCIES:");
|
||||
for (Map.Entry<String, Set<String>> entry : controlDeps.entrySet()) {
|
||||
if (!entry.getValue().isEmpty()) {
|
||||
System.out.println(" " + entry.getKey());
|
||||
for (String dep : entry.getValue()) {
|
||||
System.out.println(" <- " + dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the PDG graph.
|
||||
public Graph<PDGNode, PDGEdge> getGraph() {
|
||||
return pdg;
|
||||
System.out.println("\nDATA DEPENDENCIES:");
|
||||
for (Map.Entry<String, Set<String>> entry : dataDeps.entrySet()) {
|
||||
if (!entry.getValue().isEmpty()) {
|
||||
System.out.println(" " + entry.getKey());
|
||||
for (String dep : entry.getValue()) {
|
||||
System.out.println(" <- " + dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the underlying CFG.
|
||||
public ControlFlowGraph getCFG() {
|
||||
return cfg;
|
||||
public Map<String, Set<String>> getControlDependencies() {
|
||||
return new HashMap<>(controlDeps);
|
||||
}
|
||||
|
||||
//Helper class for tracking definitions
|
||||
private static class Definition {
|
||||
Node node;
|
||||
String variable;
|
||||
|
||||
Definition(Node node, String variable) {
|
||||
this.node = node;
|
||||
this.variable = variable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Definition)) return false;
|
||||
Definition that = (Definition) o;
|
||||
return node.equals(that.node) && variable.equals(that.variable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(node, variable);
|
||||
}
|
||||
public Map<String, Set<String>> getDataDependencies() {
|
||||
return new HashMap<>(dataDeps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Edge in the PDG representing either control or data dependence.
|
||||
*/
|
||||
package pdg;
|
||||
|
||||
import org.jgrapht.graph.DefaultEdge;
|
||||
|
||||
public class PDGEdge extends DefaultEdge {
|
||||
|
||||
public enum EdgeType {
|
||||
CONTROL, // Control dependence
|
||||
DATA // Data dependence
|
||||
}
|
||||
|
||||
private EdgeType type;
|
||||
|
||||
public PDGEdge(EdgeType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public EdgeType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
|
||||
/**
|
||||
* PDG Node that wraps a CFG node (org.lsmr.cfg.Node) and adds def-use information.
|
||||
*/
|
||||
package pdg;
|
||||
|
||||
import org.lsmr.cfg.Node;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
public class PDGNode {
|
||||
private Node cfgNode;
|
||||
private Set<String> defs;
|
||||
private Set<String> uses;
|
||||
|
||||
public PDGNode(Node cfgNode) {
|
||||
this.cfgNode = cfgNode;
|
||||
this.defs = new HashSet<>();
|
||||
this.uses = new HashSet<>();
|
||||
}
|
||||
|
||||
public Node getCfgNode() {
|
||||
return cfgNode;
|
||||
}
|
||||
|
||||
public Set<String> getDefs() {
|
||||
return defs;
|
||||
}
|
||||
|
||||
public void setDefs(Set<String> defs) {
|
||||
this.defs = defs;
|
||||
}
|
||||
|
||||
public Set<String> getUses() {
|
||||
return uses;
|
||||
}
|
||||
|
||||
public void setUses(Set<String> uses) {
|
||||
this.uses = uses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PDGNode{" + cfgNode.label() +
|
||||
", defs=" + defs + ", uses=" + uses + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof PDGNode)) return false;
|
||||
PDGNode pdgNode = (PDGNode) o;
|
||||
return cfgNode.equals(pdgNode.cfgNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(cfgNode);
|
||||
}
|
||||
}
|
||||
29
tests/Example1.java
Normal file
29
tests/Example1.java
Normal file
@@ -0,0 +1,29 @@
|
||||
public class Example1 {
|
||||
public void test() {
|
||||
int x = 5;
|
||||
int y = x + 10;
|
||||
int z = y * 2;
|
||||
int result = z + x;
|
||||
}
|
||||
public void test1(int a) {
|
||||
int x = 5;
|
||||
int y = 0;
|
||||
|
||||
if (a > 10) {
|
||||
y = x + a;
|
||||
} else {
|
||||
y = x - a;
|
||||
}
|
||||
|
||||
int result = y * 2;
|
||||
}
|
||||
public void test2() {
|
||||
int sum = 0;
|
||||
int i = 0;
|
||||
|
||||
while (i < 10) {
|
||||
sum = sum + i;
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user