Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f480939
Initial Commit
robinmaisch Dec 29, 2023
6ae3fc5
Fixed ReplaceOperation to support swapping elements
robinmaisch Jan 1, 2024
0c6be35
Reorder passes; Fixed Token generation; Extended NodePattern relations
robinmaisch Jan 5, 2024
185b311
Bump CPG dependencies from 8.0.0-alpha-2 to 8.0.0
robinmaisch Jan 5, 2024
7ab9b0f
Add patterns to match one edge of a multi-edge; Introduce 2nd transfo…
robinmaisch Jan 6, 2024
100296a
Major new features:
robinmaisch Feb 29, 2024
4447224
Fixed old tests and some code problems indicated by the IDE
robinmaisch Feb 29, 2024
1b900a8
Merge branch 'jplag:main' into cpg-dev
robinmaisch Feb 29, 2024
722b256
Fix errors from merging JPlag v5.0.0
robinmaisch Feb 29, 2024
4ed18ea
Feature-complete version (before Sonar)
robinmaisch Mar 18, 2024
5536a3f
Apply Spotless
robinmaisch Mar 18, 2024
95fcb9e
Remove CompareFrontendsTest.java
robinmaisch Mar 18, 2024
640e1fa
Fix tests
robinmaisch Mar 18, 2024
eb592f0
Fix tests
robinmaisch Mar 18, 2024
88dd331
Fix JavaDoc
robinmaisch Mar 19, 2024
fc3d079
Add more documentation
robinmaisch Mar 19, 2024
83995bf
Merge remote-tracking branch 'origin/develop' into cpg-dev
robinmaisch Mar 19, 2024
9ad7388
Fix Sonar remarks
robinmaisch Mar 23, 2024
9c25f9f
Add missing files
robinmaisch Mar 23, 2024
b2fc2f1
Fix Regex
robinmaisch Mar 23, 2024
2e1e333
Improve documentation and consistency of type parameter naming
robinmaisch Mar 26, 2024
50b83ea
Revert changes to base code
robinmaisch Apr 2, 2024
1acd991
Revert changes to base code – No.2
robinmaisch Apr 2, 2024
7946823
Add support for Java-CPG module in report viewer
robinmaisch Apr 2, 2024
60427fd
Fix missing comma
robinmaisch Apr 3, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Parameter descriptions:
The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown (default: 500)
-new, --new=<newDirectories>[,<newDirectories>...]
Root-directories with submissions to check for plagiarism (same as root).
--normalize Activate the normalization of tokens. Supported for languages: Java, C++.
--normalize Activate the normalization of tokens. Supported for languages: Java, Java-CPG, C++.
-old, --old=<oldDirectories>[,<oldDirectories>...]
Root-directories with prior submissions to compare against.
-r, --result-file=<resultFile>
Expand Down
5 changes: 5 additions & 0 deletions cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
<artifactId>java</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>de.jplag</groupId>
<artifactId>java-cpg</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>de.jplag</groupId>
<artifactId>python-3</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion cli/src/main/java/de/jplag/cli/CliOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class CliOptions implements Runnable {
"--mode"}, description = "The mode of JPlag: either only run analysis, only open the viewer, or do both (default: ${DEFAULT_VALUE})")
public JPlagMode mode = JPlagMode.RUN;

@Option(names = {"--normalize"}, description = "Activate the normalization of tokens. Supported for languages: Java, C++.")
@Option(names = {"--normalize"}, description = "Activate the normalization of tokens. Supported for languages: Java, Java-CPG, C++.")
public boolean normalize = false;

@ArgGroup(heading = "%nAdvanced%n", exclusive = false)
Expand Down
2 changes: 1 addition & 1 deletion cli/src/test/java/de/jplag/cli/LanguageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void testInvalidLanguage() {
@Test
void testLoading() {
var languages = LanguageLoader.getAllAvailableLanguages();
assertEquals(19, languages.size(), "Loaded Languages: " + languages.keySet());
assertEquals(20, languages.size(), "Loaded Languages: " + languages.keySet());
}

@Test
Expand Down
2 changes: 1 addition & 1 deletion docs/1.-How-to-Use-JPlag.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Parameter descriptions:
The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown (default: 500)
-new, --new=<newDirectories>[,<newDirectories>...]
Root-directories with submissions to check for plagiarism (same as root).
--normalize Activate the normalization of tokens. Supported for languages: Java, C++.
--normalize Activate the normalization of tokens. Supported for languages: Java, Java-CPG, C++.
-old, --old=<oldDirectories>[,<oldDirectories>...]
Root-directories with prior submissions to compare against.
-r, --result-file=<resultFile>
Expand Down
87 changes: 87 additions & 0 deletions languages/java-cpg/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.jplag</groupId>
<artifactId>languages</artifactId>
<version>${revision}</version>
</parent>

<artifactId>java-cpg</artifactId>

<properties>
<cpg.version>8.0.0</cpg.version>
<cpg.groupId>de.fraunhofer.aisec</cpg.groupId>
<kotlin.version>1.9.0</kotlin.version>
</properties>

<dependencies>

<dependency>
<groupId>${cpg.groupId}</groupId>
<artifactId>cpg-core</artifactId>
<version>${cpg.version}</version>

<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${cpg.groupId}</groupId>
<artifactId>cpg-language-java</artifactId>
<version>${cpg.version}</version>
</dependency>
<dependency>
<groupId>${cpg.groupId}</groupId>
<artifactId>cpg-analysis</artifactId>
<version>${cpg.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>1.8</jvmTarget>
<args>
<arg>-Xcontext-receivers</arg>
</args>
</configuration>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<phase>process-sources</phase>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<phase>test-compile</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
128 changes: 128 additions & 0 deletions languages/java-cpg/src/main/java/de/jplag/java_cpg/CpgAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package de.jplag.java_cpg;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;

import de.fraunhofer.aisec.cpg.*;
import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage;
import de.fraunhofer.aisec.cpg.passes.*;
import de.jplag.ParsingException;
import de.jplag.Token;
import de.jplag.java_cpg.passes.*;
import de.jplag.java_cpg.transformation.GraphTransformation;
import de.jplag.java_cpg.transformation.GraphTransformation.ExecutionPhase;

import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;

/**
* This class handles the transformation of files of code to a token list.
*/
public class CpgAdapter {

private List<Token> tokenList;
private boolean reorderingEnabled = true;

/**
* Constructs a new CpgAdapter.
* @param transformations a list of {@link GraphTransformation}s
*/
public CpgAdapter(GraphTransformation... transformations) {
addTransformations(transformations);
}

/* package-private */ List<Token> adapt(Set<File> files, boolean normalize) throws ParsingException, InterruptedException {
assert !files.isEmpty();
tokenList = null;
if (!normalize) {
clearTransformations();
setReorderingEnabled(false);
}
// TokenizationPass sets tokenList
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field tokenList is set to null a few lines before. I thought I'd include that comment to clarify that the tokenList is not supposed to still be null when it is returned in line 49, but it is set by the TokenizationPass via the method CpgAdapter.setTokenList(List<Token>).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay, this was not clear to me, thanks.


translate(files);

return tokenList;
}

/**
* Adds a transformation at the end of its respective ATransformationPass.
* @param transformation a {@link GraphTransformation}
*/
public void addTransformation(GraphTransformation transformation) {
switch (transformation.getPhase()) {
case OBLIGATORY -> PrepareTransformationPass.registerTransformation(transformation);
case AST_TRANSFORM -> AstTransformationPass.registerTransformation(transformation);
case CPG_TRANSFORM -> CpgTransformationPass.registerTransformation(transformation);
}
}

/**
* Registers the given transformations to be applied in the transformation step.
* @param transformations the transformations
*/
public void addTransformations(GraphTransformation[] transformations) {
Arrays.stream(transformations).forEach(this::addTransformation);
}

/**
* Clears all non-{@link ExecutionPhase#OBLIGATORY} transformations from the pipeline.
*/
public void clearTransformations() {
AstTransformationPass.clearTransformations();
CpgTransformationPass.clearTransformations();
}

private <T extends Pass<?>> KClass<T> getKClass(Class<T> javaPassClass) {
return JvmClassMappingKt.getKotlinClass(javaPassClass);
}

/**
* Sets <code>reorderingEnabled</code>. If true, statements may be reordered.
* @param enabled value for reorderingEnabled.
*/
public void setReorderingEnabled(boolean enabled) {
this.reorderingEnabled = enabled;
}

private void setTokenList(List<Token> tokenList) {
this.tokenList = tokenList;
}

/* package-private */ TranslationResult translate(Set<File> files) throws ParsingException, InterruptedException {
InferenceConfiguration inferenceConfiguration = InferenceConfiguration.builder().guessCastExpressions(true).inferRecords(true)
.inferDfgForUnresolvedCalls(true).build();

TranslationResult translationResult;
TokenizationPass.Companion.setCallback(CpgAdapter.this::setTokenList);
try {
TranslationConfiguration.Builder configBuilder = new TranslationConfiguration.Builder().inferenceConfiguration(inferenceConfiguration)
.sourceLocations(files.toArray(new File[] {})).registerLanguage(new JavaLanguage());

List<Class<? extends Pass<?>>> passClasses = new ArrayList<>(List.of(TypeResolver.class, TypeHierarchyResolver.class,
ImportResolver.class, SymbolResolver.class, PrepareTransformationPass.class, FixAstPass.class, DynamicInvokeResolver.class,
FilenameMapper.class, AstTransformationPass.class, EvaluationOrderGraphPass.class, // creates
// EOG
DfgSortPass.class, CpgTransformationPass.class, TokenizationPass.class));

if (!reorderingEnabled)
passClasses.remove(DfgSortPass.class);

for (Class<? extends Pass<?>> passClass : passClasses) {
configBuilder.registerPass(getKClass(passClass));
}

translationResult = TranslationManager.builder().config(configBuilder.build()).build().analyze().get();

} catch (InterruptedException e) {
throw e;
} catch (ExecutionException | ConfigurationException e) {
throw new ParsingException(List.copyOf(files).getFirst(), e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the copy when only one element is passed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because any one element can not be gotten from the Set<File> files. I agree that this is not very elegant.
Maybe replace it with files.stream().findAny().orElse(null)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use files.iterator().next(); instead.

}
return translationResult;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package de.jplag.java_cpg;

import static de.jplag.java_cpg.transformation.TransformationRepository.*;

import java.io.File;
import java.util.List;
import java.util.Set;

import org.kohsuke.MetaInfServices;

import de.jplag.Language;
import de.jplag.ParsingException;
import de.jplag.Token;
import de.jplag.java_cpg.transformation.GraphTransformation;

/**
* This class represents the frond end of the CPG module of JPlag.
*/
@MetaInfServices(de.jplag.Language.class)
public class JavaCpgLanguage implements Language {
private static final int DEFAULT_MINIMUM_TOKEN_MATCH = 9;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could take the dafault mtm of the java module instead of setting it to 9

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is possible, but it would require a module dependency on the Java frontend.
Do you think that would be justified?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that would not be justified.

private static final String[] FILE_EXTENSIONS = {".java"};
private static final String NAME = "Java Code Property Graph module";
private static final String IDENTIFIER = "java-cpg";
private final CpgAdapter cpgAdapter;

/**
* Creates a new {@link JavaCpgLanguage}.
*/
public JavaCpgLanguage() {
this.cpgAdapter = new CpgAdapter(allTransformations());
}

/**
* Adds the given {@link GraphTransformation} to the list to apply to the submissions.
* @param transformation the transformation
*/
public void addTransformation(GraphTransformation transformation) {
this.cpgAdapter.addTransformation(transformation);
}

/**
* Adds the given {@link GraphTransformation}s to the list to apply to the submissions.
* @param transformations the transformations
*/
public void addTransformations(GraphTransformation[] transformations) {
this.cpgAdapter.addTransformations(transformations);
}

@Override
public String getIdentifier() {
return IDENTIFIER;
}

@Override
public String getName() {
return NAME;
}

@Override
public int minimumTokenMatch() {
return DEFAULT_MINIMUM_TOKEN_MATCH;
}

@Override
public List<Token> parse(Set<File> files, boolean normalize) throws ParsingException {
try {
return cpgAdapter.adapt(files, normalize);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return List.of();
Comment on lines +70 to +71
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if returning empty is a good idea here. The user would not understand why there is noting parsed. Why not throw a parsing exception (see method signature)? Also: Can you catch the InterruptedException earlier? Directly in the adapter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I'll catch it in the CpgAdapter.
It seems like Sonar enforced that the thread be re-interrupted. I will gladly just throw a ParsingException. If something comes up in Sonar as a result, at least you will know where it came from.

}
}

@Override
public boolean requiresCoreNormalization() {
return false;
}

/**
* Resets the set of transformations to the obligatory transformations only.
*/
public void resetTransformations() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a lot of methods here for the state management of the adapter. Exposing that via the language interface leads you to have a lot of methods that just delegate to the adapter. We should discuss this, maybe there is a better option? Like, two constructors, one with a default adapter and one where you pass the pre-configured adapter? That would remove a lot of complexity here...

this.cpgAdapter.clearTransformations();
this.cpgAdapter.addTransformations(this.obligatoryTransformations());
this.cpgAdapter.addTransformations(this.standardTransformations());
}

/**
* Returns the set of transformations required to ensure that the tokenization works properly.
* @return the array of obligatory transformations
*/
private GraphTransformation[] obligatoryTransformations() {
return new GraphTransformation[] {wrapThenStatement, wrapElseStatement, wrapForStatement, wrapWhileStatement, wrapDoStatement};
}

/**
* Returns a set of transformations suggested for use.
* @return the array of recommended transformations
*/
public GraphTransformation[] standardTransformations() {
return new GraphTransformation[] {removeOptionalOfCall, removeOptionalGetCall, moveConstantToOnlyUsingClass, inlineSingleUseVariable,
removeLibraryRecord, removeEmptyRecord,};
}

/**
* Returns a set of all transformations.
* @return the array of all transformations
*/
public GraphTransformation[] allTransformations() {
return new GraphTransformation[] {ifWithNegatedConditionResolution, forStatementToWhileStatement, removeOptionalOfCall, removeOptionalGetCall,
removeGetterMethod, moveConstantToOnlyUsingClass, inlineSingleUseConstant, inlineSingleUseVariable, removeEmptyDeclarationStatement,
removeImplicitStandardConstructor, removeLibraryRecord, removeLibraryField, removeEmptyConstructor, removeUnsupportedConstructor,
removeUnsupportedMethod, removeEmptyRecord,};
}
Comment on lines +92 to +115
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also be provided by a separate class to separate the concerns, for example a dedicated utility class?


@Override
public String[] suffixes() {
return FILE_EXTENSIONS;
}

@Override
public boolean supportsNormalization() {
return true;
}
}
Loading