Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
79c0dd3
Add Regexp-Regexp Concat operator for Grammar DSL
vkutuev Feb 10, 2026
36a33ce
Add paths extraction app
vkutuev Feb 10, 2026
5ece2ae
Add more examples
vkutuev Feb 10, 2026
38dd6a0
Optimize imports in path extractor
vkutuev Feb 11, 2026
8672268
Remove unneeded class in path extractor
vkutuev Feb 11, 2026
573f321
Add sppf saving to path extractor
vkutuev Feb 11, 2026
7e7ea11
Corrected grammar. More examples.
gsvgit Feb 11, 2026
0f27589
Multiple paths from loops.
gsvgit Feb 11, 2026
38072c4
Corrected grammar. Corrected examples.
gsvgit Feb 11, 2026
0cc147c
Brief readme.
gsvgit Feb 11, 2026
dc6ab72
Small code cleanup.
gsvgit Feb 11, 2026
77f2a5e
Corrected grammar. More examples.
gsvgit Feb 11, 2026
85a5231
Multiple paths from loops.
gsvgit Feb 11, 2026
03b2582
Corrected grammar. Corrected examples.
gsvgit Feb 11, 2026
b813da6
Brief readme.
gsvgit Feb 11, 2026
19c1444
Small code cleanup.
gsvgit Feb 11, 2026
ff15198
Fix examples. Not finished.
gsvgit Feb 20, 2026
5ca7f8a
Added explicit Points-to analysis
gsvgit Feb 20, 2026
9d28557
Draft of updated readme.
gsvgit Feb 20, 2026
cb70a3c
Impreved readme: detailed description of examples
gsvgit Feb 20, 2026
cb8c88e
Missed figures
gsvgit Feb 20, 2026
5c16ff5
Merge branch 'path-extractor' into path-extractor
gsvgit Feb 20, 2026
4ddcde2
Merge pull request #2 from gsvgit/path-extractor
vkutuev Feb 20, 2026
7ddd261
Fix module and packages names
vkutuev Mar 8, 2026
29e3a67
docs(cfpq-paths-app): add reference to points-to grammar source
UsatovPavel Oct 17, 2026
ca64e90
chore(cfpq-paths-app): move figures to docs/docs/figures, ignore gen/
UsatovPavel Oct 17, 2026
45f8251
Merge upstream main into path-extractor
UsatovPavel Oct 17, 2026
396e7fe
fix(cfpq-paths-app): remove ambiguous regexp.or import
UsatovPavel Oct 17, 2026
f0a7e0d
test(cfpq-paths-app): add tests for path extraction and points-to exa…
UsatovPavel Oct 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
### Generated files ###
/benchmarks/gen/
/benchmarks/logs/
/cfpq-paths-app/gen/

### Antlr parsers ###
/benchmarks/src/main/java/org/antlr/*
Expand Down
248 changes: 248 additions & 0 deletions cfpq-paths-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
>[!CAUTION]
>For demo purposes only!
>Do not expect big graphs to be processed successfully (in reasonable time or without out-of-memory errors).

This demo is based on UCFS, which, for a given grammar represented as an RSM, a graph, and start vertices, produces an SPPF.

**RSM** (Recursive State Machine) is an automaton-like representation of context-free languages.

**SPPF** (Shared Packed Parse Forest) is a derivation-tree-like structure that represents **all** possible paths satisfying the specified grammar. If the number of such paths is infinite, the SPPF contains cycles.
SPPF consists of nodes of the types listed below. Each node has a unique Id and detailed information specific to its type.

* **Nonterminal** node contains the name of the non-terminal and pairs of vertices from the input graph that are the start and end of paths derived from that non-terminal.

![Graph for example 1](../docs/docs/figures/Nonterm_example.dot.svg)

This node has number ```0``` and is the root of all derivations for all paths from 1 to 4 derivable from non-terminal ```S```

* **Terminal** node is a leaf and corresponds to an edge.

![Graph for example 1](../docs/docs/figures/Terminal_example.dot.svg)

This node depicts edge ```3 -alloc-> 4```.

* **Epsilon** node is a simplified way to represent that $\varepsilon$ is derived at a specific position.

![Graph for example 1](../docs/docs/figures/Epsilon_example.dot.svg)

* **Range** node is a supplementary node that helps reuse subtrees.

![Graph for example 1](../docs/docs/figures/Range_example.dot.svg)

This node represents all subpaths from 0 to 4 that are accepted while the RSM transitions from ```S_0``` to ```S_2```.

* **Intermediate** node is a supplementary node used to connect subpaths.

![Graph for example 1](../docs/docs/figures/Intermediate_example.dot.svg)

This node depicts that the path from 0 to 2 is composed of two parts: from 0 to 1 and from 1 to 2.

**Requirements**: 11 java

**To run (from project root)**:

```bash
./gradlew :cfpq-app:run
```

**Input graphs:** ```src/main/resources/```

**Grammar and code for paths extraction:** ```src/main/kotlin/me/vkutuev/Main.kt```

>[!NOTE]
> We implemented a very naive path extraction algorithm solely to demonstrate SPPF traversal.

## Examples

We provide a few code snippets, the corresponding graphs to be analyzed, parts of the resulting SPPFs, and extracted paths.

For analysis, we use the following extended points-to grammar (start non-terminal is ```S```), which allows us to analyze chains of fields.
```
PointsTo -> ("assign" | ("load_i" Alias "store_i"))* "alloc"
FlowsTo -> "alloc_r" ("assign_r" | ("store_i_r" Alias "load_o_r"))*
Alias -> PointsTo FlowsTo
S -> (Alias? "store_i")* PointsTo
```

The `PointsTo`, `FlowsTo` and `Alias` rules follow the field-sensitive points-to grammar from [Dietrich et al., "Giga-scale exhaustive points-to analysis for Java in under a minute", OOPSLA 2015](https://doi.org/10.1145/2814270.2814307).

For all our examples, we use a common grammar with $i \in [0..3]$.
The corresponding RSM is presented below:

![Graph for example 1](../docs/docs/figures/rsm.dot.svg)

### Example 1
Code snippet:
```java
val n = new X()
val y = new Y()
val z = new Z()
val l = n
val t = y
l.u = y
t.v = z
```

Respective graph:

![Graph for example 1](../docs/docs/figures/graph_1.dot.svg)

Resulting SPPF:

![SPPF for example 1](../docs/docs/figures/graph_1_sppf.dot.svg)

Three trees are extracted because there are three paths of interest from node 1.
We do not extract subpaths derivable from non-terminals ```Alias``` and ```PointsTo```, as they contain no useful information for restoring fields.


Respective paths:

* [(1-PointsTo->0)]

This path is trivial. Such paths will be omitted in further examples.

* [(1-Alias->2), (2-store_0->3), (3-PointsTo->4)]

This path means that ```n.u = new Y()```. Vertex 2 is an alias for 1 (corresponding to ```n```), and 2 has a field ```u``` that points to ```new Y()``` (```store_0``` corresponds to ```l.u = y```).

* [(1-Alias->2), (2-store_0->3), (3-Alias->5), (5-store_1->6), (6-PointsTo->7)]

This path means that ```n.u.v = new Z()```.


### Example 2

Code snippet:
```java
val n = new X()
val l = n
while (...){
l.next = new X()
l = l.next
}
```

Respective graph:

![Graph for example 2](../docs/docs/figures/graph_2.dot.svg)

Part of resulting SPPF:

![SPPF for example 2](../docs/docs/figures/graph_2_sppf.dot.svg)

This part contains a cycle formed by vertices 27–31–34–37–38–40–42–44–47–49–52–56 (colored in red). This is because there are infinitely many paths of interest. We extract some of them:

* [(0-Alias->2), (2-store_0->3), (3-PointsTo->4)]

```n.next = new X () // line 4```

* [(0-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-PointsTo->4)]

```n.next.next = new X () // line 4```

* [(0-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-PointsTo->4)]

```n.next.next.next = new X () // line 4```

* [(0-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-PointsTo->4)]

```n.next.next.next.next = new X () // line 4```

* [(0-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-PointsTo->4)]

```n.next.next.next.next.next = new X () // line 4```

* [(0-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-Alias->2), (2-store_0->3), (3-PointsTo->4)]

```n.next.next.next.next.next.next = new X () // line 4```

More paths can be extracted if needed. Traversal should be tuned accordingly.

### Example 3

Code snippet:
```java
val n = new X()
val l = n
while (...){
val t = new X()
l.next = t
l = t
}
```

Respective graph:

![Graph for example 3](../docs/docs/figures/graph_3.dot.svg)

Part of resulting SPPF:

![SPPF for example 3](../docs/docs/figures/graph_3_sppf.dot.svg)

This SPPF also contains a cycle (3–5–7–11), so there are infinitely many paths of interest, and we extract only a few of them.

* [(0-Alias->1), (1-store_0->2), (2-PointsTo->3)]

```n.next = new X() // line 4```

* [(0-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-PointsTo->3)]

```n.next.next = new X() // line 4```

* [(0-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-PointsTo->3)]

```n.next.next.next = new X() // line 4```

* [(0-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-PointsTo->3)]

```n.next.next.next.next = new X() // line 4```

* [(0-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-PointsTo->3)]

```n.next.next.next.next.next = new X() // line 4```

* [(0-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-Alias->1), (1-store_0->2), (2-PointsTo->3)]

```n.next.next.next.next.next.next = new X() // line 4```


### Example 4

Code snippet:

```java
val n = new X()
val z = new Z()
val u = new U()
z.x = n
u.y = n
val v = z.x
v.p = new Y()
val r = u.y
r.q = new P()
```
Respective graph:

![Graph for example 4](../docs/docs/figures/graph_4.dot.svg)

For this example, we omit the figure of the SPPF due to its size. However, we present the respective paths. Note that in this example, we specify two vertices as start: 1 and 8.

* [(1-Alias->9), (9-store_3->11), (11-PointsTo->13)]

```n.q = new P()```

* [(1-Alias->8), (8-store_2->10), (10-PointsTo->12)]

```n.p = new Y() ```

* [(8-Alias->9), (9-store_3->11), (11-PointsTo->13)]

```v.q = new P() ```

* [(8-store_2->10), (10-PointsTo->12)]

```v.p = new Y() ```

* [(8-Alias->8), (8-store_2->10), (10-PointsTo->12)]

```v.p = new Y() ```
26 changes: 26 additions & 0 deletions cfpq-paths-app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
kotlin("jvm") version "2.3.0"
application
}

repositories {
mavenCentral()
}

dependencies {
implementation(kotlin("stdlib"))
implementation(project(":solver"))

testImplementation(kotlin("test"))
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
}

kotlin {
jvmToolchain(11)
}

tasks.test { useJUnitPlatform() }

application {
mainClass = "org.ucfs.paths.MainKt"
}
Loading