Skip to content

Commit 1769df5

Browse files
committed
Merge branch 'develop' into fix-configure
2 parents 1d227f5 + 0094462 commit 1769df5

File tree

7 files changed

+68
-38
lines changed

7 files changed

+68
-38
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
DiffDetective is an open-source Java library for variability-aware source code differencing and the **analysis of version histories of software product lines**. This means that DiffDetective can **turn a generic differencer into a variability-aware differencer** by means of a pre- or post-processing. DiffDetective is centered around **formally verified** data structures for variability (variation trees) and variability-aware diffs (variation diffs). These data structures are **generic**, and DiffDetective currently implements **C preprocessor support** to parse respective annotations when used to implement variability. The picture below depicts the process of variability-aware differencing.
1212

13-
<img alt="Variability-Aware Differencing Overview" src="docs/teaser.png" height="500" />
13+
<img alt="Variability-Aware Differencing Overview" src="docs/variability-aware-differencing.png" height="500" />
1414

1515
Given two states of a C-preprocessor annotated source code file (left), for example before and after a commit, DiffDetective constructs a variability-aware diff (right) that distinguishes changes to source code from changes to variability annotations. DiffDetective can construct such a variation diff either, by first using a generic differencer, and separating the information (center path), or by first parsing both input versions to an abstract representation, a variation tree (center top and bottom), and constructing a variation diff using a tree differencing algorithm in a second step.
1616

@@ -80,6 +80,18 @@ Additionally, there is a screencast available on YouTube, guiding you through th
8080
[![DiffDetective Demonstration](docs/yt_thumbnail.png)](https://www.youtube.com/watch?v=q6ight5EDQY)
8181

8282

83+
## Supported Differencing Algorithms
84+
85+
In principle, any generic differencing algorithm (i.e, any algorithm that may operate on text or trees) can be made variability-aware with DiffDetective, as explained in our demo paper (see below). Some algorithms are integrated directly in the DiffDetective library, while others come as additional Maven projects.
86+
87+
### Shipped with DiffDetective
88+
- Git Diff as implemented by [JGit](https://github.com/eclipse-jgit/jgit)
89+
- [GumTree](https://github.com/GumTreeDiff/gumtree), and all algorithms and matching engines supported by the GumTree library
90+
91+
### Extra Modules
92+
- [TrueDiff](https://gitlab.rlp.net/plmz/truediff): Support for TrueDiff comes as [a separate Maven project](https://github.com/VariantSync/TrueDiffDetective).
93+
94+
8395
## Publications
8496

8597
### Variability-Aware Differencing with DiffDetective (FSE 2024, ⭐ [Best Demo Paper](https://2024.esec-fse.org/info/awards) ⭐)
@@ -167,9 +179,10 @@ Edge-typed variation diffs and the replication package are implemented in a fork
167179

168180
DiffDetective was extended and used within bachelor's and master's theses:
169181

182+
- _Unparsing von Datenstrukturen zur Analyse von C-Präprozessor-Variabilität_, Eugen Shulimov, Bachelor's Thesis, 2025, [DOI 10.17619/UNIPB/1-2385](http://doi.org/10.17619/UNIPB/1-2385), (german): Eugen added an unparser for variation trees, essentially inverting the horizontal arrows in our commuting diagram at the top of this README file. The unparser for variation diffs reuses the unparser for variation trees by projecting a variation diff to its two variation trees (before and after the change), unparsing the trees, and then diffing the obtained text files to eventually compute a text-based diff.
170183
- _Constructing Variation Diffs Using Tree Diffing Algorithms_, Benjamin Moosherr, Bachelor's Thesis, 2023, [DOI 10.18725/OPARU-50108](https://dx.doi.org/10.18725/OPARU-50108): Benjamin added support for tree-differencing and integrated the GumTree differencer ([Github](https://github.com/GumTreeDiff/gumtree), [Paper](https://doi.org/10.1145/2642937.2642982)). In his thesis, Benjamin also reviewed a range of quality metrics for tree-diffs with focus on their applicability for rating variability-aware diffs. The [org.variantsync.diffdetective.experiments.thesis_bm](src/main/java/org/variantsync/diffdetective/experiments/thesis_bm) package implements the corresponding empirical study and may serve as an example on how to use the tree-differencing.
171184
- _Reverse Engineering Feature-Aware Commits From Software Product-Line Repositories_, Lukas Bormann, Bachelor's Thesis, 2023, [10.18725/OPARU-47892](https://dx.doi.org/10.18725/OPARU-47892): Lukas implemented an algorithm for feature-based commit-untangling, which turns variation diff into a series of smaller diffs, each of which contains an edit to a single feature or feature formula. This work was later refined in our publication _Views on Edits to Variational Software_ illustrated above.
172-
- _Inspecting the Evolution of Feature Annotations in Configurable Software_, Lukas Güthing, Master's Thesis, 2023: Lukas implemented different edge-types for associating variability annotations within variation diffs. He published his work later at VaMoS 2024 under the title _Explaining Edits to Variability Annotations in Evolving Software Product Lines_, illustrated above.
185+
- _Inspecting the Evolution of Feature Annotations in Configurable Software_, Lukas Güthing, Master's Thesis, 2023: Lukas implemented different edge-types for associating variability annotations within variation diffs. He published his work later at VaMoS 2024 under the title _Explaining Edits to Variability Annotations in Evolving Software Product Lines_, illustrated above. His work can be found in a [fork][forklg] of DiffDetective.
173186
- _Empirical Evaluation of Feature Trace Recording on the Edit History of Marlin_, Sören Viegener, Bachelor's Thesis, 2021, [DOI 10.18725/OPARU-38603](http://dx.doi.org/10.18725/OPARU-38603): In his thesis, Sören started the DiffDetective project and implemented the first version of an algorithm, which parses text-based diffs to C-preprocessor files to variation diffs. He also came up with an initial classification of edits, which we wanted to reuse to evaluate [Feature Trace Recording](https://variantsync.github.io/FeatureTraceRecording/), a method for deriving variability annotations from annotated patches.
174187

175188
[documentation]: https://variantsync.github.io/DiffDetective/docs/javadoc

docs/teaser.png

-286 KB
Binary file not shown.
160 KB
Loading

src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -742,10 +742,14 @@ public static DiffNode<DiffLinesLabel> fromID(int id, String label) {
742742
* Checks that the VariationDiff is in a valid state.
743743
* In particular, this method checks that all edges are well-formed (e.g., edges can be inconsistent because edges are double-linked).
744744
* This method also checks that a node with exactly one parent was edited, and that a node with exactly two parents was not edited.
745+
* To check all children recursively, use {@link VariationDiff#assertConsistency}.
745746
* @see Assert#assertTrue
746747
* @throws AssertionError when an inconsistency is detected.
747748
*/
748749
public void assertConsistency() {
750+
// check that the projections are valid (i.e., node type specific consistency checks)
751+
diffType.forAllTimesOfExistence(time -> this.projection(time).assertConsistency());
752+
749753
// check consistency of children lists and edges
750754
for (final DiffNode<L> c : getAllChildren()) {
751755
Assert.assertTrue(isChild(c), () -> "Child " + c + " of " + this + " is neither a before nor an after child!");
@@ -769,6 +773,10 @@ public void assertConsistency() {
769773
if (pb != null && pa == null) {
770774
Assert.assertTrue(isRem());
771775
}
776+
// the root was not edited
777+
if (pb == null && pa == null) {
778+
Assert.assertTrue(isNon());
779+
}
772780
// a node with exactly two parents was not edited
773781
if (pb != null && pa != null) {
774782
Assert.assertTrue(isNon());
@@ -779,22 +787,6 @@ public void assertConsistency() {
779787
Assert.assertTrue(pb.isNon());
780788
}
781789
}
782-
783-
// Else and Elif nodes have an If or Elif as parent.
784-
if (this.isElse() || this.isElif()) {
785-
Time.forAll(time -> {
786-
if (getParent(time) != null) {
787-
Assert.assertTrue(getParent(time).isIf() || getParent(time).isElif(), time + " parent " + getParent(time) + " of " + this + " is neither IF nor ELIF!");
788-
}
789-
});
790-
}
791-
792-
// Only if and elif nodes have a formula
793-
if (this.isIf() || this.isElif()) {
794-
Assert.assertTrue(this.getFormula() != null, "If or elif without feature mapping!");
795-
} else {
796-
Assert.assertTrue(this.getFormula() == null, "Node with type " + getNodeType() + " has a non null feature mapping");
797-
}
798790
}
799791

800792
/**

src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,9 @@ public Node getFormula() {
130130
public int getID() {
131131
return getBackingNode().getID();
132132
}
133+
134+
@Override
135+
public String toString() {
136+
return String.format("Projection(%s, %s)", time, getBackingNode());
137+
}
133138
};

src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -499,26 +499,28 @@ public VariationTreeNode<L> toVariationTree(final Map<? super T, VariationTreeNo
499499
}
500500

501501
/**
502-
* Checks that this node satisfies some easy to check invariants.
503-
* In particular, this method checks that
504-
* <ul>
505-
* <li>if-chains are nested correctly,
506-
* <li>the root is an {@link NodeType#IF} with the feature mapping {@code "true"},
507-
* <li>the feature mapping is {@code null} iff {@code isConditionalAnnotation} is {@code false}
508-
* and
509-
* <li>all edges are well-formed (e.g., edges can be inconsistent because edges are
510-
* double-linked).
511-
* </ul>
512-
*
513-
* <p>Some invariants are not checked. These include
514-
* <ul>
515-
* <li>There should be no cycles and
516-
* <li>{@link getID} should be unique in the whole variation tree.
517-
* </ul>
518-
*
519-
* @see Assert#assertTrue
520-
* @throws AssertionError when an inconsistency is detected.
521-
*/
502+
* Checks that this node satisfies some easy to check invariants.
503+
* In particular, this method checks that
504+
* <ul>
505+
* <li>if-chains are nested correctly,
506+
* <li>the root is an {@link NodeType#IF} with the feature mapping {@code "true"},
507+
* <li>the feature mapping is {@code null} iff {@code isConditionalAnnotation} is {@code false}
508+
* and
509+
* <li>all edges are well-formed (e.g., edges can be inconsistent because edges are
510+
* double-linked).
511+
* </ul>
512+
*
513+
* <p>Some invariants are not checked. These include
514+
* <ul>
515+
* <li>There should be no cycles,
516+
* <li>{@link getID} should be unique in the whole variation tree, and
517+
* <li>children are not checked recursively.
518+
* </ul>
519+
* Use {@link VariationTree#assertConsistency} to check all children recursively.
520+
*
521+
* @see Assert#assertTrue
522+
* @throws AssertionError when an inconsistency is detected.
523+
*/
522524
public void assertConsistency() {
523525
// ELSE and ELIF nodes have an IF or ELIF as parent.
524526
if (isElse() || isElif()) {
@@ -549,6 +551,12 @@ public void assertConsistency() {
549551
"The root has to have the feature mapping 'true'");
550552
}
551553

554+
// check that there is at most one ELIF/ELSE
555+
Assert.assertTrue(
556+
getChildren().stream().filter(c -> c.isElif() || c.isElse()).count() <= 1,
557+
"There is more than one ELIF/ELSE node."
558+
);
559+
552560
// check consistency of children lists and edges
553561
for (var child : getChildren()) {
554562
Assert.assertTrue(

src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.variantsync.diffdetective.util.Assert;
66
import org.variantsync.diffdetective.variation.DiffLinesLabel;
77
import org.variantsync.diffdetective.variation.Label;
8+
import org.variantsync.diffdetective.variation.NodeType; // For Javadoc
89
import org.variantsync.diffdetective.variation.diff.DiffNode;
910
import org.variantsync.diffdetective.variation.diff.VariationDiff;
1011
import org.variantsync.diffdetective.variation.diff.Projection;
@@ -223,6 +224,17 @@ public String unparse() {
223224
return result.toString();
224225
}
225226

227+
/**
228+
* Checks whether this {@link VariationTree} is consistent.
229+
* Throws an error if this {@link VariationTree} is inconsistent (e.g., if there are multiple
230+
* {@link NodeType#ELSE} nodes). Has no side-effects otherwise.
231+
*
232+
* @see VariationNode#assertConsistency
233+
*/
234+
public void assertConsistency() {
235+
forAllPreorder(VariationTreeNode::assertConsistency);
236+
}
237+
226238
@Override
227239
public String toString() {
228240
return "variation tree from " + source;

0 commit comments

Comments
 (0)