diff --git a/.gitignore b/.gitignore index 7121fdcef4..16388692ac 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ target/ .project .settings/ .classpath +.factorypath +website/node_modules *.class diff --git a/cli/pom.xml b/cli/pom.xml index f43d5867f3..c756a78977 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -30,6 +30,11 @@ de.jplag java + + de.jplag + java-cpg + ${revision} + de.jplag python-3 diff --git a/core/src/main/java/de/jplag/Submission.java b/core/src/main/java/de/jplag/Submission.java index b7181c673a..2d6d4c3c21 100644 --- a/core/src/main/java/de/jplag/Submission.java +++ b/core/src/main/java/de/jplag/Submission.java @@ -1,25 +1,12 @@ package de.jplag; -import static de.jplag.SubmissionState.CANNOT_PARSE; -import static de.jplag.SubmissionState.NOTHING_TO_PARSE; -import static de.jplag.SubmissionState.TOO_SMALL; -import static de.jplag.SubmissionState.UNPARSED; -import static de.jplag.SubmissionState.VALID; +import static de.jplag.SubmissionState.*; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,11 +58,24 @@ public Submission(String name, File submissionRootFile, boolean isNew, Collectio state = UNPARSED; } + private static File createErrorDirectory(String... subdirectoryNames) { + File subdirectory = Path.of(JPlagOptions.ERROR_FOLDER, subdirectoryNames).toFile(); + if (!subdirectory.exists()) { + subdirectory.mkdirs(); + } + return subdirectory; + } + @Override public int compareTo(Submission other) { return name.compareTo(other.name); } + @Override + public int hashCode() { + return Objects.hash(name); + } + @Override public boolean equals(Object obj) { if (obj == this) { @@ -88,8 +88,8 @@ public boolean equals(Object obj) { } @Override - public int hashCode() { - return Objects.hash(name); + public String toString() { + return name; } /** @@ -100,6 +100,14 @@ public JPlagComparison getBaseCodeComparison() { return baseCodeComparison; } + /** + * Sets the base code comparison. + * @param baseCodeComparison is submissions matches with the base code. + */ + public void setBaseCodeComparison(JPlagComparison baseCodeComparison) { + this.baseCodeComparison = baseCodeComparison; + } + /** * Provided all source code files. * @return a collection of files this submission consists of. @@ -153,6 +161,14 @@ public List getTokenList() { return tokenList; } + /** + * Sets the tokens that have been parsed from the files this submission consists of. + * @param tokenList is the list of these tokens. + */ + public void setTokenList(List tokenList) { + this.tokenList = Collections.unmodifiableList(new ArrayList<>(tokenList)); + } + /** * @return true if a comparison between the submission and the base code is available. Does not imply if there are * matches to the base code. @@ -185,22 +201,6 @@ public boolean isNew() { return isNew; } - /** - * Sets the base code comparison. - * @param baseCodeComparison is submissions matches with the base code. - */ - public void setBaseCodeComparison(JPlagComparison baseCodeComparison) { - this.baseCodeComparison = baseCodeComparison; - } - - /** - * Sets the tokens that have been parsed from the files this submission consists of. - * @param tokenList is the list of these tokens. - */ - public void setTokenList(List tokenList) { - this.tokenList = Collections.unmodifiableList(new ArrayList<>(tokenList)); - } - /** * String representation of the code files contained in this submission, annotated with all tokens. * @return the annotated code as string. @@ -209,11 +209,6 @@ public String getTokenAnnotatedSourceCode() { return TokenPrinter.printTokens(tokenList, submissionRootFile); } - @Override - public String toString() { - return name; - } - /** * This method is used to copy files that can not be parsed to a special folder. */ @@ -229,14 +224,6 @@ private void copySubmission() { } } - private static File createErrorDirectory(String... subdirectoryNames) { - File subdirectory = Path.of(JPlagOptions.ERROR_FOLDER, subdirectoryNames).toFile(); - if (!subdirectory.exists()) { - subdirectory.mkdirs(); - } - return subdirectory; - } - /** * Parse files of the submission. * @param debugParser specifies if the submission should be copied upon parsing errors. @@ -348,7 +335,9 @@ public Map getTokenCountPerFile() { fileTokenCount.put(file, 0); } for (Token token : this.tokenList) { - fileTokenCount.put(token.getFile(), fileTokenCount.get(token.getFile()) + 1); + File tokenfile = token.getFile(); + fileTokenCount.computeIfAbsent(tokenfile, k -> 0); + fileTokenCount.put(tokenfile, fileTokenCount.get(tokenfile) + 1); } } return fileTokenCount; diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml index d54f8c6658..c42ff94198 100644 --- a/coverage-report/pom.xml +++ b/coverage-report/pom.xml @@ -43,6 +43,10 @@ de.jplag java + + de.jplag + java-cpg + de.jplag python-3 diff --git a/endtoend-testing/src/test/java/de/jplag/endtoend/architecture/PomVersionTest.java b/endtoend-testing/src/test/java/de/jplag/endtoend/architecture/PomVersionTest.java index 9985a4acbc..995b8798bc 100644 --- a/endtoend-testing/src/test/java/de/jplag/endtoend/architecture/PomVersionTest.java +++ b/endtoend-testing/src/test/java/de/jplag/endtoend/architecture/PomVersionTest.java @@ -17,6 +17,7 @@ import org.jdom2.xpath.XPathBuilder; import org.jdom2.xpath.XPathExpression; import org.jdom2.xpath.XPathFactory; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -50,6 +51,34 @@ class PomVersionTest { private static final File projectRoot = new File(".."); private static final File projectRootPom = new File(projectRoot, POM_XML_NAME); + private static final List ALLOWED_EXTERNAL_DEPENDENCIES = List.of("de.fraunhofer.aisec:cpg-core", "de.fraunhofer.aisec:cpg-language-java", + "de.fraunhofer.aisec:cpg-analysis", "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "org.jetbrains.kotlin:kotlin-test"); + + private static final List ALLOWED_EXTERNAL_PLUGINS = List.of("org.jetbrains.kotlin:kotlin-maven-plugin"); + + static List getAllPomFiles() { + List collector = new ArrayList<>(); + getAllPomFiles(projectRoot, collector); + return collector; + } + + static List getAllChildPomFiles() { + List poms = getAllPomFiles(); + poms.remove(projectRootPom); + return poms; + } + + static void getAllPomFiles(File scanDir, List collector) { + for (File file : scanDir.listFiles()) { + if (file.isFile() && file.getName().equals(POM_XML_NAME)) { + collector.add(file); + } + if (file.isDirectory()) { + getAllPomFiles(file, collector); + } + } + } + @ParameterizedTest @MethodSource("getAllChildPomFiles") @DisplayName("Ensure all child poms use " + REVISION_REFERENCE + " to reference parent") @@ -130,34 +159,31 @@ private List scanXpath(Document doc, String xpathExpression) { return expression.evaluate(doc); } - private void failForVersionTag(Element versionTag) { + private void failForVersionTag(@NotNull Element versionTag) { Namespace ns = versionTag.getNamespace(); Element definition = versionTag.getParentElement(); - String definitionString = "{groupId: " + definition.getChildText("groupId", ns) + ", artifactId:" + definition.getChildText("artifactId", ns) - + ", version:" + versionTag.getText() + "}"; + String groupId = definition.getChildText("groupId", ns); + String artifactId = definition.getChildText("artifactId", ns); + // Handle inherited groupId from parent + if (groupId == null) { + groupId = "de.jplag"; + } + String key = groupId + ":" + artifactId; + // Allow external dependencies and plugins + if (ALLOWED_EXTERNAL_DEPENDENCIES.contains(key) || ALLOWED_EXTERNAL_PLUGINS.contains(key)) { + return; + } + // Allow property-based external dependencies + if (groupId.startsWith("${") && (ALLOWED_EXTERNAL_DEPENDENCIES.stream().anyMatch(dep -> dep.endsWith(":" + artifactId)) + || ALLOWED_EXTERNAL_PLUGINS.stream().anyMatch(plugin -> plugin.endsWith(":" + artifactId)))) { + return; + } + // Allow internal JPlag dependencies with ${revision} + if ("de.jplag".equals(groupId) && "${revision}".equals(versionTag.getText())) { + return; + } + String definitionString = "{groupId: " + groupId + ", artifactId:" + artifactId + ", version:" + versionTag.getText() + "}"; Assertions.fail("Invalid version tag found for: " + definitionString); } - static List getAllPomFiles() { - List collector = new ArrayList<>(); - getAllPomFiles(projectRoot, collector); - return collector; - } - - static List getAllChildPomFiles() { - List poms = getAllPomFiles(); - poms.remove(projectRootPom); - return poms; - } - - static void getAllPomFiles(File scanDir, List collector) { - for (File file : scanDir.listFiles()) { - if (file.isFile() && file.getName().equals(POM_XML_NAME)) { - collector.add(file); - } - if (file.isDirectory()) { - getAllPomFiles(file, collector); - } - } - } } diff --git a/languages/java-cpg/pom.xml b/languages/java-cpg/pom.xml new file mode 100644 index 0000000000..97b7f331b0 --- /dev/null +++ b/languages/java-cpg/pom.xml @@ -0,0 +1,111 @@ + + + 4.0.0 + + de.jplag + languages + ${revision} + + + java-cpg + + + 8.3.0 + de.fraunhofer.aisec + 2.3.0 + + + + + + ${cpg.groupId} + cpg-core + ${cpg.version} + + + + org.apache.logging.log4j + log4j-slf4j2-impl + + + + + ${cpg.groupId} + cpg-language-java + ${cpg.version} + + + ${cpg.groupId} + cpg-analysis + ${cpg.version} + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + de.jplag + jplag + test + + + de.jplag + java + test + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + 1.8 + + -Xcontext-receivers + + + + + compile + + compile + + process-sources + + + test-compile + + test-compile + + test-compile + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + src/main/java + + **/*.java + + + -Xdoclint:none + + false + + + + + + diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/CpgAdapter.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/CpgAdapter.java new file mode 100644 index 0000000000..45123ed95b --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/CpgAdapter.java @@ -0,0 +1,159 @@ +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 org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.ConfigurationException; +import de.fraunhofer.aisec.cpg.InferenceConfiguration; +import de.fraunhofer.aisec.cpg.TranslationConfiguration; +import de.fraunhofer.aisec.cpg.TranslationManager; +import de.fraunhofer.aisec.cpg.TranslationResult; +import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage; +import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass; +import de.fraunhofer.aisec.cpg.passes.DynamicInvokeResolver; +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass; +import de.fraunhofer.aisec.cpg.passes.FilenameMapper; +import de.fraunhofer.aisec.cpg.passes.ImportResolver; +import de.fraunhofer.aisec.cpg.passes.JavaExternalTypeHierarchyResolver; +import de.fraunhofer.aisec.cpg.passes.JavaImportResolver; +import de.fraunhofer.aisec.cpg.passes.Pass; +import de.fraunhofer.aisec.cpg.passes.ProgramDependenceGraphPass; +import de.fraunhofer.aisec.cpg.passes.ReplaceCallCastPass; +import de.fraunhofer.aisec.cpg.passes.SymbolResolver; +import de.fraunhofer.aisec.cpg.passes.TypeHierarchyResolver; +import de.fraunhofer.aisec.cpg.passes.TypeResolver; +import de.jplag.ParsingException; +import de.jplag.Token; +import de.jplag.java_cpg.ai.AiPass; +import de.jplag.java_cpg.passes.AstTransformationPass; +import de.jplag.java_cpg.passes.CpgTransformationPass; +import de.jplag.java_cpg.passes.DfgSortPass; +import de.jplag.java_cpg.passes.FixAstPass; +import de.jplag.java_cpg.passes.PrepareTransformationPass; +import de.jplag.java_cpg.passes.TokenizationPass; +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 final boolean removeDeadCode; + private final boolean detectDeadCode; + private List tokenList; + private boolean reorderingEnabled = true; + + /** + * Constructs a new CpgAdapter. + * @param transformations a list of {@link GraphTransformation}s + * @param removeDeadCode whether dead code should be removed + * @param detectDeadCode whether dead code should be detected + * @param reorder whether statements may be reordered + */ + public CpgAdapter(boolean removeDeadCode, boolean detectDeadCode, boolean reorder, GraphTransformation... transformations) { + addTransformations(transformations); + this.removeDeadCode = removeDeadCode; + this.detectDeadCode = detectDeadCode; + setReorderingEnabled(reorder); + } + + List adapt(@NotNull Set files, boolean normalize) throws ParsingException, InterruptedException { + assert !files.isEmpty(); + tokenList = null; + if (!normalize) { + clearTransformations(); + addTransformations(JavaCpgLanguage.minimalTransformations()); + setReorderingEnabled(false); + } + // TokenizationPass sets tokenList + translate(files); + return tokenList; + } + + /** + * Adds a transformation at the end of its respective ATransformationPass. + * @param transformation a {@link GraphTransformation} + */ + public void addTransformation(@NotNull 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(); + } + + @NotNull + private > KClass getKClass(Class javaPassClass) { + return JvmClassMappingKt.getKotlinClass(javaPassClass); + } + + /** + * Sets reorderingEnabled. If true, statements may be reordered. + * @param enabled value for reorderingEnabled. + */ + public void setReorderingEnabled(boolean enabled) { + this.reorderingEnabled = enabled; + } + + TranslationResult translate(@NotNull Set files) throws ParsingException, InterruptedException { + InferenceConfiguration inferenceConfiguration = InferenceConfiguration.builder().inferRecords(true).inferDfgForUnresolvedCalls(true).build(); + TranslationResult translationResult; + TokenizationPass.Companion.setCallback(CpgAdapter.this::setTokenList); + AiPass.AiPassCompanion.setRemoveDeadCode(CpgAdapter.this.removeDeadCode); + try { + TranslationConfiguration.Builder configBuilder = new TranslationConfiguration.Builder().inferenceConfiguration(inferenceConfiguration) + .sourceLocations(files.toArray(new File[] {})).registerLanguage(new JavaLanguage()); + + List>> passClasses = new ArrayList<>(List.of(TypeResolver.class, TypeHierarchyResolver.class, + JavaExternalTypeHierarchyResolver.class, JavaImportResolver.class, ImportResolver.class, SymbolResolver.class, + PrepareTransformationPass.class, FixAstPass.class, DynamicInvokeResolver.class, FilenameMapper.class, ReplaceCallCastPass.class, + AstTransformationPass.class, EvaluationOrderGraphPass.class, ControlDependenceGraphPass.class, ProgramDependenceGraphPass.class, + DfgSortPass.class, CpgTransformationPass.class, AiPass.class, TokenizationPass.class)); + + if (!reorderingEnabled) { + passClasses.remove(DfgSortPass.class); + } + if (!detectDeadCode && !removeDeadCode) { + passClasses.remove(AiPass.class); + } + for (Class> passClass : passClasses) { + configBuilder.registerPass(getKClass(passClass)); + } + translationResult = TranslationManager.builder().config(configBuilder.build()).build().analyze().get(); + } catch (ConfigurationException | ExecutionException e) { + throw new ParsingException(List.copyOf(files).getFirst(), e); + } + return translationResult; + } + + private void setTokenList(List tokenList) { + this.tokenList = tokenList; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/JavaCpgLanguage.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/JavaCpgLanguage.java new file mode 100644 index 0000000000..bec97faf3c --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/JavaCpgLanguage.java @@ -0,0 +1,219 @@ +package de.jplag.java_cpg; + +import static de.jplag.java_cpg.transformation.TransformationRepository.forStatementToWhileStatement; +import static de.jplag.java_cpg.transformation.TransformationRepository.ifWithNegatedConditionResolution; +import static de.jplag.java_cpg.transformation.TransformationRepository.inlineSingleUseConstant; +import static de.jplag.java_cpg.transformation.TransformationRepository.inlineSingleUseVariable; +import static de.jplag.java_cpg.transformation.TransformationRepository.moveConstantToOnlyUsingClass; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeEmptyConstructor; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeEmptyDeclarationStatement; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeEmptyRecord; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeGetterMethod; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeImplicitStandardConstructor; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeLibraryField; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeLibraryRecord; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeOptionalGetCall; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeOptionalOfCall; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeUnsupportedConstructor; +import static de.jplag.java_cpg.transformation.TransformationRepository.removeUnsupportedMethod; +import static de.jplag.java_cpg.transformation.TransformationRepository.wrapDoStatement; +import static de.jplag.java_cpg.transformation.TransformationRepository.wrapElseStatement; +import static de.jplag.java_cpg.transformation.TransformationRepository.wrapForStatement; +import static de.jplag.java_cpg.transformation.TransformationRepository.wrapThenStatement; +import static de.jplag.java_cpg.transformation.TransformationRepository.wrapWhileStatement; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.kohsuke.MetaInfServices; + +import de.jplag.Language; +import de.jplag.ParsingException; +import de.jplag.Token; +import de.jplag.java_cpg.ai.ArrayAiType; +import de.jplag.java_cpg.ai.CharAiType; +import de.jplag.java_cpg.ai.FloatAiType; +import de.jplag.java_cpg.ai.IntAiType; +import de.jplag.java_cpg.ai.StringAiType; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.transformation.GraphTransformation; + +/** + * This class represents the front 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; + 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(true, true, true, allTransformations()); + } + + /** + * Creates a new {@link JavaCpgLanguage}. + * @param removeDeadCode whether dead code should be removed + * @param detectDeadCode whether dead code should be detected + * @param reorder whether statements may be reordered + */ + public JavaCpgLanguage(boolean removeDeadCode, boolean detectDeadCode, boolean reorder) { + this.cpgAdapter = new CpgAdapter(removeDeadCode, detectDeadCode, reorder, allTransformations()); + } + + /** + * Creates a new {@link JavaCpgLanguage}. + * @param removeDeadCode whether dead code should be removed + * @param detectDeadCode whether dead code should be detected + * @param reorder whether statements may be reordered + * @param transformations the code graph transformations to apply + */ + public JavaCpgLanguage(boolean removeDeadCode, boolean detectDeadCode, boolean reorder, GraphTransformation[] transformations) { + this.cpgAdapter = new CpgAdapter(removeDeadCode, detectDeadCode, reorder, transformations); + } + + /** + * Creates a new {@link JavaCpgLanguage}. + * @param removeDeadCode whether dead code should be removed + * @param detectDeadCode whether dead code should be detected + * @param reorder whether statements may be reordered + * @param transformations the code graph transformations to apply + * @param intAiType the AI type to use for integer values + * @param floatAiType the AI type to use for float values + * @param stringAiType the AI type to use for string values + * @param charAiType the AI type to use for char values + * @param arrayAiType the AI type to use for array values + */ + public JavaCpgLanguage(boolean removeDeadCode, boolean detectDeadCode, boolean reorder, GraphTransformation[] transformations, + IntAiType intAiType, FloatAiType floatAiType, StringAiType stringAiType, CharAiType charAiType, ArrayAiType arrayAiType) { + this(removeDeadCode, detectDeadCode, reorder, transformations); + Value.setUsedIntAiType(intAiType); + Value.setUsedFloatAiType(floatAiType); + Value.setUsedStringAiType(stringAiType); + Value.setUsedCharAiType(charAiType); + Value.setUsedArrayAiType(arrayAiType); + } + + /** + * @return array with only the minimal set of transformations needed for a standard tokenization + */ + @NotNull + public static GraphTransformation[] minimalTransformations() { + return new GraphTransformation[] {removeImplicitStandardConstructor, removeLibraryRecord, removeLibraryField,}; + } + + /** + * @return array with only the set of transformations needed for dead code removal + */ + @NotNull + public static GraphTransformation[] deadCodeRemovalTransformations() { + return new GraphTransformation[] {removeEmptyDeclarationStatement, removeImplicitStandardConstructor, removeLibraryRecord, removeLibraryField, + removeEmptyConstructor, removeUnsupportedConstructor, removeUnsupportedMethod, removeEmptyRecord}; + } + + /** + * 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); + } + + /** + * Resets the set of transformations to the obligatory transformations only. + */ + public void resetTransformations() { + 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,}; + } + + @Override + public List fileExtensions() { + return Arrays.asList(FILE_EXTENSIONS); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public int minimumTokenMatch() { + return DEFAULT_MINIMUM_TOKEN_MATCH; + } + + @Override + public List parse(Set files, boolean normalize) throws ParsingException { + try { + return cpgAdapter.adapt(files, normalize); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return List.of(); + } + } + + @Override + public boolean expectsSubmissionOrder() { // FixMe: parallelimus seems to only sometimes work correctly + return true; + } + + @Override + public boolean supportsNormalization() { + return true; + } + + @Override + public boolean requiresCoreNormalization() { + return false; + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AbstractInterpretation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AbstractInterpretation.java new file mode 100644 index 0000000000..e6865da555 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AbstractInterpretation.java @@ -0,0 +1,1405 @@ +package de.jplag.java_cpg.ai; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.checkerframework.dataflow.qual.Impure; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import de.fraunhofer.aisec.cpg.graph.Name; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration; +import de.fraunhofer.aisec.cpg.graph.declarations.EnumConstantDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration; +import de.fraunhofer.aisec.cpg.graph.scopes.TryScope; +import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement; +import de.fraunhofer.aisec.cpg.graph.statements.BreakStatement; +import de.fraunhofer.aisec.cpg.graph.statements.CaseStatement; +import de.fraunhofer.aisec.cpg.graph.statements.CatchClause; +import de.fraunhofer.aisec.cpg.graph.statements.ContinueStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DefaultStatement; +import de.fraunhofer.aisec.cpg.graph.statements.EmptyStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement; +import de.fraunhofer.aisec.cpg.graph.statements.Statement; +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement; +import de.fraunhofer.aisec.cpg.graph.statements.TryStatement; +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConditionalExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ExpressionList; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.LambdaExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ShortCircuitOperator; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.SubscriptExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator; +import de.fraunhofer.aisec.cpg.graph.types.HasType; +import de.fraunhofer.aisec.cpg.graph.types.PointerType; +import de.fraunhofer.aisec.cpg.graph.types.Type; +import de.jplag.java_cpg.ai.variables.Variable; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.VariableStore; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IJavaObject; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.NullValue; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.arrays.IJavaArray; +import de.jplag.java_cpg.ai.variables.values.arrays.JavaArray; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.transformation.operations.DummyNeighbor; +import de.jplag.java_cpg.transformation.operations.TransformationUtil; + +/** + * Abstract Interpretation engine for Java programs. This class is the interface between the CPG Graph and the Abstract + * Interpretation Data Structures. + * @author ujiqk + * @version 1.0 + */ +public class AbstractInterpretation { + + private final List returnStorage; + /** + * Helper to detect recursive method calls. + */ + private final List lastVisitedMethod; + /** + * Recorder for visited lines to detect dead methods/classes later. + */ + private final VisitedLinesRecorder visitedLinesRecorder; + private final boolean removeDeadCode; + /** + * Helper: if we are recording changes for while loops. + */ + @NotNull + private final RecordingChanges recordingChanges; + /** + * Helper stack to work around cpg limitations. + */ + private List lastVisitedLoopOrIf; + /** + * Stack for EOG traversal. + */ + @NotNull + private ArrayList nodeStack; + /** + * Stack for values during EOG traversal. + */ + @NotNull + private ArrayList valueStack; + /** + * The scoped variable store for the current scope. + */ + @NotNull + private VariableStore variables; + /** + * The object this AI engine is currently interpreting. + */ + private IJavaObject object; + /** + * Helper counter for nested if-else statements because cpg does not provide enough information. + */ + private int ifElseCounter = 0; + /** + * Helper to detect if we are currently in a constructor. ConstructExpressions behave differently inside constructors. + */ + private boolean inConstructor = false; + + /** + * @param visitedLinesRecorder Recorder for visited lines to detect dead methods/classes later. + * @param removeDeadCode Whether dead code should be removed after the interpretation. + */ + public AbstractInterpretation(VisitedLinesRecorder visitedLinesRecorder, boolean removeDeadCode) { + this(visitedLinesRecorder, removeDeadCode, new RecordingChanges(false)); + } + + private AbstractInterpretation(VisitedLinesRecorder visitedLinesRecorder, boolean removeDeadCode, @NotNull RecordingChanges recordingChanges) { + variables = new VariableStore(); + nodeStack = new ArrayList<>(); + valueStack = new ArrayList<>(); + lastVisitedLoopOrIf = new ArrayList<>(); + returnStorage = new ArrayList<>(); + lastVisitedMethod = new ArrayList<>(); + this.visitedLinesRecorder = visitedLinesRecorder; + this.removeDeadCode = removeDeadCode; + this.recordingChanges = recordingChanges; + } + + private static JavaObject createNewObject(@NotNull ConstructExpression ce) { + JavaObject newObject; + String name = ce.getType().getName().toString(); + name = name.split("<")[0]; // remove generics + switch (name) { + case "java.util.HashMap", "java.util.Map" -> newObject = new de.jplag.java_cpg.ai.variables.objects.HashMap(); + case "java.util.Scanner" -> newObject = new de.jplag.java_cpg.ai.variables.objects.Scanner(); + case "java.util.ArrayList", "java.util.List", "java.util.Vector", "java.util.LinkedList", "java.util.PriorityQueue" -> newObject = new JavaArray(); + default -> newObject = new JavaObject(); + } + return newObject; + } + + /** + * @param object the object this AI engine is currently interpreting. + */ + public void setRelatedObject(@NotNull IJavaObject object) { + this.object = object; + } + + /** + * Starts the abstract interpretation by running the main method. + * @param tud TranslationUnitDeclaration graph node representing the whole program. + * @throws IllegalStateException if no main method is found. + */ + public void runMain(@NotNull TranslationUnitDeclaration tud) { + RecordDeclaration mainClas; + if (tud.getDeclarations().stream().map(Declaration::getClass).filter(x -> x.equals(NamespaceDeclaration.class)).count() == 1) { + // Code in a package + mainClas = tud.getDeclarations().stream().filter(NamespaceDeclaration.class::isInstance).map(NamespaceDeclaration.class::cast).findFirst() + .map(ns -> (RecordDeclaration) ns.getDeclarations().getFirst()) + .orElseThrow(() -> new IllegalStateException("No NamespaceDeclaration found in translation unit")); + } else if (tud.getDeclarations().stream().map(Declaration::getClass).anyMatch(x -> x.equals(RecordDeclaration.class))) { + // Code without a package + mainClas = tud.getDeclarations().stream().filter(RecordDeclaration.class::isInstance).map(RecordDeclaration.class::cast) + .filter(rd -> rd.getMethods().stream().anyMatch(m -> m.getName().getLocalName().equals("main") && m.isStatic() && m.hasBody())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No RecordDeclaration with public static main method found in translation unit")); + } else { + throw new IllegalStateException("Unexpected number of classes or namespaces in translation unit generated by cpg"); + } + JavaObject mainClassVar = new JavaObject(); + setupClass(mainClas, mainClassVar); + assert mainClas.getMethods().stream().map(MethodDeclaration::getName).filter(x -> x.getLocalName().equals("main")).count() == 1; + for (MethodDeclaration md : mainClas.getMethods()) { + if (md.getName().getLocalName().equals("main")) { + visitedLinesRecorder.recordFirstLineVisited(md); + // Run main method + List eog = md.getNextEOG(); + assert eog.size() == 1; + variables.newScope(); + variables.addVariable(new Variable(new VariableName("args"), Value.getNewArayValue(de.jplag.java_cpg.ai.variables.Type.STRING))); + graphWalker(eog.getFirst()); + variables.removeScope(); + } + + } + // ignore include declaration for now + } + + /** + * Sets up the abstract interpretation for the given class and runs its constructor. + * @param rd RecordDeclaration node representing the class. + * @param objectInstance the object instance that should represent the class. + * @param constructorArgs the arguments for the constructor. + */ + private void runClass(@NotNull RecordDeclaration rd, @NotNull IJavaObject objectInstance, List constructorArgs, + @NotNull ConstructorDeclaration constructor) { + setupClass(rd, objectInstance); + visitedLinesRecorder.recordFirstLineVisited(constructor); + // Run constructor method + this.inConstructor = true; + List eog = constructor.getNextEOG(); + if (eog.size() == 1) { + variables.newScope(); + assert constructor.getParameters().size() == constructorArgs.size(); + for (int i = 0; i < constructorArgs.size(); i++) { + variables + .addVariable(new Variable(new VariableName(constructor.getParameters().get(i).getName().toString()), constructorArgs.get(i))); + } + graphWalker(eog.getFirst()); + variables.removeScope(); + } else if (eog.isEmpty()) { + // empty constructor -> return + } else { + throw new IllegalStateException("Unexpected value: " + eog.size()); + } + this.inConstructor = false; + } + + /** + * Sets up the abstract interpretation for the given class. Adds fields and methods to the object instance. + * @param rd RecordDeclaration node representing the class. + * @param objectInstance the object instance that should represent the class. + */ + @Impure + private void setupClass(@NotNull RecordDeclaration rd, @NotNull IJavaObject objectInstance) { + objectInstance.setAbstractInterpretation(this); + variables.addVariable(new Variable(new VariableName(rd.getName().toString()), objectInstance)); + variables.setThisName(new VariableName(rd.getName().toString())); + variables.addVariable( + new Variable(de.jplag.java_cpg.ai.variables.objects.System.getName(), new de.jplag.java_cpg.ai.variables.objects.System())); + variables.addVariable(new Variable(de.jplag.java_cpg.ai.variables.objects.Math.getName(), new de.jplag.java_cpg.ai.variables.objects.Math())); + variables.addVariable( + new Variable(de.jplag.java_cpg.ai.variables.objects.Integer.getName(), new de.jplag.java_cpg.ai.variables.objects.Integer())); + variables.addVariable( + new Variable(de.jplag.java_cpg.ai.variables.objects.Boolean.getName(), new de.jplag.java_cpg.ai.variables.objects.Boolean())); + variables.addVariable( + new Variable(de.jplag.java_cpg.ai.variables.objects.Double.getName(), new de.jplag.java_cpg.ai.variables.objects.Double())); + variables.addVariable( + new Variable(de.jplag.java_cpg.ai.variables.objects.String.getName(), new de.jplag.java_cpg.ai.variables.objects.String())); + variables.addVariable( + new Variable(de.jplag.java_cpg.ai.variables.objects.Arrays.getName(), new de.jplag.java_cpg.ai.variables.objects.Arrays())); + variables.addVariable( + new Variable(de.jplag.java_cpg.ai.variables.objects.Pattern.getName(), new de.jplag.java_cpg.ai.variables.objects.Pattern())); + variables.addVariable( + new Variable(de.jplag.java_cpg.ai.variables.objects.Random.getName(), new de.jplag.java_cpg.ai.variables.objects.Random())); + this.object = objectInstance; + visitedLinesRecorder.recordFirstLineVisited(rd); + setupFieldDeclarations(rd, objectInstance); + RecordDeclaration currentClass = rd; + while (true) { + Set superClass = currentClass.getSuperTypeDeclarations(); + if (superClass.isEmpty()) { + break; + } + assert superClass.size() == 1; + RecordDeclaration superRd = superClass.stream().findFirst().orElseThrow(); + setupFieldDeclarations(superRd, objectInstance); + currentClass = superRd; + } + } + + private void setupFieldDeclarations(@NotNull RecordDeclaration rd, @NotNull IJavaObject objectInstance) { + for (FieldDeclaration fd : rd.getFields()) { + visitedLinesRecorder.recordLinesVisited(fd); + Type type = fd.getType(); + Name name = fd.getName(); + if (fd.getInitializer() == null) { // no initial value + de.jplag.java_cpg.ai.variables.Type aiType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(type); + Variable newVar; + if (aiType == de.jplag.java_cpg.ai.variables.Type.ARRAY || aiType == de.jplag.java_cpg.ai.variables.Type.LIST) { + de.jplag.java_cpg.ai.variables.Type innerType = de.jplag.java_cpg.ai.variables.Type + .fromCpgType(((PointerType) type).getElementType()); + IJavaArray arrayValue = Value.getNewArayValue(innerType); + newVar = new Variable(new VariableName(name.toString()), arrayValue); + } else { + newVar = new Variable(new VariableName(name.toString()), aiType); + } + newVar.setInitialValue(); + objectInstance.setField(newVar); + } else if (!(fd.getInitializer() instanceof ProblemExpression)) { + if (fd.getInitializer() instanceof UnaryOperator unop) { + assert Objects.equals(unop.getOperatorCode(), "-"); + IValue value = graphWalker(fd.getNextEOG().getFirst()); + assert value != null; + objectInstance.setField(new Variable(new VariableName(name.toString()), value)); + } else { + IValue result = graphWalker(fd.getNextEOG().getFirst()); + assert result != null; + objectInstance.setField(new Variable(new VariableName(name.toString()), result)); + } + } + } + } + + /** + * Graph walker for EOG traversal. Walks the EOG starting from the given node until the current block ends. If present, + * returns the value produced by the return statement. + * @param node the starting graph node. + * @return the value resulting from the traversal, or null if no value is produced. + */ + @Nullable + private IValue graphWalker(@NotNull Node node) { + if (node instanceof FieldDeclaration || node instanceof RecordDeclaration) { + IValue value = valueStack.getLast(); + valueStack.removeLast(); + nodeStack.removeLast(); + return value; // return so that the class setup method can use the graph walker + } + List nextEOG = node.getNextEOG(); + Node nextNode; + visitedLinesRecorder.recordLinesVisited(node); + switch (node) { + case VariableDeclaration vd -> { + nodeStack.add(vd); + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case Literal l -> { // adds its value to the value stack + nodeStack.add(l); + valueStack.add(Value.valueFactory(l.getValue())); + if (nextEOG.isEmpty()) { // when used as a field initializer + IValue value = valueStack.getLast(); + valueStack.removeLast(); + return value; + } + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case MemberExpression me -> { + walkMemberExpression(me); + if (nextEOG.isEmpty()) { // when used as a field initializer + IValue value = valueStack.getLast(); + valueStack.removeLast(); + return value; + } + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case Reference ref -> { // adds its value to the value stack + if (ref.getName().getLocalName().equals("this")) { + valueStack.add(this.object); + } else { + Variable variable = variables.getVariable(new VariableName(ref.getName().toString())); + if (variable != null) { + valueStack.add(variables.getVariable(new VariableName(ref.getName().toString())).getValue()); + } else if (object.accessField(ref.getName().toString()) != null) { // sometimes cpg does not insert "this". + IValue value = object.accessField(ref.getName().toString()); + if (value.getType() == de.jplag.java_cpg.ai.variables.Type.VOID) { // value isn't known + value = Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.fromCpgType(ref.getType())); + } + valueStack.add(value); + } else { // unknown reference + assert false; + } + } + nodeStack.add(ref); + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case SubscriptExpression se -> nextNode = walkSubscriptExpression(se); + case MemberCallExpression mce -> { // adds its value to the value stack + walkMemberCallExpression(mce); + if (nextEOG.isEmpty()) { // when used as a field initializer + IValue value = valueStack.getLast(); + valueStack.removeLast(); + return value; + } + if (nextEOG.size() == 2 && nextEOG.getLast() instanceof ShortCircuitOperator) { + nextNode = nextEOG.getFirst(); + } else { + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + } + case DeclarationStatement ds -> { + for (int i = ds.getDeclarations().size() - 1; i >= 0; i--) { + if (((VariableDeclaration) ds.getDeclarations().get(i)).getInitializer() == null) { + Variable newVar = new Variable(new VariableName((ds.getDeclarations().get(i)).getName().toString()), + de.jplag.java_cpg.ai.variables.Type.fromCpgType(((VariableDeclaration) ds.getDeclarations().get(i)).getType())); + newVar.setInitialValue(); + variables.addVariable(newVar); + nodeStack.removeLast(); + } else { + assert !valueStack.isEmpty(); + Variable variable = new Variable((ds.getDeclarations().get(i)).getName().toString(), valueStack.getLast()); + variables.addVariable(variable); + nodeStack.removeLast(); + nodeStack.removeLast(); + valueStack.removeLast(); + if (nextEOG.getFirst() instanceof ForEachStatement) { + valueStack.add(variable.getValue()); + } + } + } + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case AssignExpression ae -> nextNode = walkAssignExpression(ae); + case ShortCircuitOperator scop -> nextNode = walkShortCircuitOperator(scop); + case BinaryOperator bop -> { + assert valueStack.size() >= 2 && !nodeStack.isEmpty(); + String operator = bop.getOperatorCode(); + assert operator != null; + IValue result = valueStack.get(valueStack.size() - 2).binaryOperation(operator, valueStack.getLast()); + assert nodeStack.size() >= 2; + nodeStack.removeLast(); + nodeStack.removeLast(); + nodeStack.add(bop); + valueStack.removeLast(); + valueStack.removeLast(); + valueStack.add(result); + assert nextEOG.size() == 1 || (nextEOG.size() == 2 && nextEOG.getLast() instanceof ShortCircuitOperator); + nextNode = nextEOG.getFirst(); + } + case UnaryOperator uop -> { + assert !valueStack.isEmpty() && !nodeStack.isEmpty(); + String operator = uop.getOperatorCode(); + assert operator != null; + IValue result = valueStack.getLast().unaryOperation(operator); + nodeStack.removeLast(); + nodeStack.add(uop); + valueStack.removeLast(); + valueStack.add(result); + assert nextEOG.size() == 1 || (nextEOG.size() == 2 && nextEOG.getLast() instanceof ShortCircuitOperator); + nextNode = nextEOG.getFirst(); + } + case IfStatement ifStmt -> { + nextNode = walkIfStatement(ifStmt); + if (nextNode == null) { + return null; + } + } + case SwitchStatement sw -> { + nextNode = walkSwitchStatement(sw); + if (nextNode == null) { + return new VoidValue(); // ToDo: function return (CropArea:31) + } + } + case CaseStatement _ -> { + IValue caseValue = valueStack.getLast(); + IValue switchValue = valueStack.getLast(); + if (!Objects.equals(caseValue, switchValue)) { + return null; + } + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case DefaultStatement _ -> { + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case Block b -> { + if (b.getScope() instanceof TryScope) { + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } else { + // assert block is exited + if (nextEOG.size() == 1) { // end of if + nodeStack.add(nextEOG.getFirst()); + return null; + } else if (nextEOG.isEmpty()) { // at the end of a while loop or after throw statement + nodeStack.add(null); + return null; + } else { + assert false; + return null; + } + } + } + case ReturnStatement _ -> { + return walkReturnStatement(); + } + case ConstructExpression ce -> { + // inside Constructors, no NewExpression nodes come after ConstructExpression nodes + if (inConstructor && !(nextEOG.getFirst() instanceof NewExpression)) { + ConstructorDeclaration constructor = ce.getConstructor(); + assert constructor != null; + List eog = constructor.getNextEOG(); + if (!(eog.isEmpty())) { // Constructor has a body + List arguments = new ArrayList<>(); + if (!ce.getArguments().isEmpty()) { + int size = ce.getArguments().size(); + for (int i = 0; i < size; i++) { + arguments.add(valueStack.getLast()); + valueStack.removeLast(); + nodeStack.removeLast(); + } + } + Collections.reverse(arguments); + for (int i = 0; i < constructor.getParameters().size(); i++) { + variables.addVariable( + new Variable(new VariableName(constructor.getParameters().get(i).getName().toString()), arguments.get(i))); + } + graphWalker(eog.getFirst()); + } + } else { + nodeStack.add(ce); + } + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case NewExpression ne -> { + walkNewExpression(ne); + if (nextEOG.isEmpty() || nextEOG.getFirst() instanceof DummyNeighbor) { // when used as a field initializer + IValue value = valueStack.getLast(); + valueStack.removeLast(); + return value; + } + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case WhileStatement ws -> { + nextNode = walkWhileStatement(ws); + if (nextNode == null) { + return null; + } + } + case ForStatement ws -> { + nextNode = walkForStatement(ws); + if (nextNode == null) { + return null; + } + } + case ForEachStatement fes -> { + nextNode = walkForEachStatement(fes); + if (nextNode == null) { + return null; + } + } + case InitializerListExpression ile -> { + assert !ile.getInitializers().isEmpty(); + assert valueStack.size() >= ile.getInitializers().size(); + assert nodeStack.size() >= ile.getInitializers().size(); + List arguments = new ArrayList<>(); + for (int i = 0; i < ile.getInitializers().size(); i++) { + nodeStack.removeLast(); + arguments.add(valueStack.getLast()); + valueStack.removeLast(); + } + assert arguments.stream().map(IValue::getType).distinct().count() == 1; + IJavaArray list = new JavaArray(arguments); + if (nextEOG.isEmpty()) { // when used as a field initializer + return list; + } + valueStack.add(list); + nodeStack.add(ile); + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case NewArrayExpression nae -> { + walkNewArrayExpression(nae); + if (nextEOG.isEmpty() || nextEOG.getFirst() instanceof RecordDeclaration) { // when used as a field initializer + IValue value = valueStack.getLast(); + valueStack.removeLast(); + return value; + } + nodeStack.add(nae); + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case ConditionalExpression _ -> { + assert nextEOG.size() == 2; + if (valueStack.getLast() instanceof VoidValue) { + valueStack.removeLast(); + valueStack.add(new BooleanValue()); + } + BooleanValue condition = (BooleanValue) valueStack.getLast(); + valueStack.removeLast(); + nodeStack.removeLast(); + // paths have no block statements at the end + // ToDo + throw new IllegalArgumentException("ConditionalExpression not supported yet"); + } + case BreakStatement _ -> { + assert nextEOG.size() == 1; + nodeStack.add(nextEOG.getFirst()); + return null; + } + case CatchClause _ -> { + // nothing for now + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case TryStatement _ -> { + // ignore for now + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case LambdaExpression le -> { + FunctionDeclaration lambda = le.getFunction(); + // ToDo + valueStack.add(Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.FUNCTION)); + nodeStack.add(le); + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case EmptyStatement _ -> { + // occurs, for example, when while loop body is empty + assert nextEOG.size() == 1; + return null; + } + case ExpressionList _ -> { + // indicates the end of an expression list, for example ("for (i2 = 6, i4 = 4; i2 < j; i2++)"), can be skipped + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case CastExpression _ -> { + // ignore casts for now as java types are not tracked precisely yet + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case AssertStatement _ -> { + // ignore for now, is technically dead code + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case ContinueStatement _ -> throw new IllegalStateException("ContinueStatement not supported yet"); + default -> throw new IllegalStateException("Unexpected value: " + node); + } + assert nextNode != null; + return graphWalker(nextNode); + } + + private void walkMemberExpression(@NotNull MemberExpression me) { + if (me.getRefersTo() instanceof FieldDeclaration || me.getRefersTo() instanceof EnumConstantDeclaration) { + if (valueStack.getLast() instanceof IJavaObject javaObject) { + assert valueStack.getLast() instanceof IJavaObject; + nodeStack.removeLast(); + // like Reference + nodeStack.add(me); + assert me.getName().getParent() != null; + valueStack.removeLast(); // remove object reference + IValue result = javaObject.accessField(me.getName().getLocalName()); + result.setParentObject(javaObject); + valueStack.add(result); + } else { + nodeStack.removeLast(); + nodeStack.add(me); + valueStack.removeLast(); // remove object reference + Value result = new VoidValue(); + result.setParentObject((IJavaObject) Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.OBJECT)); + valueStack.add(result); + } + } else if (me.getRefersTo() instanceof MethodDeclaration) { + nodeStack.removeLast(); + nodeStack.add(me); + } else { + // unknown + // look at last item on value stack + IValue value = valueStack.getLast(); + if (value instanceof VoidValue) { + valueStack.removeLast(); // remove object reference + valueStack.add(new JavaObject()); + } else { + throw new IllegalStateException("Unexpected value: " + value); + } + nodeStack.removeLast(); + nodeStack.add(me); + } + } + + private Node walkSubscriptExpression(@NotNull SubscriptExpression se) { // adds its value to the value stack + assert nodeStack.getLast() instanceof Literal || nodeStack.getLast() instanceof Reference || nodeStack.getLast() instanceof BinaryOperator + || nodeStack.getLast() instanceof MemberCallExpression || nodeStack.getLast() instanceof UnaryOperator + || nodeStack.getLast() instanceof SubscriptExpression; + assert !valueStack.isEmpty(); + if ((valueStack.getLast() instanceof VoidValue)) { + valueStack.removeLast(); + valueStack.add(Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.INT)); + } + INumberValue indexLiteral = (INumberValue) valueStack.getLast(); + valueStack.removeLast(); // remove index value + assert indexLiteral != null; + IValue ref = valueStack.getLast(); + valueStack.removeLast(); // remove array reference + if (!(ref instanceof IJavaArray)) { + // array might not be initialized yet + ref = Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.ARRAY); + } + IValue result = ((IJavaArray) ref).arrayAccess(indexLiteral); + result.setArrayPosition((IJavaArray) ref, indexLiteral); + valueStack.add(result); + nodeStack.removeLast(); + nodeStack.removeLast(); + nodeStack.add(se); + assert se.getNextEOG().size() == 1 || (se.getNextEOG().size() == 2 && se.getNextEOG().getLast() instanceof ShortCircuitOperator); + return se.getNextEOG().getFirst(); + } + + private void walkMemberCallExpression(@NotNull MemberCallExpression mce) { // adds its value to the value stack + IValue result; + if (mce.getArguments().isEmpty()) { // no arguments + MemberExpression me = (MemberExpression) nodeStack.getLast(); + Name memberName = me.getName(); + if (valueStack.getLast() instanceof VoidValue || valueStack.getLast() instanceof NullValue) { + // null value can happen: "if (opts.name == null || opts.name.isBlank())" where we dont strictly follow evaluation + // order. + valueStack.removeLast(); + valueStack.add(new JavaObject(new AbstractInterpretation(visitedLinesRecorder, removeDeadCode, recordingChanges))); + } + JavaObject javaObject = (JavaObject) valueStack.getLast(); + result = javaObject.callMethod(memberName.getLocalName(), null, (MethodDeclaration) mce.getInvokes().getLast()); + } else { + List argumentList = new ArrayList<>(); + for (int i = 0; i < mce.getArguments().size(); i++) { + if (mce.getArguments().get(i) instanceof ProblemExpression) { + continue; + } + argumentList.add(valueStack.getLast()); + nodeStack.removeLast(); + valueStack.removeLast(); + } + Collections.reverse(argumentList); + while (!(nodeStack.getLast() instanceof MemberExpression me)) { + // necessary for calls like g.inserirLigacao(v1,almax>=lmin && acmax>=cmin && ahmax>=hmin,v2); + // where the arguments contain operations + nodeStack.removeLast(); + } + Name memberName = me.getName(); + assert memberName.getParent() != null; + assert !valueStack.isEmpty(); + if (valueStack.getLast() instanceof VoidValue) { + valueStack.removeLast(); + valueStack.add(new JavaObject(new AbstractInterpretation(visitedLinesRecorder, removeDeadCode, recordingChanges))); + } + JavaObject javaObject = (JavaObject) valueStack.getLast(); + result = javaObject.callMethod(memberName.getLocalName(), argumentList, + (!mce.getInvokes().isEmpty()) ? (MethodDeclaration) mce.getInvokes().getLast() : null); + } + valueStack.removeLast(); // remove object reference + if (result == null) { // if method reference isn't known + result = Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.fromCpgType(mce.getType())); + } + valueStack.add(result); + nodeStack.removeLast(); + nodeStack.add(mce); + } + + private Node walkAssignExpression(@NotNull AssignExpression ae) { + assert !valueStack.isEmpty(); + if (ae.getLhs().getFirst() instanceof SubscriptExpression) { + assert ae.getLhs().size() == 1; + IValue newValue = valueStack.getLast(); + valueStack.removeLast(); + IValue oldValue = valueStack.getLast(); + // sometimes the value of assign is used after, so don't remove it + oldValue.getArrayPosition().component1().arrayAssign(oldValue.getArrayPosition().component2(), newValue); + } else { + Variable variable = variables.getVariable((nodeStack.get(nodeStack.size() - 2)).getName().toString()); + if (variable == null || nodeStack.get(nodeStack.size() - 2) instanceof MemberExpression) { // class access + IJavaObject classVal; + if (nodeStack.get(nodeStack.size() - 2).getName().getParent() == null) { // this class + classVal = variables.getThisObject(); + } else { + assert nodeStack.get(nodeStack.size() - 2).getName().getParent() != null; + classVal = valueStack.get(valueStack.size() - 2).getParentObject(); + } + assert classVal != null; + classVal.changeField((nodeStack.get(nodeStack.size() - 2)).getName().getLocalName(), valueStack.getLast()); + } else { + variable.setValue(valueStack.getLast()); + } + nodeStack.removeLast(); + // sometimes the value of assign is used after, so don't remove it + } + assert ae.getNextEOG().size() == 1; + return ae.getNextEOG().getFirst(); + } + + private Node walkShortCircuitOperator(@NotNull ShortCircuitOperator scop) { + assert scop.getPrevEOG().size() == 2; + if (valueStack.get(valueStack.size() - 2) instanceof VoidValue) { + valueStack.set(valueStack.size() - 2, new BooleanValue()); + } + BooleanValue value1 = (BooleanValue) valueStack.get(valueStack.size() - 2); + if (valueStack.getLast() instanceof VoidValue) { + valueStack.removeLast(); + valueStack.add(new BooleanValue()); + } + BooleanValue value2 = (BooleanValue) valueStack.getLast(); + valueStack.removeLast(); + valueStack.removeLast(); + if (Objects.equals(scop.getOperatorCode(), "||")) { + valueStack.add(value1.binaryOperation("||", value2)); + } else if (Objects.equals(scop.getOperatorCode(), "&&")) { + valueStack.add(value1.binaryOperation("&&", value2)); + } else { + throw new UnsupportedOperationException(scop.getOperatorCode() + " is not supported in ShortCircuitOperator"); + } + assert scop.getNextEOG().size() == 1 || scop.getNextEOG().size() == 2; + return scop.getNextEOG().getFirst(); + } + + @Nullable + private Node walkIfStatement(@NotNull IfStatement ifStmt) { + Node nextNode; + List nextEOG = ifStmt.getNextEOG(); + // detect infinite loops when no Block inserted by cpg + if (!lastVisitedLoopOrIf.isEmpty() && lastVisitedLoopOrIf.contains(ifStmt)) { + nodeStack.add(null); + return null; + } + lastVisitedLoopOrIf.addLast(ifStmt); + if (valueStack.getLast() instanceof VoidValue) { + valueStack.removeLast(); + valueStack.add(new BooleanValue()); + } + assert valueStack.getLast() instanceof BooleanValue; + BooleanValue condition = (BooleanValue) valueStack.getLast(); + valueStack.removeLast(); + boolean runThenBranch = true; + boolean runElseBranch = true; + Node thenBlock = ifStmt.getThenStatement(); // not always a block + Node elseBlock = ifStmt.getElseStatement(); + if (thenBlock == null || nextEOG.getFirst() instanceof DummyNeighbor) { + runThenBranch = false; + } + if (elseBlock == null || nextEOG.getLast() instanceof DummyNeighbor) { + runElseBranch = false; + } + if (condition.getInformation() && !recordingChanges.isRecording()) { + if (condition.getValue()) { + runElseBranch = false; + if (ifStmt.getElseStatement() != null) { + System.out.println("Dead code detected -> remove else branch"); + visitedLinesRecorder.recordLinesVisited(ifStmt.getElseStatement()); + } + if (ifStmt.getElseStatement() != null && removeDeadCode) { + // Dead code detected -> remove else branch + TransformationUtil.disconnectFromPredecessor(nextEOG.getLast()); + ifStmt.setElseStatement(null); + } + } else { + runThenBranch = false; + // Dead code detected + System.out.println("Dead code detected -> remove then branch"); + visitedLinesRecorder.recordDetectedDeadLines(ifStmt.getThenStatement()); + if (ifStmt.getElseStatement() == null) { + visitedLinesRecorder.recordDetectedDeadLines(ifStmt); + } + if (removeDeadCode) { + TransformationUtil.disconnectFromPredecessor(nextEOG.getFirst()); + ifStmt.setThenStatement(null); + if (ifStmt.getElseStatement() == null) { + TransformationUtil.disconnectFromPredecessor(ifStmt); + assert ifStmt.getScope() != null; + Block containingBlock = (Block) ifStmt.getScope().getAstNode(); + assert containingBlock != null; + List statements = containingBlock.getStatements(); + statements.remove(ifStmt); + containingBlock.setStatements(statements); + } + } + } + } + nodeStack.removeLast(); // remove condition + assert nextEOG.size() == 2; + VariableStore originalVariables = variables; + VariableStore thenVariables = new VariableStore(variables); + VariableStore elseVariables = new VariableStore(variables); + // then statement + if (runThenBranch) { + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + if (nodeStack.getLast() == null && elseBlock != null && ifStmt.getElseStatement() == null && !elseBlock.getNextEOG().isEmpty()) { + // special case for dead else branch + assert elseBlock.getNextEOG().size() == 1; + nodeStack.add(elseBlock.getNextEOG().getFirst()); + } + if (nodeStack.isEmpty() || nodeStack.getLast() == null) { + nodeStack.add(nextEOG.getLast()); + } + } + // else statement + if (runElseBranch) { + if (ifStmt.getElseStatement() instanceof IfStatement) { // this loop is a loop with if else + ifElseCounter++; + } + if (runThenBranch) { + variables = elseVariables; + this.object = variables.getThisObject(); + } + variables.newScope(); + graphWalker(nextEOG.getLast()); + variables.removeScope(); + if (nodeStack.getLast() == null) { + nodeStack.add(nextEOG.getFirst()); + } + } + // merge branches + if (runThenBranch && runElseBranch) { + originalVariables.merge(elseVariables); + } else if (runThenBranch) { + if (!condition.getInformation()) { + thenVariables.merge(originalVariables); + nodeStack.add(nextEOG.getLast()); + } + } else if (runElseBranch) { + if (!condition.getInformation()) { + originalVariables.merge(elseVariables); + } + } else { // no branch is run + nodeStack.add(nextEOG.getLast()); + } + this.object = variables.getThisObject(); // Update object reference + nextNode = nodeStack.getLast(); + lastVisitedLoopOrIf.remove(ifStmt); + if (ifElseCounter > 0) { + ifElseCounter--; + return null; + } + if (returnStorage.size() >= 2 || (!returnStorage.isEmpty() && (runThenBranch != runElseBranch) && condition.getInformation())) { // FixMe: + // stringAiComplex + // return in every branch + valueStack.add(returnStorage.getLast()); + nextNode = new ReturnStatement(); + } + return nextNode; + } + + private IValue walkReturnStatement() { + IValue result; + if (valueStack.isEmpty()) { + result = new VoidValue(); + } else { + result = valueStack.getLast(); + valueStack.removeLast(); + } + if (!lastVisitedLoopOrIf.isEmpty()) { + // we are inside a loop or if statement + returnStorage.addLast(result); + } else { + // merge other returns + for (IValue value : returnStorage) { + result.merge(value); + } + returnStorage.clear(); + } + nodeStack.add(null); + return result; + } + + private Node walkSwitchStatement(@NotNull SwitchStatement sw) { // ToDo delete dead Code in switch + Node nextNode; + assert !valueStack.isEmpty(); + int branches = sw.getNextEOG().size(); + VariableStore originalVariables = new VariableStore(variables); + VariableStore result = null; + nodeStack.removeLast(); + for (Node branch : sw.getNextEOG()) { + variables = new VariableStore(originalVariables); + this.object = variables.getThisObject(); + variables.newScope(); + graphWalker(branch); + variables.removeScope(); + if (result == null) { + result = variables; + } else { + result.merge(variables); + } + } + variables = result; + this.object = variables.getThisObject(); + nextNode = nodeStack.getLast(); + if (nextNode instanceof Block block) { // scoped switch statements have an extra block + assert block.getNextEOG().size() == 1; + nextNode = block.getNextEOG().getFirst(); + } + return nextNode; + } + + private void walkNewExpression(@NotNull NewExpression ne) { + Declaration classNode = ((ConstructExpression) nodeStack.getLast()).getInstantiates(); + List arguments = new ArrayList<>(); + ConstructExpression ce = (ConstructExpression) nodeStack.getLast(); + nodeStack.removeLast(); // remove ConstructExpression + if (!ce.getArguments().isEmpty()) { + int size = ce.getArguments().size(); + for (int i = 0; i < size; i++) { + arguments.add(valueStack.getLast()); + valueStack.removeLast(); + nodeStack.removeLast(); + } + } + Collections.reverse(arguments); + JavaObject newObject = createNewObject(ce); + valueStack.add(newObject); + // run constructor + if (classNode != null) { + AbstractInterpretation classAi = new AbstractInterpretation(visitedLinesRecorder, removeDeadCode, recordingChanges); + classAi.runClass((RecordDeclaration) classNode, newObject, arguments, Objects.requireNonNull(ce.getConstructor())); + } + nodeStack.add(ne); + } + + @Nullable + private Node walkWhileStatement(@NotNull WhileStatement ws) { + Node nextNode; + List nextEOG = ws.getNextEOG(); + assert nextEOG.size() == 2; + // detect infinite loops when no Block inserted by cpg + if (!lastVisitedLoopOrIf.isEmpty() && lastVisitedLoopOrIf.contains(ws)) { + nodeStack.add(null); + return null; + } + lastVisitedLoopOrIf.addLast(ws); + // evaluate condition + if (valueStack.getLast() instanceof VoidValue) { + valueStack.removeLast(); + valueStack.add(new BooleanValue()); + } + assert !valueStack.isEmpty() && valueStack.getLast() instanceof BooleanValue; + BooleanValue condition = (BooleanValue) valueStack.getLast(); + valueStack.removeLast(); + nodeStack.removeLast(); + if (!condition.getInformation() || condition.getValue()) { // run body if the condition is true or unknown + if (recordingChanges.isRecording()) { // higher level loop wants to know which variables change + variables.recordChanges(); + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + Set changedVariables = variables.stopRecordingChanges(); + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + } else { + VariableStore originalVariables = this.variables; + // 1: first loop run: detect variables that change in loop -> run loop with completely unknown variables + record + // changes + this.variables = new VariableStore(variables); + variables.setEverythingUnknown(); + variables.recordChanges(); + recordingChanges.setRecording(true); + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + recordingChanges.setRecording(false); + Set changedVariables = variables.stopRecordingChanges(); + // 2: second loop run with only changed variables unknown + this.variables = new VariableStore(originalVariables); + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + // 3: restore variables and set changed variables to unknown + this.variables = originalVariables; + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + } + } else if (!recordingChanges.isRecording()) { + // Dead code detected, loop never runs + if (removeDeadCode) { + TransformationUtil.disconnectFromPredecessor(nextEOG.getFirst()); + TransformationUtil.disconnectFromPredecessor(ws); + assert ws.getScope() != null; + Block containingBlock = (Block) ws.getScope().getAstNode(); + assert containingBlock != null; + List statements = containingBlock.getStatements(); + statements.remove(ws); + containingBlock.setStatements(statements); + } + visitedLinesRecorder.recordDetectedDeadLines(ws); + System.out.println("Dead code detected -> remove while"); + } + // continue with next node after while + lastVisitedLoopOrIf.removeLast(); + nextNode = nextEOG.getLast(); + if (!returnStorage.isEmpty() && condition.getInformation() && condition.getValue()) { + // return in every branch + valueStack.add(returnStorage.getLast()); + nextNode = new ReturnStatement(); + } + return nextNode; + } + + @Nullable + private Node walkForStatement(@NotNull ForStatement ws) { + Node nextNode; + List nextEOG = ws.getNextEOG(); + assert nextEOG.size() == 2; + // detect infinite loops when no Block inserted by cpg + if (!lastVisitedLoopOrIf.isEmpty() && lastVisitedLoopOrIf.contains(ws)) { + nodeStack.add(null); + return null; + } + lastVisitedLoopOrIf.addLast(ws); + // evaluate condition + assert !valueStack.isEmpty() && valueStack.getLast() instanceof BooleanValue; + BooleanValue condition = (BooleanValue) valueStack.getLast(); + valueStack.removeLast(); + nodeStack.removeLast(); + if (!condition.getInformation() || condition.getValue()) { // run body if the condition is true or unknown + if (recordingChanges.isRecording()) { // higher level loop wants to know which variables change + variables.recordChanges(); + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + Set changedVariables = variables.stopRecordingChanges(); + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + } else { + VariableStore originalVariables = this.variables; + // 1: first loop run: detect variables that change in loop -> run loop with completely unknown variables + record + // changes + this.variables = new VariableStore(variables); + variables.setEverythingUnknown(); + variables.recordChanges(); + recordingChanges.setRecording(true); + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + recordingChanges.setRecording(false); + Set changedVariables = variables.stopRecordingChanges(); + // 2: second loop run with only changed variables unknown + this.variables = new VariableStore(originalVariables); + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + // for loop special: iteration variable also unknown + if (ws.getIterationStatement() != null) { + Variable iterVar; + if (ws.getIterationStatement() instanceof UnaryOperator unaryOperator) { + iterVar = variables.getVariable(new VariableName(unaryOperator.getInput().getName().toString())); + } else if (ws.getIterationStatement() instanceof AssignExpression assignExpression) { + assert assignExpression.getLhs().size() == 1; + iterVar = variables.getVariable(new VariableName(assignExpression.getLhs().getFirst().getName().toString())); + } else { + throw new IllegalStateException(); + } + if (iterVar != null) { + iterVar.setToUnknown(); + } + } + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + // 3: restore variables and set changed variables to unknown + this.variables = originalVariables; + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + // for loop special: iteration variable also unknown + if (ws.getIterationStatement() != null) { + Variable iterVar; + if (ws.getIterationStatement() instanceof UnaryOperator unaryOperator) { + iterVar = variables.getVariable(new VariableName(unaryOperator.getInput().getName().toString())); + } else if (ws.getIterationStatement() instanceof AssignExpression assignExpression) { + assert assignExpression.getLhs().size() == 1; + iterVar = variables.getVariable(new VariableName(assignExpression.getLhs().getFirst().getName().toString())); + } else { + throw new IllegalStateException(); + } + if (iterVar != null) { + iterVar.setToUnknown(); + } + } + } + } else if (!recordingChanges.isRecording()) { + // Dead code detected, loop never runs + if (removeDeadCode) { + TransformationUtil.disconnectFromPredecessor(nextEOG.getFirst()); + TransformationUtil.disconnectFromPredecessor(ws); + assert ws.getScope() != null; + Block containingBlock = (Block) ws.getScope().getAstNode(); + assert containingBlock != null; + List statements = containingBlock.getStatements(); + statements.remove(ws); + containingBlock.setStatements(statements); + } + visitedLinesRecorder.recordDetectedDeadLines(ws); + System.out.println("Dead code detected -> remove for"); + } + // continue with next node after while + lastVisitedLoopOrIf.removeLast(); + nextNode = nextEOG.getLast(); + if (!returnStorage.isEmpty() && condition.getInformation() && condition.getValue()) { + // return in every branch + valueStack.add(returnStorage.getLast()); + nextNode = new ReturnStatement(); + } + return nextNode; + } + + @Nullable + private Node walkForEachStatement(@NotNull ForEachStatement fes) { + Node nextNode; + List nextEOG = fes.getNextEOG(); + assert nextEOG.size() == 2; + if (!lastVisitedLoopOrIf.isEmpty() && fes == lastVisitedLoopOrIf.getLast()) { + nodeStack.add(null); + return null; + } + lastVisitedLoopOrIf.addLast(fes); + assert !valueStack.isEmpty(); + if (valueStack.getLast() instanceof VoidValue) { + valueStack.removeLast(); + valueStack.add(Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.ARRAY)); + } + IJavaArray collection = (IJavaArray) valueStack.getLast(); + // ToDo: set right variable value + valueStack.removeLast(); + assert fes.getVariable() != null; + String varName = (fes.getVariable().getDeclarations().getFirst()).getName().toString(); + Variable variable1 = new Variable(new VariableName(varName), collection.arrayAccess((INumberValue) Value.valueFactory(0))); + variables.addVariable(variable1); + if (collection.accessField("length") instanceof INumberValue length && length.getInformation() && (length.getValue() == 0)) { + if (!recordingChanges.isRecording()) { + // Dead code detected, loop never runs + if (removeDeadCode) { + TransformationUtil.disconnectFromPredecessor(nextEOG.getFirst()); + TransformationUtil.disconnectFromPredecessor(fes); + assert fes.getScope() != null; + Block containingBlock = (Block) fes.getScope().getAstNode(); + assert containingBlock != null; + List statements = containingBlock.getStatements(); + statements.remove(fes); + containingBlock.setStatements(statements); + } + visitedLinesRecorder.recordDetectedDeadLines(fes); + System.out.println("Dead code detected -> remove for each"); + } + } else { + if (recordingChanges.isRecording()) { // higher level loop wants to know which variables change + variables.recordChanges(); + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + Set changedVariables = variables.stopRecordingChanges(); + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + } else { + VariableStore originalVariables = this.variables; + // 1: first loop run: detect variables that change in loop -> run loop with completely unknown variables + record + // changes + this.variables = new VariableStore(variables); + variables.setEverythingUnknown(); + variables.recordChanges(); + recordingChanges.setRecording(true); + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + recordingChanges.setRecording(false); + Set changedVariables = variables.stopRecordingChanges(); + // 2: second loop run with only changed variables unknown + this.variables = new VariableStore(originalVariables); + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + // 3: restore variables and set changed variables to unknown + this.variables = originalVariables; + for (Variable variable : changedVariables) { + variables.getVariable(variable.getName()).setToUnknown(); + } + } + } + // continue with the next node after for each + lastVisitedLoopOrIf.removeLast(); + nextNode = nextEOG.getLast(); + return nextNode; + } + + private void walkNewArrayExpression(@NotNull NewArrayExpression nae) { + // either dimension or initializer is present + if (!nae.getDimensions().isEmpty()) { + List dimensions = new ArrayList<>(); + for (int i = 0; i < nae.getDimensions().size(); i++) { + if (valueStack.getLast() instanceof VoidValue) { + dimensions.add((INumberValue) Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.INT)); + } else { + dimensions.add((INumberValue) valueStack.getLast()); + } + valueStack.removeLast(); + nodeStack.removeLast(); + } + // recover inner type + de.jplag.java_cpg.ai.variables.Type innerType = null; + if (((HasType) nae.getTypeObservers().iterator().next()).getType() instanceof PointerType pointerType) { + innerType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(pointerType.elementType); + } + IJavaArray newArray = Value.getNewArayValue(innerType, dimensions.getFirst()); + for (int i = 1; i < nae.getDimensions().size(); i++) { // multi-dimensional arrays + List innerArrays = new ArrayList<>(); + INumberValue dimension = dimensions.get(i - 1); + if (dimension.getInformation()) { + for (int j = 0; j < dimension.getValue(); j++) { + innerArrays.add(Value.getNewArayValue(innerType)); + } + } else { + // Dimension is unknown, create an array with unknown contents + innerArrays.add(Value.getNewArayValue(innerType)); + } + newArray = new JavaArray(innerArrays.stream().map(a -> (IValue) a).toList()); + } + valueStack.add(newArray); + } else if (nae.getInitializer() != null) { + if (nae.getPrevEOG().getFirst() instanceof InitializerListExpression) { + // initializer has already been processed + assert valueStack.getLast() instanceof JavaArray; + assert nodeStack.getLast() instanceof InitializerListExpression; + } else { + throw new IllegalStateException("Unexpected value: " + nae); + } + } else { + throw new IllegalStateException("Unexpected value: " + nae); + } + } + + @NotNull + @TestOnly + protected VariableStore getVariables() { + return variables; + } + + /** + * Runs a method in this abstract interpretation engine context with the given name and parameters. + * @param name the name of the method to run. + * @param paramVars the parameters to pass to the method. + * @param method the cpg method declaration to this method. + * @return null if the method is not known. + */ + public IValue runMethod(@NotNull String name, List paramVars, @Nullable MethodDeclaration method) { + if (lastVisitedMethod.contains(method)) { + // recursive call detected + this.variables.setEverythingUnknown(); + return new VoidValue(); + } + lastVisitedMethod.add(method); + if (method == null) { + return null; + } + int numberOfCalls = method.getUsages().size(); + if (numberOfCalls > 1) { + // method is called multiple times, set all variables to unknown + this.variables.setEverythingUnknown(); + paramVars.forEach(IValue::setToUnknown); + } + ArrayList oldNodeStack = this.nodeStack; // Save stack + ArrayList oldValueStack = this.valueStack; + List oldLastVisitedLoopOrIf = this.lastVisitedLoopOrIf; + this.nodeStack = new ArrayList<>(); + this.valueStack = new ArrayList<>(); + this.lastVisitedLoopOrIf = new ArrayList<>(); + + visitedLinesRecorder.recordFirstLineVisited(method); + variables.newScope(); + if (paramVars != null) { + assert method.getParameters().size() == paramVars.size(); + for (int i = 0; i < paramVars.size(); i++) { + variables.addVariable(new Variable(new VariableName(method.getParameters().get(i).getName().getLocalName()), paramVars.get(i))); + } + } else { + assert method.getParameters().isEmpty(); + } + IValue result; + assert method.getNextEOG().size() <= 1; + if (method.getNextEOG().size() == 1) { + result = graphWalker(method.getNextEOG().getFirst()); + } else { + result = new VoidValue(); + } + variables.removeScope(); + this.nodeStack = oldNodeStack; // restore stack + this.valueStack = oldValueStack; + this.lastVisitedLoopOrIf = oldLastVisitedLoopOrIf; + lastVisitedMethod.removeLast(); + return result; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AiPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AiPass.kt new file mode 100644 index 0000000000..e4bed92056 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AiPass.kt @@ -0,0 +1,152 @@ +package de.jplag.java_cpg.ai + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.Component +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration +import de.fraunhofer.aisec.cpg.graph.records +import de.fraunhofer.aisec.cpg.passes.TranslationResultPass +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore +import de.jplag.java_cpg.passes.CpgTransformationPass +import de.jplag.java_cpg.passes.TokenizationPass +import java.net.URI +import java.util.* + +/** + * A CPG Pass performing Abstract Interpretation on the CPG Translation Result. + */ +@DependsOn(CpgTransformationPass::class) +@ExecuteBefore(TokenizationPass::class) +class AiPass(ctx: TranslationContext) : TranslationResultPass(ctx) { + //passes have to be in kotlin! + + /** + * Empty cleanup method. + */ + override fun cleanup() { + // Nothing to do + } + + override fun accept(p0: TranslationResult) { + var visitedLinesRecorder = VisitedLinesRecorder() + val abstractInterpretation = AbstractInterpretation(visitedLinesRecorder, removeDeadCode) + val comp: Component = p0.components.first() + for (translationUnit in comp.translationUnits) { + if (translationUnit.name.parent?.localName?.endsWith("Main") == true || translationUnit.name.toString() + .endsWith("Main.java") || comp.translationUnits.size == 1 + ) { + runCatching { + abstractInterpretation.runMain(translationUnit) + }.onFailure { t -> + log.error("AI pass failed for translation unit: ${translationUnit.name}", t) + throw t + } + } + } + println("Abstract Interpretation finished.") + //find dead methods and classes + try { + for (translationUnit in comp.translationUnits) { + for (recordDeclaration in translationUnit.records) { + if (checkIfCompletelyDead(recordDeclaration, visitedLinesRecorder) && removeDeadCode) { + println("Dead code (class) detected: ${recordDeclaration.name}") + // Try removing from Translation Unit directly + val tuIndex = translationUnit.declarations.indexOf(recordDeclaration) + if (tuIndex > 0) { + translationUnit.declarationEdges.removeAt(tuIndex) + } + // Try removing from Namespaces + for (ns in translationUnit.declarations.filterIsInstance()) { + val nsIndex = ns.declarations.indexOf(recordDeclaration) + if (nsIndex > 0) { + ns.declarations.removeAt(nsIndex) + } + } + continue + } + for (method in recordDeclaration.methods) { + if (checkIfCompletelyDead(method, visitedLinesRecorder) && removeDeadCode) { + println("Dead code (method) detected: ${method.name} in class ${recordDeclaration.name}") + val index = recordDeclaration.methods.indexOf(method) + recordDeclaration.methodEdges.removeAt(index) + } + } + //inner classes + for (innerClass in recordDeclaration.records) { + if (checkIfCompletelyDead(innerClass, visitedLinesRecorder) && removeDeadCode) { + println("Dead code (class) detected: ${recordDeclaration.name}") + val tuIndex = recordDeclaration.declarations.indexOf(innerClass) + recordDeclaration.recordEdges.removeAt(tuIndex) + } + } + } + } + } catch (e: Exception) { + log.error("Error while detecting dead classes and methods", e) + } + //Debug + val visitedLines: Set = visitedLinesRecorder.visitedLines.values.firstOrNull() ?: emptySet() + val sortedVisitedLines = TreeSet(visitedLines) + println("Abstract Interpretation code removal finished.") + } + + fun checkIfCompletelyDead(node: Node, visitedLinesRecorder: VisitedLinesRecorder): Boolean { + val fileName: URI = node.location?.artifactLocation?.uri ?: URI.create("unknown") + val startLine: Int = node.location?.region?.startLine ?: -1 + val endLine: Int = node.location?.region?.endLine ?: -1 + val completelyDead: Boolean = visitedLinesRecorder.checkIfCompletelyDead(fileName, startLine, endLine) + if (completelyDead) { + visitedLinesRecorder.recordDetectedDeadLines(fileName, startLine, endLine) + } + return completelyDead + } + + companion object AiPassCompanion { + var removeDeadCode: Boolean = true + } + +} + +/** + * Enumeration of different representations for integers. + */ +enum class IntAiType { + INTERVALS, + DEFAULT, + SET, +} + +/** + * Enumeration of different representations for floating-point numbers. + */ +enum class FloatAiType { + SET, + DEFAULT, +} + +/** + * Enumeration of different representations for strings. + */ +enum class StringAiType { + CHAR_INCLUSION, + REGEX, + DEFAULT, +} + +/** + * Enumeration of different representations for characters. + */ +enum class CharAiType { + SET, + DEFAULT, +} + +/** + * Enumeration of different representations for arrays. + */ +enum class ArrayAiType { + LENGTH, + DEFAULT, +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/README.md b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/README.md new file mode 100644 index 0000000000..9dc567fa0a --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/README.md @@ -0,0 +1,63 @@ +# JPlag-cpg Abstract Interpretation Engine + +For debug, please run with `-ea` JVM flag. + +**For now a lot of methods have a default switch case that throws an exception. +They will later be replaced with a default case that sets the value to unknown and returns an unknown value.** + +All inputted code must be syntactically correct Java code that compiles without errors. + +## Build + +Maven: `mvn clean package` + +## Code Structure + +ToDo + +## Supported language features + +see [ToDos](./ToDo.md) for supported and unsupported features. + +## Explicitly not supported language features + +- exception flow is not modeled +- System.exit calls are not supported +- Continues and breaks in loops are not supported +- Iterators are not supported + +see [ToDos](./ToDo.md) for supported and unsupported features. + +## Usage Example + +```java +import de.jplag.JPlag; +import de.jplag.JPlagResult; +import de.jplag.Language; +import de.jplag.exceptions.ExitException; +import de.jplag.java_cpg.JavaCpgLanguage; +import de.jplag.options.JPlagOptions; +import de.jplag.reporting.reportobject.ReportObjectFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Set; + +public static void main(String[] args) { + Language language = new JavaCpgLanguage(); + File submissionsRoot = new File(".../submissionsFolders"); + Set submissionDirectories = Set.of(submissionsRoot); + JPlagOptions options = new JPlagOptions(language, submissionDirectories, Set.of()); + try { + JPlagResult result = JPlag.run(options); + File outDir = new File(System.getProperty("user.home") + "/Downloads/"); + ReportObjectFactory reportObjectFactory = new ReportObjectFactory(outDir); + reportObjectFactory.createAndSaveReport(result); + System.out.println("JPlag analysis finished. Report written to: " + outDir.getAbsolutePath()); + } catch (ExitException e) { + System.err.println("JPlag exited with an error: " + e.getMessage()); + } catch (FileNotFoundException e) { + System.err.println("I/O error: " + e.getMessage()); + } +} +``` diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/RecordingChanges.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/RecordingChanges.java new file mode 100644 index 0000000000..d934933665 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/RecordingChanges.java @@ -0,0 +1,34 @@ +package de.jplag.java_cpg.ai; + +/** + * Class to keep track of whether changes are being recorded between multiple ai Instances. + * @author ujiqk + * @version 1.0 + */ +public class RecordingChanges { + + private boolean recording; + + /** + * Constructor for RecordingChanges value holder. + * @param recordingChanges whether changes are being recorded. + */ + public RecordingChanges(boolean recordingChanges) { + this.recording = recordingChanges; + } + + /** + * @return whether changes are being recorded. + */ + public boolean isRecording() { + return recording; + } + + /** + * @param recordingChanges sets whether changes are being recorded. + */ + public void setRecording(boolean recordingChanges) { + this.recording = recordingChanges; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/ToDo.md b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/ToDo.md new file mode 100644 index 0000000000..749e4d325c --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/ToDo.md @@ -0,0 +1,58 @@ +# ToDos + +- [x] if with condition information +- [x] switch statements +- [x] loop ai +- [x] exceptions +- [x] hash maps, sets +- [ ] comparators +- [ ] think about copy of bound JavaObject +- [ ] ConditionalExpression (a ? b : c) (hard) +- [x] Array assign +- [x] Enums +- [ ] Streams +- [ ] Lambdas +- [ ] Import System? +- [ ] Array Init in Method fields +- [x] try/catch +- [ ] finally +- [ ] TreeSet +- [x] Comments/Javadocs +- [x] Uninitialized variables to default value +- [ ] all java datatypes +- [ ] goto +- [x] return inside if or loop +- [ ] "if (opts.name == null || opts.name.isBlank())" don't evaluate the second part if the first is true +- [ ] proper switch statement support +- [ ] if always true → remove condition +- [ ] remove empty methods and their method calls +- [ ] hard: remove useless loops ← runs only once + +## Edge Cases to Test + +- [x] if without else +- [ ] for loop with break/continue +- [ ] switch with literal returns +- [x] nested loops +- [x] Set> sortedEntries = new TreeSet<>(Comparator.comparing((Map.Entry::getKey)));] +- [x] if with || in condition +- [x] if with && in condition +- [x] if with else-if +- [ ] nested list initializer in a class field +- [x] while with assign in condition + +## Object AI + +- [ ] Maps +- [ ] Sets +- [ ] Comparators +- [x] dead methods and classes + +## Long Term + +- [ ] explicitly carry object types everywhere +- [ ] split void type and unknown type +- [ ] loop invariants detector + +## Limitations diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/VisitedLinesRecorder.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/VisitedLinesRecorder.java new file mode 100644 index 0000000000..ccc85fd799 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/VisitedLinesRecorder.java @@ -0,0 +1,171 @@ +package de.jplag.java_cpg.ai; + +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation; + +/** + * Records visited code lines in source files. + * @author ujiqk + * @version 1.0 + */ +public class VisitedLinesRecorder { + + private final Map> visitedLines; + private final Map> possibleLines; + private final Map> detectedDeadLines; + + /** + * Creates a new VisitedLinesRecorder. + */ + public VisitedLinesRecorder() { + visitedLines = new HashMap<>(); + possibleLines = new HashMap<>(); + detectedDeadLines = new HashMap<>(); + } + + /** + * @param node record lines visited in the given node + */ + public void recordLinesVisited(@NotNull Node node) { + PhysicalLocation location = node.getLocation(); + if (location == null) { + return; + } + URI uri = location.getArtifactLocation().getUri(); + Set alreadyVisitedLines = visitedLines.computeIfAbsent(uri, _ -> new HashSet<>()); + // Only line numbers and not full regions are stored + int startLine = location.getRegion().startLine; + int endLine = location.getRegion().getEndLine(); + for (int line = startLine; line <= endLine; line++) { + alreadyVisitedLines.add(line); + } + // check if file information is already present + possibleLines.computeIfAbsent(uri, this::countLinesInFile); + } + + /** + * @param node record first line visited in the given node + */ + public void recordFirstLineVisited(@NotNull Node node) { + PhysicalLocation location = node.getLocation(); + if (location == null) { + return; + } + URI uri = location.getArtifactLocation().getUri(); + Set alreadyVisitedLines = visitedLines.computeIfAbsent(uri, _ -> new HashSet<>()); + // Only line numbers and not full regions are stored + int startLine = location.getRegion().startLine; + alreadyVisitedLines.add(startLine); + // check if file information is already present + possibleLines.computeIfAbsent(uri, this::countLinesInFile); + } + + /** + * Records that lines are detected to be dead code. + * @param uri the file URI + * @param startLine the start line of the dead code region (inclusive) + * @param endLine the end line of the dead code region (inclusive) + */ + public void recordDetectedDeadLines(@NotNull URI uri, int startLine, int endLine) { + assert startLine <= endLine; + assert startLine >= 0; + Set deadLines = new HashSet<>(); + for (int line = startLine; line <= endLine; line++) { + deadLines.add(line); + } + Set alreadyDeadLines = detectedDeadLines.computeIfAbsent(uri, _ -> new HashSet<>()); + alreadyDeadLines.addAll(deadLines); + } + + /** + * Records that lines are detected to be dead code. + * @param node the node representing the dead code region + */ + public void recordDetectedDeadLines(Node node) { + if (node == null) + return; + PhysicalLocation location = node.getLocation(); + if (location == null) { + return; + } + URI uri = location.getArtifactLocation().getUri(); + int startLine = location.getRegion().startLine; + int endLine = location.getRegion().getEndLine(); + recordDetectedDeadLines(uri, startLine, endLine); + } + + /** + * @return a map of URIs to sets of line numbers that have not been visited + */ + @NotNull + @Pure + @TestOnly + public Map> getNonVisitedLines() { + Map> nonVisitedLines = new HashMap<>(); + for (Map.Entry> entry : possibleLines.entrySet()) { + URI uri = entry.getKey(); + Set possible = entry.getValue(); + Set visited = visitedLines.getOrDefault(uri, new HashSet<>()); + Set nonVisited = new HashSet<>(possible); + nonVisited.removeAll(visited); + nonVisitedLines.put(uri, nonVisited); + } + return nonVisitedLines; + } + + /** + * Checks if the given lines in the file are completely dead (not visited). + * @param uri the file URI + * @param startLine the start line of the region (inclusive) + * @param endLine the end line of the region (inclusive) + * @return true if all lines in the given range are not visited, false otherwise + */ + @Pure + public boolean checkIfCompletelyDead(@NotNull URI uri, int startLine, int endLine) { + if (startLine == -1 || endLine == -1) { + return false; + } + assert startLine <= endLine; + assert startLine >= 0; + Set visited = visitedLines.getOrDefault(uri, new HashSet<>()); + for (int line = startLine; line <= endLine; line++) { + if (visited.contains(line)) { + return false; + } + } + return true; + } + + @NotNull + private Set countLinesInFile(@NotNull URI uri) { + Set lines = new HashSet<>(); + try (var reader = new java.io.BufferedReader(new java.io.FileReader(new java.io.File(uri)))) { + int lineNumber = 1; + while (reader.readLine() != null) { + lines.add(lineNumber++); + } + } catch (java.io.IOException e) { + throw new IllegalStateException("Could not read file: " + uri, e); + } + return lines; + } + + /** + * @return the map of visited lines. For testing purposes only. + */ + @TestOnly + public Map> getVisitedLines() { + return visitedLines; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/ChangeRecorder.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/ChangeRecorder.java new file mode 100644 index 0000000000..fe83c7f627 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/ChangeRecorder.java @@ -0,0 +1,37 @@ +package de.jplag.java_cpg.ai.variables; + +import java.util.Set; + +/** + * Recorder for changes in variables. Can be added to {@link VariableStore}s, {@link Scope}s and {@link Variable}s to + * track when they are changed. + * @author ujiqk + * @version 1.0 + */ +public class ChangeRecorder { + + private final Set changedVariables = new java.util.HashSet<>(); + + /** + * Creates a new ChangeRecorder. + */ + public ChangeRecorder() { + // empty + } + + /** + * Called by a {@link Variable} when it is changed. + * @param variable the calling variable. + */ + public void recordChange(Variable variable) { + changedVariables.add(variable); + } + + /** + * @return the set of changed variables. + */ + public Set getChangedVariables() { + return changedVariables; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Scope.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Scope.java new file mode 100644 index 0000000000..000794e33e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Scope.java @@ -0,0 +1,122 @@ +package de.jplag.java_cpg.ai.variables; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A scope containing variables. + * @author ujiqk + * @version 1.0 + */ +public class Scope { + + private HashMap variables = new HashMap<>(); + + /** + * Copy constructor. Performs a deep copy of the variables. + * @param scope the scope to copy. + */ + public Scope(@NotNull Scope scope) { + for (Map.Entry entry : scope.variables.entrySet()) { + VariableName clonedKey = entry.getKey(); + Variable clonedValue = new Variable(entry.getValue()); + this.variables.put(clonedKey, clonedValue); + } + } + + /** + * Default constructor. + */ + public Scope() { + // empty + } + + /** + * Overwrites or adds a variable in this scope. + * @param variable the variable to add. + */ + public void addVariable(Variable variable) { + variables.put(variable.getName(), variable); + } + + /** + * Gets a variable by its name or null if it does not exist in this scope. + * @param name the name of the variable. + * @return the variable or null if it does not exist in this scope. + */ + public Variable getVariable(VariableName name) { + return variables.get(name); + } + + /** + * Merges the information of another instance of the same scope into this one. The same variables with potentially + * different values must exist in both scopes. + * @param otherScope the other scope to merge into this one. + */ + public void merge(@NotNull Scope otherScope) { + // assert: both scopes contain the same variables with potentially different values + for (Map.Entry entry : otherScope.variables.entrySet()) { + if (!(this.variables.containsKey(entry.getKey()))) { + // can happen if the object is null in one branch but assigned in other + entry.getValue().setToUnknown(); + continue; + } + assert this.variables.containsKey(entry.getKey()); + this.variables.get(entry.getKey()).merge(entry.getValue()); + } + otherScope.variables = this.variables; + } + + /** + * Sets all variables to completely unknown. + */ + public void setEverythingUnknown() { + for (Variable variable : variables.values()) { + variable.setToUnknown(); + } + } + + /** + * Sets all variables to their initial value. The initial value depends on the variable type. + */ + public void setEverythingInitialValue() { + for (Variable variable : variables.values()) { + variable.setInitialValue(); + } + } + + /** + * Starts recording changes to all variables. + * @param changeRecorder the ChangeRecorder to notify on changes. + */ + public void addChangeRecorder(@NotNull ChangeRecorder changeRecorder) { + for (Map.Entry entry : variables.entrySet()) { + entry.getValue().addChangeRecorder(changeRecorder); + } + } + + /** + * Stops recording changes to all variables and returns the ChangeRecorder. If addChangeRecorder() was not called + * before, null is returned. + * @return the ChangeRecorder that was removed, or null if no recorders existed. + */ + @Nullable + public ChangeRecorder removeLastChangeRecorder() { + List recorders = new ArrayList<>(); + for (Map.Entry entry : variables.entrySet()) { + recorders.add(entry.getValue().removeLastChangeRecorder()); + } + if (recorders.isEmpty()) { + return null; + } + ChangeRecorder first = recorders.getFirst(); + assert recorders.stream().allMatch(r -> r == first); + return first; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Type.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Type.java new file mode 100644 index 0000000000..402a6d1bc7 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Type.java @@ -0,0 +1,52 @@ +package de.jplag.java_cpg.ai.variables; + +import org.jetbrains.annotations.NotNull; + +/** + * Enumeration of supported variable types. + * @author ujiqk + * @version 1.0 + */ +public enum Type { + INT, + FLOAT, + STRING, + BOOLEAN, + OBJECT, + ARRAY, + LIST, + NULL, + FUNCTION, + CHAR, + UNKNOWN, // ToDo: split void value to void and unknown?? + VOID; + + /** + * @param cpgType CPG type to convert. + * @return the corresponding Type enum. + * @throws IllegalArgumentException if the CPG type is not supported. + */ + public static Type fromCpgType(@NotNull de.fraunhofer.aisec.cpg.graph.types.Type cpgType) { + if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.IntegerType.class) { + return INT; + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.StringType.class) { + return STRING; + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.BooleanType.class) { + return BOOLEAN; + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.ObjectType.class) { + return OBJECT; + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.PointerType.class) { + return ARRAY; // in java pointer types are used only for arrays + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.FloatingPointType.class) { + return FLOAT; + } else if ((cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.IncompleteType.class && cpgType.getName().getLocalName().equals("void")) + || cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.UnknownType.class) { + return VOID; + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.ParameterizedType.class) { + return VOID; // ToDo: should be changed to UNKNOWN everywhere + } else { + throw new IllegalArgumentException("Unsupported CPG type: " + cpgType); + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Variable.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Variable.java new file mode 100644 index 0000000000..1c2788e6c5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Variable.java @@ -0,0 +1,146 @@ +package de.jplag.java_cpg.ai.variables; + +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.NullValue; +import de.jplag.java_cpg.ai.variables.values.Value; + +/** + * A variable is a named value. + * @author ujiqk + * @version 1.0 + */ +public class Variable { + + private final VariableName name; + private IValue value; + private List changeRecorders = new ArrayList<>(); + + /** + * A variable is a named value. + * @param name the name of this variable. + * @param value the value of this variable. + */ + public Variable(@NotNull VariableName name, @NotNull IValue value) { + this.name = name; + this.value = value; + } + + /** + * A variable is a named value. + * @param string the name of this variable. + * @param value the value of this variable. + */ + public Variable(@NotNull String string, @NotNull IValue value) { + this.name = new VariableName(string); + this.value = value; + } + + /** + * A variable is a named value. + * @param name the name of this variable. + * @param type the type of this variable; the initial value will be created based on the type. + */ + public Variable(@NotNull VariableName name, @NotNull Type type) { + this.name = name; + this.value = Value.valueFactory(type); + } + + /** + * Copy constructor. + * @param variable the variable to deep copy. + */ + public Variable(@NotNull Variable variable) { + this.name = variable.name; + this.value = variable.value.copy(); + this.changeRecorders = variable.changeRecorders; // no deep copy + } + + /** + * @return The name of this variable in the program. + */ + public VariableName getName() { + return name; + } + + /** + * Triggers change recording. + * @return The value of this variable. + */ + public IValue getValue() { + // trigger change recording because returned value could be modified outside. + // ToDo: only trigger when actual changes happen + for (ChangeRecorder changeRecorder : changeRecorders) { + changeRecorder.recordChange(this); + } + return value; + } + + /** + * Set the value of this variable. Triggers change recording. + * @param value the new value for this variable. + */ + public void setValue(IValue value) { + assert value != null; + this.value = value; + for (ChangeRecorder changeRecorder : changeRecorders) { + changeRecorder.recordChange(this); + } + } + + /** + * Merge content from another variable into this variable. The provided variable must have the same name as this + * variable. The actual merge behavior is delegated to the underlying {@link Value} implementation. + * @param value the variable whose content will be merged into this one; must have the same name. + */ + public void merge(@NotNull Variable value) { + assert this.changeRecorders.equals(value.changeRecorders); + assert value.name.equals(this.name); + this.value.merge(value.value); + } + + /** + * Delete all content information in this variable. + */ + public void setToUnknown() { + if (getValue() instanceof NullValue) { + // replace null with unknown JavaObject + setValue(Value.valueFactory(Type.OBJECT)); + } else { + value.setToUnknown(); + } + for (ChangeRecorder changeRecorder : changeRecorders) { + changeRecorder.recordChange(this); + } + } + + /** + * Reset all information in this variable expect type and name. Initial values depend on the type. + */ + public void setInitialValue() { + value.setInitialValue(); + for (ChangeRecorder changeRecorder : changeRecorders) { + changeRecorder.recordChange(this); + } + } + + /** + * @param changeRecorder the change recorder to add. It will be notified on value changes. + */ + public void addChangeRecorder(ChangeRecorder changeRecorder) { + this.changeRecorders.add(changeRecorder); + } + + /** + * @return the last added change recorder. It is removed from this variable. + */ + public ChangeRecorder removeLastChangeRecorder() { + assert !this.changeRecorders.isEmpty(); + return this.changeRecorders.removeLast(); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/VariableName.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/VariableName.java new file mode 100644 index 0000000000..a504165034 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/VariableName.java @@ -0,0 +1,64 @@ +package de.jplag.java_cpg.ai.variables; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a variable name, potentially with a path (e.g., for namespaced or class-scoped variables). Path is + * currently not used in equals and hashCode methods. + * @author ujiqk + * @version 1.0 + */ +public class VariableName { + + private final String localName; + private final String path; + + /** + * Creates a new VariableName by splitting the given name into a path and local name. + * @param name the full variable name, potentially including path segments separated by dots. + */ + public VariableName(@NotNull String name) { + String[] names = name.split("\\."); + if (names.length > 1) { + this.path = String.join(".", java.util.Arrays.copyOfRange(names, 0, names.length - 1)); + this.localName = names[names.length - 1]; + } else { + this.path = ""; + this.localName = name; + } + } + + /** + * @return the local name of the variable (without a path). + */ + public String getLocalName() { + return localName; + } + + /** + * @return the path of the variable. + */ + public String getPath() { + return path; + } + + @Override + public int hashCode() { + return localName.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + + VariableName that = (VariableName) o; + return localName.equals(that.localName); + } + + @Override + public String toString() { + return "VariableName{" + "localName='" + localName + '\'' + ", path='" + path + '\'' + '}'; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/VariableStore.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/VariableStore.java new file mode 100644 index 0000000000..d8e93b760b --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/VariableStore.java @@ -0,0 +1,179 @@ +package de.jplag.java_cpg.ai.variables; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; + +/** + * Stores variables in different scopes. + * @author ujiqk + * @version 1.0 + */ +public class VariableStore { + + private final ArrayList scopes = new ArrayList<>(); + private int currentScopeIndex = 0; + private VariableName thisObject; + + /** + * Copy constructor. Performs deep copy down the values. + * @param variableStore the variable store to copy. + */ + public VariableStore(@NotNull VariableStore variableStore) { + this.currentScopeIndex = variableStore.currentScopeIndex; + for (Scope p : variableStore.scopes) { + this.scopes.add(new Scope(p)); + } + thisObject = variableStore.thisObject; + } + + /** + * Default constructor. Initializes with one {@link Scope}. + */ + public VariableStore() { + scopes.add(new Scope()); + } + + /** + * @param variable the variable to add to the current scope. + */ + public void addVariable(Variable variable) { + scopes.get(currentScopeIndex).addVariable(variable); + } + + /** + * @param thisName the name of the {@link JavaObject} this variable store is associated with. + */ + public void setThisName(VariableName thisName) { + this.thisObject = thisName; + } + + /** + * @return the {@link JavaObject} this variable store is associated with, or null if not set. + */ + public JavaObject getThisObject() { + if (thisObject == null) { + return null; + } + Variable variable = getVariable(thisObject); + if (variable == null) { + return null; + } + IValue value = variable.getValue(); + if (value instanceof JavaObject javaObject) { + return javaObject; + } + return null; + } + + /** + * @param name the name of the variable to retrieve. + * @return the variable with the given name or null if it does not exist. + */ + public Variable getVariable(VariableName name) { + assert name != null; + for (int i = currentScopeIndex; i >= 0; i--) { + Variable variable = scopes.get(i).getVariable(name); + if (variable != null) { + return variable; + } + } + return null; + } + + /** + * @param name the name of the variable to retrieve. + * @return the variable with the given name or null if it does not exist. + */ + public Variable getVariable(String name) { + return getVariable(new VariableName(name)); + } + + /** + * Creates a new scope on top of the current one. + */ + public void newScope() { + scopes.add(new Scope()); + currentScopeIndex++; + } + + /** + * Removes the current scope. + */ + public void removeScope() { + if (currentScopeIndex > 0) { + scopes.remove(currentScopeIndex); + currentScopeIndex--; + } + assert currentScopeIndex >= 0; + } + + /** + * Merges the information from another variable store into this one. Used for merging variable states after control-flow + * joins. Merges scopes up to the minimum scope depth of both stores. + * @param other the other variable store to merge. + */ + public void merge(@NotNull VariableStore other) { + // In complex control-flow, the scope depth can differ. + int targetIndex = Math.min(this.currentScopeIndex, other.currentScopeIndex); + if (this.currentScopeIndex > targetIndex) { + // remove scopes from the end until we match the target index + for (int i = this.currentScopeIndex; i > targetIndex; i--) { + // scopes are always appended at the end; safe to remove by index + scopes.remove(i); + } + this.currentScopeIndex = targetIndex; + } + for (int i = 0; i <= targetIndex; i++) { + Scope thisScope = this.scopes.get(i); + Scope otherScope = other.scopes.get(i); + thisScope.merge(otherScope); + } + } + + /** + * Sets all variables in all scopes to completely unknown. + */ + public void setEverythingUnknown() { + for (Scope scope : scopes) { + scope.setEverythingUnknown(); + } + } + + /** + * Starts recording changes to variables in all scopes. + */ + public void recordChanges() { + ChangeRecorder changeRecorder = new ChangeRecorder(); + for (Scope scope : scopes) { + scope.addChangeRecorder(changeRecorder); + } + } + + /** + * Stops recording changes to variables in all scopes and returns the set of changed variables. Method assumes that + * recordChanges() was called before. + * @return the set of changed variables. + */ + @NotNull + public Set stopRecordingChanges() { + List recorders = new ArrayList<>(); + for (Scope scope : scopes) { + recorders.add(scope.removeLastChangeRecorder()); + } + ChangeRecorder first = recorders.stream().filter(Objects::nonNull).findFirst().orElse(null); + assert recorders.stream().allMatch(r -> (r == first) || (r == null)); + if (first == null) { + return new HashSet<>(); + } + return first.getChangedVariables(); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Arrays.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Arrays.java new file mode 100644 index 0000000000..6480e71497 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Arrays.java @@ -0,0 +1,84 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.arrays.JavaArray; + +/** + * Representation of the static java.util.Arrays class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class Arrays extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.util"; + private static final java.lang.String NAME = "Arrays"; + + /** + * Representation of the static java.util.Arrays class. + */ + public Arrays() { + super(); + } + + /** + * @return The variable name representing java.util.Arrays. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "toString" -> { + assert paramVars.size() == 1; + JavaArray array = (JavaArray) paramVars.getFirst(); + return array.callMethod("toString", List.of(), null); + } + case "fill" -> { // void fill(int[] a, int val) or void fill(int[] a, int fromIndex, int toIndex, int val) + assert paramVars.size() == 2 || paramVars.size() == 4; + JavaArray array = (JavaArray) paramVars.getFirst(); + return array.callMethod("fill", paramVars.subList(1, paramVars.size()), null); + } + case "sort" -> { // void sort(int[] a) or void sort(int[] a, int fromIndex, int toIndex) + assert paramVars.size() == 1 || paramVars.size() == 3 || paramVars.size() == 2; + JavaArray array = (JavaArray) paramVars.getFirst(); + return array.callMethod("sort", paramVars.subList(1, paramVars.size()), null); + } + case "copyOfRange" -> { // int[] copyOfRange(int[] original, int from, int to) + assert paramVars.size() == 3; + JavaArray array = (JavaArray) paramVars.getFirst(); + return array.callMethod("copyOfRange", paramVars.subList(1, paramVars.size()), null); + } + case "asList" -> { // List asList(T... a) + return Value.getNewArayValue(paramVars); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Arrays(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof Arrays; + // nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Boolean.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Boolean.java new file mode 100644 index 0000000000..09b7d3ef0f --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Boolean.java @@ -0,0 +1,70 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * Representation of the static java.lang.Boolean class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class Boolean extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.lang"; + private static final java.lang.String NAME = "Boolean"; + + /** + * Representation of the static java.lang.Boolean class. + */ + public Boolean() { + super(); + } + + /** + * @return The variable name representing java.lang.Boolean. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "parseBoolean" -> { + assert paramVars.size() == 1; + IValue value = paramVars.getFirst(); + switch (value) { + case IStringValue str -> { + return str.callMethod("parseBoolean", paramVars, null); + } + default -> throw new IllegalStateException("Unexpected value: " + value); + } + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Boolean(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof Boolean; + // nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Double.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Double.java new file mode 100644 index 0000000000..e90a1ec41f --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Double.java @@ -0,0 +1,94 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * Representation of the static java.lang.Double class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class Double extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.lang"; + private static final java.lang.String NAME = "Double"; + + /** + * Representation of the static java.lang.Double class. + */ + public Double() { + super(); + } + + /** + * @return Gets the variable name of this object. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "parseDouble", "valueOf" -> { + assert paramVars.size() == 1; + IValue value = paramVars.getFirst(); + switch (value) { + case IStringValue str -> { + return str.callMethod("parseDouble", paramVars, null); + } + case INumberValue num -> { + if (num.getInformation()) { + return Value.getNewFloatValue(num.getValue()); + } else { + return Value.valueFactory(Type.FLOAT); + } + } + case VoidValue _ -> { + return Value.valueFactory(Type.FLOAT); + } + default -> throw new IllegalStateException("Unexpected value: " + value); + } + } + case "compare" -> { + assert paramVars.size() == 2; + IValue first = paramVars.get(0); + IValue second = paramVars.get(1); + if (first instanceof INumberValue && second instanceof INumberValue) { + return first.binaryOperation("compareTo", second); + } else { + throw new IllegalStateException("Unexpected values: " + first + ", " + second); + } + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Double(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof Double; + // nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/HashMap.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/HashMap.java new file mode 100644 index 0000000000..c611b7cff9 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/HashMap.java @@ -0,0 +1,107 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; + +/** + * Representation of the java.util.HashMap class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class HashMap extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.util"; + private static final java.lang.String NAME = "HashMap"; + + /** + * Representation of the java.util.HashMap class. + */ + public HashMap() { + super(); + } + + /** + * @return The variable name representing java.util.HashMap. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + // ToDo: not yet implemented + switch (methodName) { + case "put" -> { + assert paramVars.size() == 2; + return new VoidValue(); + } + case "get" -> { + assert paramVars.size() == 1; + return new VoidValue(); + } + case "containsKey" -> { + assert paramVars.size() == 1; + return new VoidValue(); + } + case "size" -> { + assert paramVars == null || paramVars.size() == 0; + return Value.valueFactory(Type.INT); + } + case "remove" -> { + assert paramVars.size() == 1; + return new VoidValue(); + } + case "putAll" -> { + assert paramVars.size() == 1; + return new VoidValue(); + } + case "clear" -> { + assert paramVars == null || paramVars.size() == 0; + return new VoidValue(); + } + case "keySet" -> { + assert paramVars == null || paramVars.size() == 0; + return new VoidValue(); + } + case "clone" -> { + assert paramVars == null || paramVars.size() == 0; + return new HashMap(); + } + case "entrySet" -> { + assert paramVars == null || paramVars.size() == 0; + return new VoidValue(); + } + case "getOrDefault" -> { + assert paramVars.size() == 2; + return new VoidValue(); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new HashMap(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof HashMap; + // Nothing to merge yet + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/ISpecialObject.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/ISpecialObject.java new file mode 100644 index 0000000000..7ec6d6a284 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/ISpecialObject.java @@ -0,0 +1,10 @@ +package de.jplag.java_cpg.ai.variables.objects; + +/** + * Interface for special Java objects like java.lang.Double. + * @author ujiqk + * @version 1.0 + */ +public interface ISpecialObject { + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/InputStream.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/InputStream.java new file mode 100644 index 0000000000..17a89a9709 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/InputStream.java @@ -0,0 +1,68 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; + +/** + * Representation of the java.io.InputStream class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class InputStream extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.io"; + private static final java.lang.String NAME = "InputStream"; + + /** + * Representation of the java.io.InputStream class. + */ + public InputStream() { + super(); + } + + /** + * @return The variable name representing java.io.InputStream. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "read" -> { + if (paramVars == null || paramVars.isEmpty()) { + return Value.valueFactory(Type.INT); + } else { + throw new UnsupportedOperationException("InputStream.read() with parameters is not supported"); + } + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new InputStream(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof InputStream; + // nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Integer.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Integer.java new file mode 100644 index 0000000000..24c92541f2 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Integer.java @@ -0,0 +1,75 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * Representation of the static java.lang.Integer class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class Integer extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.lang"; + private static final java.lang.String NAME = "Integer"; + + /** + * Representation of the static java.lang.Integer class. + */ + public Integer() { + super(); + } + + /** + * @return The variable name representing java.lang.Integer. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "parseInt" -> { + assert paramVars.size() == 1; + IValue value = paramVars.getFirst(); + switch (value) { + case IStringValue str -> { + return str.callMethod("parseInt", paramVars, null); + } + case VoidValue ignored -> { + return VoidValue.valueFactory(Type.INT); + } + default -> throw new IllegalStateException("Unexpected value: " + value); + } + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Integer(); + } + + @Override + public void merge(@NotNull IValue other) { + // Nothing to merge + assert other instanceof Integer; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Math.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Math.java new file mode 100644 index 0000000000..6972eef490 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Math.java @@ -0,0 +1,115 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +/** + * Representation of the static java.lang.Math class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class Math extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.lang"; + private static final java.lang.String NAME = "Math"; + + /** + * Representation of the static java.lang.Math class. + */ + public Math() { + super(); + } + + /** + * @return The variable name representing java.lang.Math. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "abs" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof INumberValue || paramVars.getFirst() instanceof VoidValue; + return paramVars.getFirst().unaryOperation("abs"); + } + case "min" -> { + assert paramVars.size() == 2; + if (paramVars.getFirst() instanceof VoidValue || paramVars.getLast() instanceof VoidValue) { + return new VoidValue(); + } + assert paramVars.get(0) instanceof INumberValue; + assert paramVars.get(1) instanceof INumberValue; + return paramVars.get(0).binaryOperation("min", paramVars.get(1)); + } + case "max" -> { + assert paramVars.size() == 2; + if (paramVars.getFirst() instanceof VoidValue || paramVars.getLast() instanceof VoidValue) { + return new VoidValue(); + } + assert paramVars.get(0) instanceof INumberValue; + assert paramVars.get(1) instanceof INumberValue || paramVars.get(1) instanceof VoidValue; + return paramVars.get(0).binaryOperation("max", paramVars.get(1)); + } + case "sqrt" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + return new VoidValue(); + } + assert paramVars.getFirst() instanceof INumberValue; + return paramVars.getFirst().unaryOperation("sqrt"); + } + case "pow" -> { + assert paramVars.size() == 2; + if (paramVars.getFirst() instanceof VoidValue || paramVars.getLast() instanceof VoidValue) { + return new VoidValue(); + } + assert paramVars.get(0) instanceof INumberValue; + assert paramVars.get(1) instanceof INumberValue; + return paramVars.get(0).binaryOperation("pow", paramVars.get(1)); + } + case "sin" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + return Value.valueFactory(Type.FLOAT); + } + assert paramVars.getFirst() instanceof INumberValue; + return paramVars.getFirst().unaryOperation("sin"); + } + case "random" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(Type.FLOAT); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Math(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof Math; + // Nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Pattern.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Pattern.java new file mode 100644 index 0000000000..371b858dbe --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Pattern.java @@ -0,0 +1,64 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; + +/** + * Representation of the java.util.regex.Pattern class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class Pattern extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.util.regex"; + private static final java.lang.String NAME = "Pattern"; + + /** + * Representation of the java.util.regex.Pattern class. + */ + public Pattern() { + super(); + } + + /** + * @return The variable name representing java.util.regex.Pattern. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "matches" -> { + assert paramVars.size() == 2; + return new BooleanValue(); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Pattern(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof Pattern; + // Nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/PrintStream.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/PrintStream.java new file mode 100644 index 0000000000..d0d43eb6f0 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/PrintStream.java @@ -0,0 +1,68 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.VoidValue; + +/** + * Representation of the java.io.PrintStream class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class PrintStream extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.io"; + private static final java.lang.String NAME = "PrintStream"; + + /** + * Representation of the java.io.PrintStream class. + */ + public PrintStream() { + super(); + } + + /** + * @return The variable name representing java.io.PrintStream. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "println", "print", "printf" -> { + // do nothing + return new VoidValue(); + } + case "format" -> { + // do nothing & return this + return this; + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new PrintStream(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof PrintStream; + // Nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Random.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Random.java new file mode 100644 index 0000000000..55b0310612 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Random.java @@ -0,0 +1,75 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; + +/** + * Representation of the static java.util.Random class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class Random extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.util"; + private static final java.lang.String NAME = "Random"; + + /** + * Representation of the java.util.Random class. + */ + public Random() { + super(); + } + + /** + * @return The variable name representing java.util.Random. + */ + @Pure + @NotNull + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "nextInt" -> { + if (paramVars == null || paramVars.isEmpty()) { + return Value.valueFactory(Type.INT); + } + assert paramVars.size() == 1; + return Value.valueFactory(Value.valueFactory(0), paramVars.getFirst()); + } + default -> throw new UnsupportedOperationException(methodName + " is not supported in " + PATH + "." + NAME); + } + } + + @Override + public Value accessField(@NotNull java.lang.String fieldName) { + switch (fieldName) { + default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported in " + PATH + "." + NAME); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Random(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof Random; + // Nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Scanner.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Scanner.java new file mode 100644 index 0000000000..4149e01cd7 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Scanner.java @@ -0,0 +1,99 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; + +/** + * Representation of the java.util.Scanner class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class Scanner extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.util"; + private static final java.lang.String NAME = "Scanner"; + + /** + * Creates a new Scanner object representation. + */ + public Scanner() { + super(); + } + + /** + * @return The variable name representing java.util.Scanner. + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "nextLine", "next" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(Type.STRING); + } + case "close" -> { + assert paramVars == null || paramVars.isEmpty(); + return new VoidValue(); + } + case "nextInt", "nextLong" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(Type.INT); + } + case "nextDouble", "nextFloat" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(Type.FLOAT); + } + case "hasNextInt", "hasNext", "hasNextLine" -> { + assert paramVars == null || paramVars.isEmpty() || (paramVars.size() == 1 && paramVars.get(0).getType() == Type.STRING); + return Value.valueFactory(Type.BOOLEAN); + } + case "useLocale" -> { + assert paramVars.size() == 1; + // We don't model Locale, so just return this + return this; + } + case "useDelimiter" -> { + assert paramVars.size() == 1; + // We don't model Pattern, so just return this + return this; + } + default -> throw new UnsupportedOperationException(methodName + " is not supported in Scanner."); + } + } + + @Override + public Value accessField(@NotNull java.lang.String fieldName) { + switch (fieldName) { + default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported in Scanner."); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Scanner(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof Scanner; + // Nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/String.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/String.java new file mode 100644 index 0000000000..68b87a55a5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/String.java @@ -0,0 +1,82 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.string.StringValue; + +/** + * Representation of the static java.lang.String class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class String extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.lang"; + private static final java.lang.String NAME = "String"; + + /** + * Creates a new representation of the java.lang.String class. + */ + public String() { + super(); + } + + /** + * @return The variable name java.lang.String + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "format" -> { + assert !paramVars.isEmpty(); + return new StringValue(); + } + case "join" -> { + assert paramVars.size() >= 2; + assert paramVars.stream().map(StringValue.class::isInstance).reduce(true, (a, b) -> a && b); + // Possibility 1: first delimiter, then strings to join + if (paramVars.stream().allMatch(x -> x instanceof StringValue stringValue && stringValue.getInformation())) { + java.lang.String joinedString = java.lang.String.join(((StringValue) paramVars.get(0)).getValue(), + paramVars.subList(1, paramVars.size()).stream().map(x -> ((StringValue) x).getValue()).toArray(java.lang.String[]::new)); + return new StringValue(joinedString); + } + // ToDo + // Possibility 2: first delimiter, then iterable of strings to join + // Possibility 3: (String prefix, String suffix, String delimiter, String[] elements, int size) + return new StringValue(); + } + case "valueOf" -> { + assert paramVars.size() == 1; + return new StringValue(); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new String(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof String; + // Nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/System.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/System.java new file mode 100644 index 0000000000..b10bee2560 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/System.java @@ -0,0 +1,86 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.string.StringValue; + +/** + * Representation of the static java.lang.System class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class System extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.lang"; + private static final java.lang.String NAME = "System"; + + /** + * Creates a new representation of the java.lang.System class. + */ + public System() { + super(); + } + + /** + * @return The variable name java.lang.System + */ + @Pure + @NotNull + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "lineSeparator" -> { + assert paramVars == null || paramVars.isEmpty(); + return new StringValue("\n"); + } + case "exit" -> { + throw new UnsupportedOperationException("System.exit() called"); + } + case "currentTimeMillis" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(Type.INT); + } + default -> throw new UnsupportedOperationException(methodName + " is not supported in " + PATH + "." + NAME); + } + } + + @Override + public Value accessField(@NotNull java.lang.String fieldName) { + switch (fieldName) { + case "out", "err" -> { + return new PrintStream(); + } + case "in" -> { + return new InputStream(); + } + default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported in " + PATH + "." + NAME); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new System(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof System; + // Nothing to merge + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/BooleanValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/BooleanValue.java new file mode 100644 index 0000000000..53728032b2 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/BooleanValue.java @@ -0,0 +1,177 @@ +package de.jplag.java_cpg.ai.variables.values; + +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * Boolean value representation with a possible lack of information. + * @author ujiqk + * @version 1.0 + */ +public class BooleanValue extends Value implements IBooleanValue { + + private boolean value; + private boolean information; + + /** + * Creates a BooleanValue without exact information. + */ + public BooleanValue() { + super(Type.BOOLEAN); + information = false; + } + + /** + * Creates a BooleanValue with exact information. + * @param value the boolean value + */ + public BooleanValue(boolean value) { + super(Type.BOOLEAN); + this.value = value; + information = true; + } + + private BooleanValue(boolean value, boolean information) { + super(Type.BOOLEAN); + this.value = value; + this.information = information; + } + + /** + * @return whether exact information is available + */ + public boolean getInformation() { + return information; + } + + /** + * Assumes that exact information is available. + * @return the boolean value. + */ + public boolean getValue() { + assert information; + return value; + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (other instanceof VoidValue) { + return new BooleanValue(); + } + if (other instanceof IStringValue stringValue && operator.equals("+")) { + if (this.getInformation()) { + return stringValue.binaryOperation(operator, other); + } else { + return Value.valueFactory(Type.STRING); + } + } + BooleanValue otherBool = (BooleanValue) other; + switch (operator) { + case "||" -> { + if (this.getInformation() && otherBool.getInformation()) { + return new BooleanValue(this.getValue() || otherBool.getValue()); + } else { + return new BooleanValue(); + } + } + case "&&" -> { + if (this.getInformation() && otherBool.getInformation()) { + return new BooleanValue(this.getValue() && otherBool.getValue()); + } else { + return new BooleanValue(); + } + } + case "==" -> { + if (this.getInformation() && otherBool.getInformation()) { + return new BooleanValue(this.getValue() == otherBool.getValue()); + } else { + return new BooleanValue(); + } + } + case "!=" -> { + if (this.getInformation() && otherBool.getInformation()) { + return new BooleanValue(this.getValue() != otherBool.getValue()); + } else { + return new BooleanValue(); + } + } + case "&" -> { + if (this.getInformation() && otherBool.getInformation()) { + return new BooleanValue(this.getValue() & otherBool.getValue()); + } else { + return new BooleanValue(); + } + } + default -> throw new UnsupportedOperationException( + "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + } + + @Pure + @Override + public Value unaryOperation(@NotNull String operator) { + switch (operator) { + case "!" -> { + if (information) { + return new BooleanValue(!value); + } else { + return new BooleanValue(); + } + } + default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType()); + } + } + + @NotNull + @Override + public Value copy() { + return new BooleanValue(value, information); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + this.information = false; + return; + } + if (other instanceof INumberValue numberValue) { // 1 and 0 to boolean conversion + if (this.information && numberValue.getInformation()) { + if (numberValue.getValue() == 0) { + if (this.value) { + this.information = false; + } + } else { + if (!this.value) { + this.information = false; + } + } + } else { + this.information = false; + } + return; + } + assert other instanceof BooleanValue; + BooleanValue otherBool = (BooleanValue) other; + if (this.information && otherBool.information && this.value == otherBool.value) { + // keep information + } else { + this.information = false; + } + } + + @Override + public void setToUnknown() { + this.information = false; + } + + @Override + public void setInitialValue() { + value = false; + information = true; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/FunctionValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/FunctionValue.java new file mode 100644 index 0000000000..9705a2bc58 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/FunctionValue.java @@ -0,0 +1,44 @@ +package de.jplag.java_cpg.ai.variables.values; + +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; + +// ToDo: not currently used, needed for lambda functions etc. + +/** + * Represents a function. Is used in lambdas. + * @author ujiqk + * @version 1.0 + */ +public class FunctionValue extends Value { + + /** + * a FunctionValue with no information. + */ + public FunctionValue() { + super(Type.FUNCTION); + } + + @NotNull + @Override + public Value copy() { + return new FunctionValue(); + } + + @Override + public void merge(@NotNull IValue other) { + // ToDo + } + + @Override + public void setToUnknown() { + // ToDo + } + + @Override + public void setInitialValue() { + // ToDo + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IBooleanValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IBooleanValue.java new file mode 100644 index 0000000000..992cce05c5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IBooleanValue.java @@ -0,0 +1,20 @@ +package de.jplag.java_cpg.ai.variables.values; + +/** + * Boolean value representations. + * @author ujiqk + * @version 1.0 + */ +public interface IBooleanValue { + + /** + * @return if the boolean value is known. + */ + boolean getInformation(); + + /** + * @return the boolean value if it is known. + */ + boolean getValue(); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IJavaObject.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IJavaObject.java new file mode 100644 index 0000000000..3b9786b5e4 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IJavaObject.java @@ -0,0 +1,54 @@ +package de.jplag.java_cpg.ai.variables.values; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.AbstractInterpretation; +import de.jplag.java_cpg.ai.variables.Variable; + +/** + * Interface for Java object representations in the abstract interpretation. + * @author ujiqk + * @version 1.0 + */ +public interface IJavaObject extends IValue { + + /** + * Calls a method on this object. The method is performed inside the abstract interpretation of this object. + * @param methodName the name of the method to call. + * @param paramVars the parameters to pass to the method. + * @param method the cpg method declaration node. + * @return the return value of the method. + */ + IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method); + + /** + * Accesses a field of this object. + * @param fieldName the name of the field to access. + * @return the value of the field. + */ + IValue accessField(@NotNull String fieldName); + + /** + * Changes a field of this object. + * @param fieldName the name of the field to change. + * @param value the new value of the field. + */ + void changeField(@NotNull String fieldName, IValue value); + + /** + * Sets the variable representing this object field. + * @param field the variable representing this object field. + */ + void setField(@NotNull Variable field); + + /** + * Sets the abstract interpretation this object belongs to. + * @param abstractInterpretation the abstract interpretation this object belongs to. + */ + void setAbstractInterpretation(@Nullable AbstractInterpretation abstractInterpretation); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IValue.java new file mode 100644 index 0000000000..24e0d4708e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IValue.java @@ -0,0 +1,91 @@ +package de.jplag.java_cpg.ai.variables.values; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.arrays.IJavaArray; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +import kotlin.Pair; + +/** + * Interface for all values. + * @author ujiqk + * @version 1.0 + */ +public interface IValue { + + /** + * @return the type of this value. + */ + @NotNull + Type getType(); + + /** + * Performs a binary operation between this value and another value. + * @param operator the operator. + * @param other the other value. + * @return the result value. VoidValue if the operation does not return a value. + * @throws UnsupportedOperationException if the operation is not supported between the two value types. + */ + IValue binaryOperation(@NotNull String operator, @NotNull IValue other); + + /** + * Performs a unary operation on this value. + * @param operator the operator. + * @return the result value. VoidValue if the operation does not return a value. + * @throws IllegalArgumentException if the operation is not supported for this value type. + */ + IValue unaryOperation(@NotNull String operator); + + /** + * Creates and returns a deep copy of this value. + * @return a deep copy of this value. + */ + @NotNull + IValue copy(); + + /** + * Merges the information of another instance of the same value into this one. Types should be the same. For example, + * when a value has different content in different branches of an if statement. + * @param other other value. + */ + void merge(@NotNull IValue other); + + /** + * Delete all information in this value. + */ + void setToUnknown(); + + /** + * Resets all information about this value except its type. The initial value depends on the specific value type. + */ + void setInitialValue(); + + /** + * {@link #setParentObject(IJavaObject)} must be called before to use this method. + * @return the parent object of this value. Can be null. + */ + IJavaObject getParentObject(); + + /** + * Sets the parent object of this value. Must be called before some filed accesses. + * @param parentObject the parent object. Can be null. + */ + void setParentObject(@Nullable IJavaObject parentObject); + + /** + * {@link #setArrayPosition(IJavaArray, INumberValue)} must be called before to use this method. + * @return the position of this value in the array that contains it. + */ + Pair getArrayPosition(); + + /** + * Sets the position of this value in the array that contains it. Necessary to set before array assignments. + * @param array the array that contains this value. + * @param index the index of this value in the array. + */ + void setArrayPosition(IJavaArray array, INumberValue index); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/JavaObject.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/JavaObject.java new file mode 100644 index 0000000000..999bc739ca --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/JavaObject.java @@ -0,0 +1,196 @@ +package de.jplag.java_cpg.ai.variables.values; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.AbstractInterpretation; +import de.jplag.java_cpg.ai.variables.Scope; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.Variable; +import de.jplag.java_cpg.ai.variables.VariableName; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; +import de.jplag.java_cpg.ai.variables.values.string.StringValue; + +/** + * A Java object instance in the abstract interpretation. All big data types are also objects (arrays, collections, + * maps, etc.). + * @author ujiqk + * @version 1.0 + */ +public class JavaObject extends Value implements IJavaObject { + + // ToDo: save type of the object (class name) + @Nullable + private Scope fields; // fields == null => object null + @Nullable + private AbstractInterpretation abstractInterpretation; // the abstract interpretation engine for this object + + private JavaObject(@Nullable Scope fields, @Nullable AbstractInterpretation abstractInterpretation) { + super(Type.OBJECT); + this.fields = fields; + this.abstractInterpretation = abstractInterpretation; + } + + /** + * Constructor for a Java object with an abstract interpretation engine and no info. + * @param abstractInterpretation the abstract interpretation engine where methods will be executed. + */ + public JavaObject(@NotNull AbstractInterpretation abstractInterpretation) { + super(Type.OBJECT); + this.fields = new Scope(); + this.abstractInterpretation = abstractInterpretation; + abstractInterpretation.setRelatedObject(this); + } + + /** + * Default constructor for a Java object with no abstract interpretation engine and no info. + */ + public JavaObject() { + super(Type.OBJECT); + this.fields = new Scope(); + } + + /** + * Internal constructor for special classes like arrays. + * @param type the type of the object. + */ + protected JavaObject(Type type) { + super(type); + this.fields = new Scope(); + } + + /** + * @param methodName the name of the method to call. + * @param paramVars the parameters to pass to the method. + * @param method the cpg method declaration of the method to call. + * @return null if the method is not known. + */ + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) { + if (abstractInterpretation == null) { + return new VoidValue(); + } + return abstractInterpretation.runMethod(methodName, paramVars, method); + } + + /** + * @param fieldName the name of the field to access. + * @return the value of the field or VoidValue if the field does not exist. + */ + public IValue accessField(@NotNull String fieldName) { + Variable result = fields.getVariable(new VariableName(fieldName)); + if (result == null) { + return new VoidValue(); + } + return result.getValue(); + } + + /** + * Changes the value of an existing field variable in this object. If the field does not exist, it will be created. + * @param fieldName the name of the field to change. + * @param value the new value of the field. + */ + public void changeField(@NotNull String fieldName, IValue value) { + Variable variable = fields.getVariable(new VariableName(fieldName)); + if (variable == null) { + fields.addVariable(new Variable(fieldName, value)); + return; + } + variable.setValue(value); + } + + /** + * @param field Sets a new field variable in this object. + */ + public void setField(@NotNull Variable field) { + this.fields.addVariable(field); + } + + /** + * Sets the abstract interpretation engine for this object. If you call methods on this object, this engine will be used + * for execution. + * @param abstractInterpretation the abstract interpretation engine or null. + */ + public void setAbstractInterpretation(@Nullable AbstractInterpretation abstractInterpretation) { + this.abstractInterpretation = abstractInterpretation; + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + switch (operator) { + case "==" -> { + if (this.equals(other)) { + return new BooleanValue(true); + } else { + return new BooleanValue(); + } + } + case "!=" -> { + if (this.equals(other)) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + case "+" -> { + if (other instanceof IStringValue stringValue) { + // case: JavaObject with toString method + IValue toStringResult = this.callMethod("toString", List.of(), null); + if (toStringResult instanceof IStringValue stringFromObject && stringValue.getInformation() + && stringFromObject.getInformation()) { + return new StringValue(stringValue.getValue() + stringFromObject.getValue()); + } + return new StringValue(); + } else { + throw new UnsupportedOperationException( + "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + } + case "instanceof" -> { + return new BooleanValue(); + } + default -> throw new UnsupportedOperationException( + "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + } + + @NotNull + @Override + public JavaObject copy() { + if (fields == null) { + return new JavaObject(null, this.abstractInterpretation); + } + return new JavaObject(new Scope(this.fields), this.abstractInterpretation); + } + + @Override + public void merge(@NotNull IValue other) { + if (!(other instanceof JavaObject)) { // cannot merge different types + setToUnknown(); + return; + } + if (fields == null || ((JavaObject) other).fields == null) { + fields = new Scope(); + return; + } + this.fields.merge(((JavaObject) other).fields); + } + + @Override + public void setToUnknown() { + if (fields != null) { + fields.setEverythingUnknown(); + } + } + + @Override + public void setInitialValue() { + if (fields != null) { + fields.setEverythingUnknown(); + } + fields = null; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/NullValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/NullValue.java new file mode 100644 index 0000000000..48e75a4628 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/NullValue.java @@ -0,0 +1,55 @@ +package de.jplag.java_cpg.ai.variables.values; + +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; + +/** + * Represents the Java null value. + * @author ujiqk + * @version 1.0 + */ +public class NullValue extends Value { + + /** + * Constructs a new NullValue. + */ + public NullValue() { + super(Type.NULL); + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + switch (operator) { + case "==" -> { + return new BooleanValue(other instanceof NullValue); + } + case "!=" -> { + return new BooleanValue(!(other instanceof NullValue)); + } + default -> throw new UnsupportedOperationException("Operator " + operator + " not supported for NullValue."); + } + } + + @NotNull + @Override + public Value copy() { + return new NullValue(); + } + + @Override + public void merge(@NotNull IValue other) { + // do nothing + } + + @Override + public void setToUnknown() { + // ToDo: replace with JavaObject? + } + + @Override + public void setInitialValue() { + // do nothing + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/Value.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/Value.java new file mode 100644 index 0000000000..b02bfa7fb8 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/Value.java @@ -0,0 +1,444 @@ +package de.jplag.java_cpg.ai.variables.values; + +import java.util.List; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import de.jplag.java_cpg.ai.ArrayAiType; +import de.jplag.java_cpg.ai.CharAiType; +import de.jplag.java_cpg.ai.FloatAiType; +import de.jplag.java_cpg.ai.IntAiType; +import de.jplag.java_cpg.ai.StringAiType; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.arrays.IJavaArray; +import de.jplag.java_cpg.ai.variables.values.arrays.JavaArray; +import de.jplag.java_cpg.ai.variables.values.arrays.JavaLengthArray; +import de.jplag.java_cpg.ai.variables.values.chars.CharSetValue; +import de.jplag.java_cpg.ai.variables.values.chars.CharValue; +import de.jplag.java_cpg.ai.variables.values.numbers.FloatSetValue; +import de.jplag.java_cpg.ai.variables.values.numbers.FloatValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.ai.variables.values.numbers.IntIntervalValue; +import de.jplag.java_cpg.ai.variables.values.numbers.IntSetValue; +import de.jplag.java_cpg.ai.variables.values.numbers.IntValue; +import de.jplag.java_cpg.ai.variables.values.string.StringCharInclValue; +import de.jplag.java_cpg.ai.variables.values.string.StringRegexValue; +import de.jplag.java_cpg.ai.variables.values.string.StringValue; + +import kotlin.Pair; + +/** + * Abstract super class for all values. + *

+ * Also contains factory methods to create Value instances based on the configured AI types. + * @author ujiqk + * @version 1.0 + */ +public abstract class Value implements IValue { + + private static IntAiType usedIntAiType = IntAiType.DEFAULT; + private static FloatAiType usedFloatAiType = FloatAiType.DEFAULT; + private static StringAiType usedStringAiType = StringAiType.DEFAULT; + private static CharAiType usedCharAiType = CharAiType.DEFAULT; + private static ArrayAiType usedArrayAiType = ArrayAiType.DEFAULT; + + @NotNull + private final Type type; + @Nullable + private Pair arrayPosition; // necessary for an array assign to work + @Nullable + private IJavaObject parentObject; // necessary for some field access + + protected Value(@NotNull Type type) { + this.type = type; + } + + /** + * The default is {@link IntAiType#DEFAULT}. + * @param intAiType the type to use for integer values. + */ + public static void setUsedIntAiType(@NotNull IntAiType intAiType) { + usedIntAiType = intAiType; + } + + /** + * The default is {@link FloatAiType#DEFAULT}. + * @param floatAiType the type to use for float values. + */ + public static void setUsedFloatAiType(@NotNull FloatAiType floatAiType) { + usedFloatAiType = floatAiType; + } + + /** + * The default is {@link StringAiType#DEFAULT}. + * @param stringAiType the type to use for string values. + */ + public static void setUsedStringAiType(@NotNull StringAiType stringAiType) { + usedStringAiType = stringAiType; + } + + /** + * The default is {@link CharAiType#DEFAULT}. + * @param charAiType the type to use for char values. + */ + public static void setUsedCharAiType(@NotNull CharAiType charAiType) { + usedCharAiType = charAiType; + } + + /** + * The default is {@link ArrayAiType#DEFAULT}. + * @param arrayAiType the type to use for array values. + */ + public static void setUsedArrayAiType(@NotNull ArrayAiType arrayAiType) { + usedArrayAiType = arrayAiType; + } + + // ------------------ Value Factories ------------------// + + /** + * Constructs a Value instance based on the provided type. + * @param type the type of the value. + * @return a Value instance corresponding to the specified type. + * @throws IllegalArgumentException if the type is unsupported. + */ + @NotNull + public static IValue valueFactory(@NotNull Type type) { + return switch (type) { + case INT -> getNewIntValue(); + case STRING -> getNewStringValue(); + case BOOLEAN -> new BooleanValue(); + case OBJECT -> new JavaObject(); + case VOID -> new VoidValue(); + case ARRAY, LIST -> getNewArayValue(); + case NULL -> new NullValue(); + case FLOAT -> getNewFloatValue(); + case FUNCTION -> new FunctionValue(); + case CHAR -> getNewCharValue(); + default -> throw new IllegalArgumentException("Unsupported type: " + type); + }; + } + + /** + * Value factory for when a value is known. + * @param value the known value. + * @return a {@link Value} instance representing the known value. + * @throws IllegalStateException if the value type is unsupported. + */ + @NotNull + public static IValue valueFactory(@Nullable Object value) { + if (value == null) { + return new NullValue(); + } + switch (value) { + case String s -> { + return getNewStringValue(s); + } + case Integer i -> { + return getNewIntValue(i); + } + case Boolean b -> { + return new BooleanValue(b); + } + case Double d -> { + return getNewFloatValue(d); + } + case Long l -> { // all integer numbers are treated as int + assert l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE; + return getNewIntValue(l.intValue()); + } + case Character c -> { + return getNewCharValue(c); + } + default -> throw new IllegalStateException("Unexpected value: " + value); + } + } + + /** + * Value factory for when a value could have multiple possible values. + * @param values the possible values. + * @param The type of the values. + * @return a {@link Value} instance representing the possible values. + * @throws IllegalStateException if the set of values contains unsupported types. + */ + @NotNull + @SuppressWarnings("unchecked") + public static IValue valueFactory(@NotNull Set values) { + assert !values.isEmpty(); + Object first = values.iterator().next(); + return switch (first) { + case String _ -> getNewStringValue((Set) values); + case Integer _ -> getNewIntValue((Set) values); + case Double _ -> getNewFloatValue((Set) values); + case Character _ -> getNewCharValue((Set) values); + default -> throw new IllegalStateException("Unexpected value type in list: " + first.getClass()); + }; + } + + /** + * Value factory for when a value has lower/upper bounds. + * @param lowerBound the lower bound. + * @param upperBound the upper bound. + * @param The type of the bounds. + * @return a {@link Value} instance representing the bounded value. + * @throws IllegalStateException if the bound types are unsupported. + */ + @NotNull + public static IValue valueFactory(@NotNull T lowerBound, @NotNull T upperBound) { + return switch (lowerBound) { + case String _ -> throw new IllegalStateException("Strings dont have bounds"); + case Integer _ -> getNewIntValue((int) lowerBound, (int) upperBound); + case Double _ -> getNewFloatValue((double) lowerBound, (double) upperBound); + default -> throw new IllegalStateException("Unexpected value type in bound : " + lowerBound.getClass()); + }; + } + + /** + * Creates a new integer value based on the configured AI type. + * @return a new integer value instance. + */ + @NotNull + public static INumberValue getNewIntValue() { + return switch (usedIntAiType) { + case INTERVALS -> new IntIntervalValue(); + case DEFAULT -> new IntValue(); + case SET -> new IntSetValue(); + }; + } + + @NotNull + private static Value getNewIntValue(int number) { + return switch (usedIntAiType) { + case INTERVALS -> new IntIntervalValue(number); + case DEFAULT -> new IntValue(number); + case SET -> new IntSetValue(number); + }; + } + + @NotNull + private static Value getNewIntValue(@NotNull Set possibleNumbers) { + return switch (usedIntAiType) { + case INTERVALS -> new IntIntervalValue(possibleNumbers); + case DEFAULT -> new IntValue(possibleNumbers); + case SET -> new IntSetValue(possibleNumbers); + }; + } + + @NotNull + private static Value getNewIntValue(int lowerBound, int upperBound) { + return switch (usedIntAiType) { + case INTERVALS -> new IntIntervalValue(lowerBound, upperBound); + case DEFAULT -> new IntValue(lowerBound, upperBound); + case SET -> new IntSetValue(lowerBound, upperBound); + }; + } + + @NotNull + private static Value getNewFloatValue() { + return switch (usedFloatAiType) { + case DEFAULT -> new FloatValue(); + case SET -> new FloatSetValue(); + }; + } + + /** + * Creates a new float value based on the configured AI type. + * @param number the float number. + * @return a new float value instance. + */ + @NotNull + public static Value getNewFloatValue(double number) { + return switch (usedFloatAiType) { + case DEFAULT -> new FloatValue(number); + case SET -> new FloatSetValue(number); + }; + } + + @NotNull + private static Value getNewFloatValue(@NotNull Set possibleNumbers) { + return switch (usedFloatAiType) { + case DEFAULT -> new FloatValue(possibleNumbers); + case SET -> new FloatSetValue(possibleNumbers); + }; + } + + @NotNull + private static Value getNewFloatValue(double lowerBound, double upperBound) { + return switch (usedFloatAiType) { + case DEFAULT -> new FloatValue(lowerBound, upperBound); + case SET -> new FloatSetValue(lowerBound, upperBound); + }; + } + + @NotNull + private static Value getNewStringValue() { + return switch (usedStringAiType) { + case DEFAULT -> new StringValue(); + case CHAR_INCLUSION -> new StringCharInclValue(); + case REGEX -> new StringRegexValue(); + }; + } + + @NotNull + private static Value getNewStringValue(String value) { + return switch (usedStringAiType) { + case DEFAULT -> new StringValue(value); + case CHAR_INCLUSION -> new StringCharInclValue(value); + case REGEX -> new StringRegexValue(value); + }; + } + + @NotNull + private static Value getNewStringValue(@NotNull Set values) { + return switch (usedStringAiType) { + case DEFAULT -> new StringValue(); + case CHAR_INCLUSION -> new StringCharInclValue(values); + case REGEX -> new StringRegexValue(values); + }; + } + + @NotNull + private static Value getNewCharValue() { + return switch (usedCharAiType) { + case DEFAULT -> new CharValue(); + case SET -> new CharSetValue(); + }; + } + + @NotNull + private static Value getNewCharValue(char character) { + return switch (usedCharAiType) { + case DEFAULT -> new CharValue(character); + case SET -> new CharSetValue(character); + }; + } + + @NotNull + private static Value getNewCharValue(@NotNull Set characters) { + return switch (usedCharAiType) { + case DEFAULT -> new CharValue(characters); + case SET -> new CharSetValue(characters); + }; + } + + @NotNull + private static Value getNewArayValue() { + return switch (usedArrayAiType) { + case DEFAULT -> new JavaArray(); + case LENGTH -> new JavaLengthArray(); + }; + } + + /** + * Creates a new array value with the specified inner type. + * @param innerType the type of the elements in the array. + * @return a new array value instance. + */ + @NotNull + public static IJavaArray getNewArayValue(Type innerType) { + return switch (usedArrayAiType) { + case DEFAULT -> new JavaArray(innerType); + case LENGTH -> new JavaLengthArray(innerType); + }; + } + + /** + * Creates a new array value with the specified values. + * @param values the values to initialize the array with. + * @return a new array value instance. + */ + @NotNull + public static IJavaArray getNewArayValue(List values) { + return switch (usedArrayAiType) { + case DEFAULT -> new JavaArray(values); + case LENGTH -> new JavaLengthArray(values); + }; + } + + /** + * Creates a new array value with the specified inner type and length. + * @param innerType the type of the elements in the array. + * @param length the length of the array. + * @return a new array value instance. + */ + @NotNull + public static IJavaArray getNewArayValue(Type innerType, INumberValue length) { + return switch (usedArrayAiType) { + case DEFAULT -> new JavaArray(length, innerType); + case LENGTH -> new JavaLengthArray(innerType, length); + }; + } + + // ------------------ End of Value Factories ------------------// + + /** + * @return the type of this value. + */ + @NotNull + public Type getType() { + return type; + } + + /** + * Performs a binary operation between this value and another value. + * @param operator the operator. + * @param other the other value. + * @return the result value. VoidValue if the operation does not return a value. + * @throws UnsupportedOperationException if the operation is not supported between the two value types. + */ + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + throw new UnsupportedOperationException("Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + + /** + * Performs a unary operation on this value. + * @param operator the operator. + * @return the result value. VoidValue if the operation does not return a value. + * @throws IllegalArgumentException if the operation is not supported for this value type. + */ + public IValue unaryOperation(@NotNull String operator) { + switch (operator) { + case "throw" -> { + return new VoidValue(); + } + default -> throw new IllegalArgumentException("Unary operation " + operator + " not supported for " + getType()); + } + } + + /** + * {@link #setParentObject(IJavaObject)} must be called before to use this method. + * @return the parent object of this value. Can be null. + */ + @Nullable + public IJavaObject getParentObject() { + return parentObject; + } + + /** + * Sets the parent object of this value. Must be called before some filed accesses. + * @param parentObject the parent object. Can be null. + */ + public void setParentObject(@Nullable IJavaObject parentObject) { + this.parentObject = parentObject; + } + + /** + * {@link #setArrayPosition(IJavaArray, INumberValue)} must be called before to use this method. + * @return the position of this value in the array that contains it. + */ + public Pair getArrayPosition() { + assert arrayPosition != null; + return arrayPosition; + } + + /** + * Sets the position of this value in the array that contains it. Necessary to set before array assignments. + * @param array the array that contains this value. + * @param index the index of this value in the array. + */ + public void setArrayPosition(@NotNull IJavaArray array, @NotNull INumberValue index) { + this.arrayPosition = new Pair<>(array, index); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/VoidValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/VoidValue.java new file mode 100644 index 0000000000..6fb8616e7d --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/VoidValue.java @@ -0,0 +1,79 @@ +package de.jplag.java_cpg.ai.variables.values; + +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.numbers.FloatValue; +import de.jplag.java_cpg.ai.variables.values.numbers.IntValue; +import de.jplag.java_cpg.ai.variables.values.string.StringValue; + +/** + * Void typed value. Represents no value or completely unknown value. + * @author ujiqk + * @version 1.0 + */ +public class VoidValue extends Value { + + /** + * Creates a new Void typed value. Represents no value or completely unknown value. + */ + public VoidValue() { + super(Type.VOID); + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + switch (operator) { + case "==", ">", "<", ">=", "<=", "!=" -> { + return new BooleanValue(); + } + case "+", "-", "*", "/" -> { + return switch (other) { + case IntValue ignored -> new IntValue(); + case FloatValue ignored -> new FloatValue(); + case StringValue ignored -> new StringValue(); + default -> new VoidValue(); + }; + } + case "&", "^" -> { + return new VoidValue(); + } + default -> throw new UnsupportedOperationException("Operator " + operator + " not supported for VoidValue."); + } + } + + @Override + public IValue unaryOperation(@NotNull String operator) { + switch (operator) { + case "!" -> { + return new BooleanValue(); + } + case "--", "++", "abs", "+", "-" -> { + return new VoidValue(); + } + default -> throw new IllegalArgumentException("Unary operation " + operator + " not supported for " + getType()); + } + } + + @NotNull + @Override + public Value copy() { + return new VoidValue(); + } + + @Override + public void merge(@NotNull IValue other) { + // do nothing + } + + @Override + public void setToUnknown() { + // do nothing + } + + @Override + public void setInitialValue() { + // nothing + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/IJavaArray.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/IJavaArray.java new file mode 100644 index 0000000000..73284d5a0d --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/IJavaArray.java @@ -0,0 +1,27 @@ +package de.jplag.java_cpg.ai.variables.values.arrays; + +import de.jplag.java_cpg.ai.variables.values.IJavaObject; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +/** + * Also needs to extend JavaObject because Java arrays are objects. + * @author ujiqk + */ +public interface IJavaArray extends IJavaObject { + + /** + * Access an array element at the given index. + * @param index The index to access. + * @return The value at the given index. + */ + IValue arrayAccess(INumberValue index); + + /** + * Assign a value to an array element at the given index. + * @param index The index to assign to. + * @param value The value to assign. + */ + void arrayAssign(INumberValue index, IValue value); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaArray.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaArray.java new file mode 100644 index 0000000000..e8eb4d4080 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaArray.java @@ -0,0 +1,534 @@ +package de.jplag.java_cpg.ai.variables.values.arrays; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IJavaObject; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.ai.variables.values.string.StringValue; + +/** + * A Java Array representation. Java arrays are objects. Lists are modeled as Java arrays. + * @author ujiqk + * @version 1.0 + */ +public class JavaArray extends JavaObject implements IJavaArray { + + private Type innerType; + @Nullable + private List values; // values = null: no information about the array + + /** + * a Java Array with no information and undefined size. + * @param innerType the type of the array elements. + */ + public JavaArray(Type innerType) { + super(Type.ARRAY); + this.innerType = innerType; + } + + /** + * a Java Array with exact information. + * @param values the values of the array in the correct order. + */ + public JavaArray(@NotNull List values) { + super(Type.ARRAY); + if (values.isEmpty()) { + this.innerType = null; + this.values = values; + return; + } + assert values.stream().map(IValue::getType).distinct().count() == 1; + this.innerType = values.getFirst().getType(); + this.values = values; + } + + /** + * a Java Array with no information and undefined size. + */ + public JavaArray() { + super(Type.ARRAY); + this.innerType = null; + } + + /** + * a Java Array with exact length and type information. + * @param length the length of the array; must contain information. + * @param innerType the type of the array elements. + */ + public JavaArray(@NotNull INumberValue length, Type innerType) { + super(Type.ARRAY); + this.innerType = innerType; + if (length.getInformation()) { + int len = Math.max(0, (int) length.getValue()); + values = new ArrayList<>(len); + Value placeholder = new VoidValue(); + for (int i = 0; i < len; i++) { + values.add(placeholder.copy()); + } + } + } + + private JavaArray(@Nullable Type innerType, @Nullable List values) { + super(Type.ARRAY); + this.innerType = innerType; + this.values = values; + } + + /** + * Access an element of the array. + * @param index the index to access; does not have to contain information. + * @return the superset of possible values at the given indexes. + * @throws UnsupportedOperationException if the inner type is not supported. + */ + public IValue arrayAccess(INumberValue index) { + if (values != null && index.getInformation()) { + int idx = (int) index.getValue(); + if (idx >= 0 && idx < values.size()) { + return values.get(idx); + } + } + // if no information, return an unknown value of the inner type + if (innerType == null) { + return new VoidValue(); + } + return switch (innerType) { + case INT -> Value.valueFactory(Type.INT); + case BOOLEAN -> new BooleanValue(); + case STRING -> Value.valueFactory(Type.STRING); + case OBJECT -> new JavaObject(); + case ARRAY, LIST -> new JavaArray(); + case FLOAT -> Value.valueFactory(Type.FLOAT); + case CHAR -> Value.valueFactory(Type.CHAR); + default -> throw new UnsupportedOperationException("Array of type " + innerType + " not supported"); + }; + } + + /** + * Assign a value to a position in the array. + * @param index the index to assign to; does not have to contain information. + * @param value the value to assign. + */ + public void arrayAssign(INumberValue index, IValue value) { + if (values != null && index.getInformation()) { + int idx = (int) index.getValue(); + if (idx >= 0 && idx < values.size()) { + values.set(idx, value); + } + } else { + // no information about the array, set to unknown + values = null; + } + } + + @Override + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "toString" -> { + assert paramVars == null || paramVars.isEmpty(); + return new StringValue(); + } + case "add" -> { + if (paramVars.size() == 1) { + if (values != null) { + assert paramVars.getFirst().getType().equals(innerType); + values.add(paramVars.getFirst()); + } + return new VoidValue(); + } else if (paramVars.size() == 2) { // index, element + if (values != null) { + assert paramVars.getFirst() instanceof INumberValue; + INumberValue index = (INumberValue) paramVars.getFirst(); + if (index.getInformation()) { + int idx = (int) index.getValue(); + if (idx >= 0 && idx <= values.size()) { + assert paramVars.getLast().getType().equals(innerType); + values.add(idx, paramVars.getLast()); + } else { + values = null; // no information + } + } else { + values = null; // no information + } + } + return new VoidValue(); + } else { + throw new UnsupportedOperationException("add with " + paramVars.size() + " parameters is not supported"); + } + } + case "stream" -> { + assert paramVars == null || paramVars.isEmpty(); + return this; + } + case "size" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null) { + return Value.valueFactory(values.size()); + } + return Value.valueFactory(Type.INT); + } + case "map" -> { + // ToDo + return this; + } + case "max" -> { + // ToDo + if (innerType == Type.INT) { + return Value.valueFactory(Type.INT); + } else if (innerType == Type.FLOAT) { + return Value.valueFactory(Type.FLOAT); + } else { + return new VoidValue(); + } + } + case "indexOf" -> { + assert paramVars.size() == 1; + if (values != null) { + for (int i = 0; i < values.size(); i++) { + if (values.get(i).equals(paramVars.getFirst())) { + return Value.valueFactory(i); + } + } + return Value.valueFactory(-1); + } + return Value.valueFactory(Type.INT); + } + case "remove" -> { + if (paramVars == null || paramVars.isEmpty()) { // remove head + return this.callMethod("removeFirst", null, method); + } + assert paramVars.size() == 1; + if (values == null) { + return new VoidValue(); + } + // either remove(int index) or remove(Object o) -> ToDo: cannot distinguish with Integer parameter + if (paramVars.getFirst() instanceof INumberValue number) { + if (number.getInformation()) { + return values.remove((int) number.getValue()); + } + return new VoidValue(); + } else { + for (int i = 0; i < values.size(); i++) { + if (values.get(i).equals(paramVars.getFirst())) { + values.remove(i); + return Value.valueFactory(true); + } + } + return Value.valueFactory(false); + } + } + case "get", "elementAt" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(Type.INT)); + } + assert paramVars.getFirst() instanceof INumberValue; + return arrayAccess((INumberValue) paramVars.getFirst()); + } + case "contains" -> { + assert paramVars.size() == 1; + if (values != null) { + for (IValue value : values) { + if (value.equals(paramVars.getFirst())) { + return Value.valueFactory(true); + } + } + return Value.valueFactory(false); + } + return Value.valueFactory(Type.BOOLEAN); + } + case "lastIndexOf" -> { + assert paramVars.size() == 1; + if (values != null) { + for (int i = values.size() - 1; i >= 0; i--) { + if (values.get(i).equals(paramVars.getFirst())) { + return Value.valueFactory(i); + } + } + return Value.valueFactory(-1); + } + return Value.valueFactory(Type.INT); + } + case "getLast", "peek" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null && !values.isEmpty()) { + return values.getLast(); + } + // no information + if (innerType == null) { + return new VoidValue(); + } + return arrayAccess((INumberValue) Value.valueFactory(1)); + } + case "removeLast", "pop" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null && !values.isEmpty()) { + return values.removeLast(); + } + // no information + values = null; + return new VoidValue(); + } + case "addLast", "push" -> { + assert paramVars.size() == 1; + if (values != null) { + assert paramVars.getFirst().getType().equals(innerType); + values.add(paramVars.getFirst()); + } + return new VoidValue(); + } + case "removeFirst", "poll" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null && !values.isEmpty()) { + return values.removeFirst(); + } + // no information + values = null; + return new VoidValue(); + } + case "isEmpty" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null) { + return Value.valueFactory(values.isEmpty()); + } + return Value.valueFactory(Type.BOOLEAN); + } + case "fill" -> { // void fill(int[] a, int val) or void fill(int[] a, int fromIndex, int toIndex, int val) + assert paramVars.size() == 1 || paramVars.size() == 3; + if (values != null) { + if (paramVars.size() == 1) { + IValue val = paramVars.getFirst(); + for (int i = 0; i < values.size(); i++) { + values.set(i, val); + } + } else { + assert paramVars.getFirst() instanceof INumberValue; + assert paramVars.get(1) instanceof INumberValue; + INumberValue fromIndex = (INumberValue) paramVars.getFirst(); + INumberValue toIndex = (INumberValue) paramVars.get(1); + if (fromIndex.getInformation() && toIndex.getInformation()) { + int fromIdx = (int) fromIndex.getValue(); + int toIdx = (int) toIndex.getValue(); + if (fromIdx >= 0 && toIdx <= values.size() && fromIdx <= toIdx) { + IValue val = paramVars.get(2); + for (int i = fromIdx; i < toIdx; i++) { + values.set(i, val); + } + } else { + values = null; // no information + } + } else { + values = null; // no information + } + } + } + return new VoidValue(); + } + case "sort" -> { // void// sort(int[] a) or void sort(int[] a, int fromIndex, int toIndex) + if (paramVars.size() == 1) { // with Comparator + this.values = null; // ToDo + return new VoidValue(); + } + assert paramVars.size() == 0 || paramVars.size() == 2; + if (values != null) { + if (paramVars.size() == 0) { + values.sort(null); + } else { + assert paramVars.getFirst() instanceof INumberValue; + assert paramVars.get(1) instanceof INumberValue; + INumberValue fromIndex = (INumberValue) paramVars.getFirst(); + INumberValue toIndex = (INumberValue) paramVars.get(1); + if (fromIndex.getInformation() && toIndex.getInformation()) { + int fromIdx = (int) fromIndex.getValue(); + int toIdx = (int) toIndex.getValue(); + if (fromIdx >= 0 && toIdx <= values.size() && fromIdx <= toIdx) { + List sublist = values.subList(fromIdx, toIdx); + sublist.sort(null); + for (int i = fromIdx; i < toIdx; i++) { + values.set(i, sublist.get(i - fromIdx)); + } + } else { + values = null; // no information + } + } else { + values = null; // no information + } + } + } + return new VoidValue(); + } + case "copyOfRange" -> { // int[] copyOfRange(int[] original, int from, int to) + assert paramVars.size() == 2; + if (values != null) { + assert paramVars.getFirst() instanceof INumberValue; + assert paramVars.get(1) instanceof INumberValue; + INumberValue fromIndex = (INumberValue) paramVars.getFirst(); + INumberValue toIndex = (INumberValue) paramVars.get(1); + if (fromIndex.getInformation() && toIndex.getInformation()) { + int fromIdx = (int) fromIndex.getValue(); + int toIdx = (int) toIndex.getValue(); + if (fromIdx >= 0 && toIdx <= values.size() && fromIdx <= toIdx) { + List sublist = new ArrayList<>(values.subList(fromIdx, toIdx)); + return new JavaArray(sublist); + } + } + } + return new JavaArray(innerType); + } + case "clear" -> { + if (values != null) { + values.clear(); + } + return new VoidValue(); + } + case "getFirst", "peekFirst" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null && !values.isEmpty()) { + return values.getFirst(); + } + // no information + if (innerType == null) { + return new VoidValue(); + } + return arrayAccess((INumberValue) Value.valueFactory(0)); + } + case "removeFirstOccurrence" -> { + assert paramVars.size() == 1; + if (values == null) { + return Value.valueFactory(false); + } + for (int i = 0; i < values.size(); i++) { + if (values.get(i).equals(paramVars.getFirst())) { + values.remove(i); + return Value.valueFactory(true); + } + } + return Value.valueFactory(false); + } + case "addAll" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof JavaArray otherArray) { + if (this.values != null && otherArray.values != null) { + for (IValue val : otherArray.values) { + assert val.getType().equals(this.innerType); + this.values.add(val); + } + } else { + this.values = null; + } + } else { + this.values = null; + } + return new VoidValue(); + } + case "addFirst" -> { + assert paramVars.size() == 1; + if (values != null) { + assert paramVars.getFirst().getType().equals(innerType); + values.addFirst(paramVars.getFirst()); + } + return new VoidValue(); + } + case "iterator" -> { + throw new UnsupportedOperationException("Iterators are not supported"); + } + case "clone" -> { + assert paramVars == null || paramVars.isEmpty(); + return this.copy(); + } + case "filter" -> { + this.values = null; + return Value.valueFactory(Type.LIST); + } + case "findFirst" -> { + if (values != null) { + if (!values.isEmpty()) { + return values.getFirst(); + } + } + return new VoidValue(); + } + case "forEach" -> { + this.values = null; + this.innerType = Type.VOID; + return Value.valueFactory(Type.LIST); + } + case "collect" -> { + this.values = null; + return Value.valueFactory(Type.LIST); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @Override + public IValue accessField(@NotNull String fieldName) { + switch (fieldName) { + case "length" -> { + if (values != null) { + return Value.valueFactory(values.size()); + } + return Value.valueFactory(Type.INT); + } + default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported for JavaArray"); + } + } + + @NotNull + @Override + public JavaArray copy() { + List newValues = new ArrayList<>(); + if (values == null) { + return new JavaArray(innerType); + } + for (IValue value : values) { + newValues.add(value.copy()); + } + return new JavaArray(innerType, newValues); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue || other instanceof IJavaObject) { // cannot merge different types + other = new JavaArray(); + } + JavaArray otherArray = (JavaArray) other; + if (this.innerType == null && otherArray.innerType != null) { + this.innerType = otherArray.innerType; + } + if (!(Objects.equals(this.innerType, otherArray.innerType))) { + this.values = null; + return; + } + assert Objects.equals(this.innerType, otherArray.innerType); + if (this.values == null || otherArray.values == null || this.values.size() != otherArray.values.size()) { + this.values = null; + } else { + for (int i = 0; i < this.values.size(); i++) { + this.values.get(i).merge(otherArray.values.get(i)); + } + } + } + + @Override + public void setToUnknown() { + values = null; + } + + @Override + public void setInitialValue() { + values = null; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaLengthArray.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaLengthArray.java new file mode 100644 index 0000000000..deb5ad7386 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaLengthArray.java @@ -0,0 +1,205 @@ +package de.jplag.java_cpg.ai.variables.values.arrays; + +import java.util.List; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.ai.variables.values.string.StringValue; + +/** + * Represents a Java array by its length and inner type. + * @author ujiqk + */ +public class JavaLengthArray extends JavaObject implements IJavaArray { + + private final Type innerType; + private INumberValue length; + + /** + * Creates a new JavaLengthArray with an unknown inner type and length. + */ + public JavaLengthArray() { + super(Type.ARRAY); + this.innerType = Type.UNKNOWN; + } + + /** + * Creates a new JavaLengthArray with the given inner type and unknown length. + * @param innerType The inner type of the array. + */ + public JavaLengthArray(@NotNull Type innerType) { + super(Type.ARRAY); + this.innerType = innerType; + } + + /** + * Creates a new JavaLengthArray with the given inner type and length. + * @param innerType The inner type of the array. + * @param length The length of the array. + */ + public JavaLengthArray(Type innerType, @NotNull INumberValue length) { + super(Type.ARRAY); + this.innerType = innerType; + this.length = length; + } + + /** + * Creates a new JavaLengthArray with the inner type and length derived from the given values. + * @param values The values to derive the inner type and length from. + */ + public JavaLengthArray(@NotNull List values) { + super(Type.ARRAY); + this.innerType = values.getFirst().getType(); + this.length = (INumberValue) Value.valueFactory(values.size()); + } + + @Override + public IValue arrayAccess(INumberValue index) { + // if no information, return an unknown value of the inner type + if (innerType == null || innerType == Type.UNKNOWN) { + return new VoidValue(); + } + return switch (innerType) { + case INT -> Value.valueFactory(Type.INT); + case BOOLEAN -> new BooleanValue(); + case STRING -> Value.valueFactory(Type.STRING); + case OBJECT -> new JavaObject(); + case ARRAY, LIST -> new JavaArray(); + case FLOAT -> Value.valueFactory(Type.FLOAT); + case CHAR -> Value.valueFactory(Type.CHAR); + default -> throw new UnsupportedOperationException("Array of type " + innerType + " not supported"); + }; + } + + @Override + public void arrayAssign(INumberValue index, IValue value) { + // do nothing + } + + @Override + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "toString" -> { + assert paramVars == null || paramVars.isEmpty(); + return new StringValue(); + } + case "add" -> { + if (paramVars.size() == 1) { + length.unaryOperation("++"); + } else if (paramVars.size() == 2) { // index, element + // do nothing, length stays the same + } else { + throw new UnsupportedOperationException("add with " + paramVars.size() + " parameters is not supported"); + } + return new VoidValue(); + } + case "stream" -> { + assert paramVars == null || paramVars.isEmpty(); + return this; + } + case "size" -> { + assert paramVars == null || paramVars.isEmpty(); + return this.length; + } + case "map" -> { + // ToDo + return this; + } + case "max" -> { + return arrayAccess((INumberValue) Value.valueFactory(1)); + } + case "indexOf", "lastIndexOf" -> { + assert paramVars.size() == 1; + return Value.valueFactory(Type.INT); + } + case "remove" -> { + assert paramVars.size() == 1; + // information lost + length.setToUnknown(); + return new VoidValue(); + } + case "get", "elementAt" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof INumberValue; + return arrayAccess((INumberValue) paramVars.getFirst()); + } + case "contains" -> { + assert paramVars.size() == 1; + return Value.valueFactory(Type.BOOLEAN); + } + case "getLast", "findFirst", "findAny" -> { + assert paramVars == null || paramVars.isEmpty(); + return arrayAccess((INumberValue) Value.valueFactory(1)); + } + case "removeLast", "removeFirst" -> { + assert paramVars == null || paramVars.isEmpty(); + this.length.unaryOperation("--"); + return new VoidValue(); + } + case "addLast" -> { + assert paramVars.size() == 1; + this.length.unaryOperation("++"); + return new VoidValue(); + } + case "isEmpty" -> { + assert paramVars == null || paramVars.isEmpty(); + if (length.getInformation()) { + return Value.valueFactory(length.getValue() == 0); + } + return Value.valueFactory(Type.BOOLEAN); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @Override + public IValue accessField(@NotNull String fieldName) { + switch (fieldName) { + case "length" -> { + return this.length; + } + default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported for JavaArray"); + } + } + + @NotNull + @Override + public JavaObject copy() { + if (this.length == null) { + return new JavaLengthArray(this.innerType); + } + INumberValue newLength = (INumberValue) this.length.copy(); + return new JavaLengthArray(this.innerType, newLength); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof JavaLengthArray; + assert Objects.equals(this.innerType, ((JavaLengthArray) other).innerType); + if (this.length == null || ((JavaLengthArray) other).length == null) { + this.length = null; + } else { + this.length.merge(((JavaLengthArray) other).length); + } + } + + @Override + public void setToUnknown() { + length = Value.getNewIntValue(); + } + + @Override + public void setInitialValue() { + length = null; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharSetValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharSetValue.java new file mode 100644 index 0000000000..0c4f08a662 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharSetValue.java @@ -0,0 +1,96 @@ +package de.jplag.java_cpg.ai.variables.values.chars; + +import java.util.Set; + +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.Value; + +/** + * Char value representation that can hold a set of possible char values or be unknown. + * @author ujiqk + * @version 1.0 + */ +public class CharSetValue extends Value implements ICharValue { + + private Set maybeContained; + private boolean information; + + /** + * an unknown char value. + */ + public CharSetValue() { + super(Type.CHAR); + this.information = false; + } + + /** + * an exactly known char value. + * @param character the known character + */ + public CharSetValue(char character) { + super(Type.CHAR); + this.information = true; + this.maybeContained = Set.of(character); + } + + /** + * a char value that can be one of the given characters. + * @param characters the possible characters + */ + public CharSetValue(@NotNull Set characters) { + super(Type.CHAR); + this.information = true; + this.maybeContained = characters; + } + + /** + * Copy constructor. + */ + private CharSetValue(Set maybeContained, boolean information) { + super(Type.CHAR); + this.maybeContained = maybeContained; + this.information = information; + } + + @NotNull + @Override + public Value copy() { + return new CharSetValue(this.maybeContained, this.information); + } + + @Override + public void merge(@NotNull IValue other) { + CharSetValue otherCharValue = (CharSetValue) other; + if (!otherCharValue.information) { + this.information = false; + } else if (this.information) { + this.maybeContained.addAll(otherCharValue.maybeContained); + } + } + + @Override + public void setToUnknown() { + this.information = false; + } + + @Override + public void setInitialValue() { + this.information = true; + this.maybeContained = Set.of(DEFAULT_VALUE); + } + + @Override + public boolean getInformation() { + return this.information && this.maybeContained.size() == 1; + } + + @Override + public double getValue() { + assert this.information; + return this.maybeContained.iterator().next(); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharValue.java new file mode 100644 index 0000000000..73b3ffbc25 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharValue.java @@ -0,0 +1,173 @@ +package de.jplag.java_cpg.ai.variables.values.chars; + +import java.util.Set; + +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * Char value representation that can hold a single char value or be unknown. + * @author ujiqk + * @version 1.0 + */ +public class CharValue extends Value implements ICharValue { + + private char value; + private boolean information; + + /** + * an unknown char value. + */ + public CharValue() { + super(Type.CHAR); + this.information = false; + } + + /** + * an exactly known char value. + * @param value the known character + */ + public CharValue(char value) { + super(Type.CHAR); + this.information = true; + this.value = value; + } + + /** + * a char value that can be one of the given characters. + * @param values the possible characters + */ + public CharValue(@NotNull Set values) { + super(Type.CHAR); + if (values.size() == 1) { + this.information = true; + this.value = values.iterator().next(); + } else { + this.information = false; + } + } + + /** + * Copy constructor. + */ + private CharValue(char value, boolean information) { + super(Type.CHAR); + this.information = information; + this.value = value; + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + switch (operator) { + case "==" -> { + CharValue otherCharValue = (CharValue) other; + if (this.information && otherCharValue.information) { + return new BooleanValue(this.value == otherCharValue.value); + } else { + return new BooleanValue(); + } + } + case "!=" -> { + CharValue otherCharValue = (CharValue) other; + if (this.information && otherCharValue.information) { + return new BooleanValue(this.value != otherCharValue.value); + } else { + return new BooleanValue(); + } + } + case "+" -> { + if (other instanceof CharValue otherCharValue) { + if (this.information && otherCharValue.information) { + return new CharValue((char) (this.value + otherCharValue.value)); + } else { + return new CharValue(); + } + } + IStringValue otherStringValue = (IStringValue) other; + if (this.information && otherStringValue.getInformation()) { + return Value.valueFactory(this.value + otherStringValue.getValue()); + } else { + return Value.valueFactory(Type.STRING); + } + } + case "-" -> { + CharValue otherCharValue = (CharValue) other; + if (this.information && otherCharValue.information) { + return new CharValue((char) (this.value - otherCharValue.value)); + } else { + return new CharValue(); + } + } + default -> throw new IllegalArgumentException("Unknown binary operator: " + operator + " for " + getType()); + } + } + + @Override + public IValue unaryOperation(@NotNull String operator) { + switch (operator) { + default -> throw new IllegalArgumentException("Unary operation " + operator + " not supported for " + getType()); + } + } + + @NotNull + @Override + public Value copy() { + return new CharValue(this.value, this.information); + } + + @Override + public void merge(@NotNull IValue other) { + switch (other) { + case CharValue otherCharValue -> { + if (!otherCharValue.information) { + this.information = false; + } else if (this.information) { + if (this.value != otherCharValue.value) { + this.information = false; + } + } + } + case INumberValue otherNumberValue -> { + if (!otherNumberValue.getInformation()) { + this.information = false; + } else if (this.information) { + if (this.value != (char) otherNumberValue.getValue()) { + this.information = false; + } + } + } + default -> { + throw new IllegalArgumentException("Cannot merge " + getType() + " with " + other.getType()); + } + } + } + + @Override + public void setToUnknown() { + this.information = false; + } + + @Override + public void setInitialValue() { + this.information = true; + this.value = DEFAULT_VALUE; + } + + @Override + public boolean getInformation() { + return this.information; + } + + @Override + public double getValue() { + assert getInformation(); + return this.value; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/ICharValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/ICharValue.java new file mode 100644 index 0000000000..00bbc6dc5a --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/ICharValue.java @@ -0,0 +1,27 @@ +package de.jplag.java_cpg.ai.variables.values.chars; + +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +/** + * Interface for all java char value representations. + * @author ujiqk + * @version 1.0 + */ +public interface ICharValue extends INumberValue { + + /** + * The initial default value for char values in java. + */ + char DEFAULT_VALUE = '\u0000'; + + /** + * @return if exact information about the char value is known. + */ + boolean getInformation(); + + /** + * @return the exact char value, if known. + */ + double getValue(); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatSetValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatSetValue.java new file mode 100644 index 0000000000..b208d85d6e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatSetValue.java @@ -0,0 +1,99 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.numbers.helpers.DoubleInterval; + +/** + * Float value represented as a set of intervals. + * @author ujiqk + * @version 1.0 + */ +public class FloatSetValue extends NumberSetValue { + + /** + * Default constructor c Float value represented as a set of intervals with no information. + */ + public FloatSetValue() { + super(Type.FLOAT); + values.add(new DoubleInterval()); + } + + private FloatSetValue(TreeSet values) { + super(Type.FLOAT, values); + } + + /** + * Constructor for FloatSetValue that is known to be a single number. + * @param number the single float number + */ + public FloatSetValue(double number) { + super(Type.FLOAT); + values.add(new DoubleInterval(number)); + } + + /** + * Constructor for FloatSetValue that is known to be one of the possible numbers. + * @param possibleNumbers the possible float numbers + */ + public FloatSetValue(@NotNull Set possibleNumbers) { + super(Type.INT); + values = new TreeSet<>(); + // ToDo: slice into intervals + values.add(new DoubleInterval()); + } + + /** + * Constructor for FloatSetValue that is known to be within a certain range. + * @param lowerBound the lower bound of the range + * @param upperBound the upper bound of the range + */ + public FloatSetValue(double lowerBound, double upperBound) { + super(Type.INT); + values.add(new DoubleInterval(lowerBound, upperBound)); + } + + @Override + protected DoubleInterval createFullInterval() { + return new DoubleInterval(); + } + + @Override + protected DoubleInterval createInterval(Double lowerBound, Double upperBound) { + return new DoubleInterval(lowerBound, upperBound); + } + + @Override + protected NumberSetValue createInstance(TreeSet values) { + return new FloatSetValue(values); + } + + @NotNull + @Override + public Value copy() { + return new FloatSetValue(new TreeSet<>(values)); + } + + @Override + public void setInitialValue() { + values = new TreeSet<>(); + values.add(createInterval(0d, 0d)); + } + + /** + * Use for testing purposes only. + * @return the set of intervals representing the float value. + */ + @TestOnly + public SortedSet getIntervals() { + return values; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatValue.java new file mode 100644 index 0000000000..6f9a6e728c --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatValue.java @@ -0,0 +1,243 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +import java.util.Set; + +import org.checkerframework.dataflow.qual.Impure; +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; + +/** + * Represents a floating point value with optional exact information. + */ +public class FloatValue extends Value implements INumberValue { + + private double value; + private boolean information; // whether exact information is available + + /** + * a IntValue with no information. + */ + public FloatValue() { + super(Type.FLOAT); + information = false; + } + + /** + * Constructor for FloatValue with exact information. + * @param value the float value. + */ + public FloatValue(double value) { + super(Type.FLOAT); + this.value = value; + information = true; + } + + /** + * Constructor for FloatValue with a range. + * @param lowerBound the lower bound of the range. + * @param upperBound the upper bound of the range. + */ + public FloatValue(double lowerBound, double upperBound) { + super(Type.FLOAT); + assert lowerBound <= upperBound; + if (lowerBound == upperBound) { + this.value = lowerBound; + this.information = true; + } else { + this.information = false; + } + } + + /** + * Constructor for FloatValue with a set of possible values. + * @param values the set of possible float values. + */ + public FloatValue(@NotNull Set values) { + super(Type.FLOAT); + if (values.size() == 1) { + this.value = values.iterator().next(); + this.information = true; + } else { + this.information = false; + } + } + + private FloatValue(double value, boolean information) { + super(Type.FLOAT); + this.value = value; + this.information = information; + } + + /** + * @return whether exact information is available. + */ + public boolean getInformation() { + return information; + } + + /** + * @return the value. Only call if information is true. + */ + public double getValue() { + assert information; + return value; + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (other instanceof VoidValue) { + return new VoidValue(); + } + assert other instanceof INumberValue; + switch (operator) { + case "+" -> { + if (information && ((INumberValue) other).getInformation()) { + return new FloatValue(this.value + ((INumberValue) other).getValue()); + } else { + return new FloatValue(); + } + } + case "<" -> { + if (information && ((INumberValue) other).getInformation()) { + return new BooleanValue(this.value < ((INumberValue) other).getValue()); + } else { + return new BooleanValue(); + } + } + case ">" -> { + if (information && ((INumberValue) other).getInformation()) { + return new BooleanValue(this.value > ((INumberValue) other).getValue()); + } else { + return new BooleanValue(); + } + } + case "-" -> { + if (information && ((INumberValue) other).getInformation()) { + return new FloatValue(this.value - ((INumberValue) other).getValue()); + } else { + return new FloatValue(); + } + } + case "==" -> { + if (information && ((INumberValue) other).getInformation()) { + return new BooleanValue(this.value == ((INumberValue) other).getValue()); + } else { + return new BooleanValue(); + } + } + case "!=" -> { + if (information && ((INumberValue) other).getInformation()) { + return new BooleanValue(this.value != ((INumberValue) other).getValue()); + } else { + return new BooleanValue(); + } + } + case "*" -> { + if (information && ((INumberValue) other).getInformation()) { + return new FloatValue(this.value * ((INumberValue) other).getValue()); + } else { + return new FloatValue(); + } + } + case "/" -> { + if (information && ((INumberValue) other).getInformation()) { + return new FloatValue(this.value / ((INumberValue) other).getValue()); + } else { + return new FloatValue(); + } + } + case "pow" -> { + if (information && ((INumberValue) other).getInformation()) { + return new FloatValue(Math.pow(this.value, ((INumberValue) other).getValue())); + } else { + return new FloatValue(); + } + } + case "<=" -> { + if (information && ((INumberValue) other).getInformation()) { + return new BooleanValue(this.value <= ((INumberValue) other).getValue()); + } else { + return new BooleanValue(); + } + } + case ">=" -> { + if (information && ((INumberValue) other).getInformation()) { + return new BooleanValue(this.value >= ((INumberValue) other).getValue()); + } else { + return new BooleanValue(); + } + } + default -> throw new UnsupportedOperationException( + "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + } + + @Override + @Impure + public IValue unaryOperation(@NotNull String operator) { + switch (operator) { + case "++" -> { + if (information) { + this.value += 1; + return new FloatValue(this.value); + } else { + return new BooleanValue(); + } + } + case "-" -> { + if (information) { + this.value = -this.value; + return new FloatValue(this.value); + } else { + return new FloatValue(); + } + } + case "sqrt" -> { + if (information) { + return new FloatValue(Math.sqrt(this.value)); + } else { + return new FloatValue(); + } + } + default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType()); + } + } + + @NotNull + @Override + public Value copy() { + return new FloatValue(value, information); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + this.information = false; + return; + } + assert other instanceof FloatValue; + FloatValue otherFloat = (FloatValue) other; + if (this.information && otherFloat.information && this.value == otherFloat.value) { + // keep information + } else { + this.information = false; + } + } + + @Override + public void setToUnknown() { + this.information = false; + } + + @Override + public void setInitialValue() { + value = 0.0; + information = true; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/INumberValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/INumberValue.java new file mode 100644 index 0000000000..c0a767edf6 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/INumberValue.java @@ -0,0 +1,22 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +import de.jplag.java_cpg.ai.variables.values.IValue; + +/** + * Interface for number values. + * @author ujiqk + * @version 1.0 + */ +public interface INumberValue extends IValue { + + /** + * @return if exact information is available. + */ + boolean getInformation(); + + /** + * @return the exact value. Only valid if getInformation() returns true. + */ + double getValue(); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntIntervalValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntIntervalValue.java new file mode 100644 index 0000000000..c6a9b9b662 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntIntervalValue.java @@ -0,0 +1,171 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +import java.util.Set; + +import org.checkerframework.dataflow.qual.Impure; +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.helpers.IntInterval; + +/** + * Represents integer values as intervals. + * @author ujiqk + * @version 1.0 + */ +public class IntIntervalValue extends Value implements INumberValue { + + private final IntInterval interval; + + /** + * a IntIntervalValue with no information. + */ + public IntIntervalValue() { + super(Type.INT); + interval = new IntInterval(); + } + + /** + * Constructor for IntIntervalValue which value is between given bounds. + * @param lowerBound the lower bound. + * @param upperBound the upper bound. + */ + public IntIntervalValue(int lowerBound, int upperBound) { + super(Type.INT); + interval = new IntInterval(lowerBound, upperBound); + } + + /** + * Constructor for IntIntervalValue with exact information. + * @param number the integer value. + */ + public IntIntervalValue(int number) { + super(Type.INT); + interval = new IntInterval(number); + } + + /** + * Constructor for IntIntervalValue which value is a set of possible values. + * @param possibleValues the set of possible integer values. + */ + public IntIntervalValue(@NotNull Set possibleValues) { + super(Type.INT); + java.util.List values = possibleValues.stream().toList(); + interval = new IntInterval(values.getFirst(), values.getLast()); + } + + private IntIntervalValue(IntInterval interval) { + super(Type.INT); + this.interval = interval; + } + + @Override + public boolean getInformation() { + return interval.getInformation(); + } + + @Override + public double getValue() { + return interval.getValue(); + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (!(other instanceof INumberValue)) { + other = new IntIntervalValue(); + } + IntIntervalValue otherValue = (IntIntervalValue) other; // ToDo: what if other float? + switch (operator) { + case "+" -> { + IntInterval newInterval = this.interval.copy().plus(otherValue.interval); + return new IntIntervalValue(newInterval); + } + case "-" -> { + IntInterval newInterval = this.interval.copy().minus(otherValue.interval); + return new IntIntervalValue(newInterval); + } + case "<" -> { + return this.interval.copy().smaller(otherValue.interval); + } + case ">" -> { + return this.interval.copy().bigger(otherValue.interval); + } + case "<=" -> { + return this.interval.copy().smallerEqual(otherValue.interval); + } + case ">=" -> { + return this.interval.copy().biggerEqual(otherValue.interval); + } + case "==" -> { + return this.interval.copy().equal(otherValue.interval); + } + case "!=" -> { + return this.interval.copy().notEqual(otherValue.interval); + } + case "*" -> { + IntInterval newInterval = this.interval.copy().times(otherValue.interval); + return new IntIntervalValue(newInterval); + } + case "/" -> { + IntInterval newInterval = this.interval.copy().divided(otherValue.interval); + return new IntIntervalValue(newInterval); + } + default -> throw new UnsupportedOperationException( + "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + } + + @Override + @Impure + public IValue unaryOperation(@NotNull String operator) { + switch (operator) { + case "++" -> { + this.interval.plusPlus(); + return this.copy(); + } + case "--" -> { + this.interval.minusMinus(); + return this.copy(); + } + case "-" -> { + IntInterval newInterval = this.interval.copy().unaryMinus(); + return new IntIntervalValue(newInterval); + } + case "abs" -> { + IntInterval newInterval = this.interval.copy().abs(); + return new IntIntervalValue(newInterval); + } + default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType()); + } + } + + @NotNull + @Override + public IValue copy() { + return new IntIntervalValue(interval.copy()); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + other = new IntIntervalValue(); + } + assert other instanceof IntIntervalValue; + this.interval.merge(((IntIntervalValue) other).interval); + } + + @Override + public void setToUnknown() { + interval.setToUnknown(); + } + + @Override + public void setInitialValue() { + interval.setUpperBound(0); + interval.setLowerBound(0); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntSetValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntSetValue.java new file mode 100644 index 0000000000..fe2446eb10 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntSetValue.java @@ -0,0 +1,99 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.numbers.helpers.IntInterval; + +/** + * Integer value represented as a set of intervals. + * @author ujiqk + * @version 1.0 + */ +public class IntSetValue extends NumberSetValue { + + /** + * Integer value represented as a set of intervals with no information. + */ + public IntSetValue() { + super(Type.INT); + values.add(new IntInterval()); + } + + private IntSetValue(TreeSet values) { + super(Type.INT, values); + } + + /** + * Constructor for IntSetValue that is known to be a single number. + * @param number the single integer number + */ + public IntSetValue(int number) { + super(Type.INT); + values.add(new IntInterval(number)); + } + + /** + * Constructor for IntSetValue that is known to be one of the possible numbers. + * @param possibleNumbers the possible integer numbers + */ + public IntSetValue(@NotNull Set possibleNumbers) { + super(Type.INT); + values = new TreeSet<>(); + // ToDo: slice into intervals + values.add(new IntInterval()); + } + + /** + * Constructor for IntSetValue that is known to be within a certain range. + * @param lowerBound the lower bound of the range + * @param upperBound the upper bound of the range + */ + public IntSetValue(int lowerBound, int upperBound) { + super(Type.INT); + values.add(new IntInterval(lowerBound, upperBound)); + } + + @Override + protected IntInterval createFullInterval() { + return new IntInterval(); + } + + @Override + protected IntInterval createInterval(Integer lowerBound, Integer upperBound) { + return new IntInterval(lowerBound, upperBound); + } + + @Override + protected NumberSetValue createInstance(TreeSet values) { + return new IntSetValue(values); + } + + @NotNull + @Override + public Value copy() { + return new IntSetValue(new TreeSet<>(values)); + } + + @Override + public void setInitialValue() { + values = new TreeSet<>(); + values.add(createInterval(0, 0)); + } + + /** + * Use for testing only! + * @return the set of intervals representing the integer value. + */ + @TestOnly + public SortedSet getIntervals() { + return values; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntValue.java new file mode 100644 index 0000000000..29abcd7246 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntValue.java @@ -0,0 +1,316 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +import java.util.Set; + +import org.checkerframework.dataflow.qual.Impure; +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.chars.CharValue; + +/** + * Represents an integer value with optional exact information. + * @author ujiqk + * @version 1.0 + */ +public class IntValue extends Value implements INumberValue { + + private int value; + private boolean information; // whether exact information is available + + /** + * a IntValue with no information. + */ + public IntValue() { + super(Type.INT); + information = false; + } + + /** + * Constructor for IntValue with exact information. + * @param value the integer value. + */ + public IntValue(int value) { + super(Type.INT); + this.value = value; + information = true; + } + + /** + * Constructor for IntValue with exact information from a double value. + * @param value the integer value as double. + */ + public IntValue(double value) { + super(Type.INT); + this.value = (int) value; + information = true; + } + + /** + * Constructor for IntValue with a set of possible values. + * @param possibleValues the set of possible integer values. + */ + public IntValue(@NotNull Set possibleValues) { + super(Type.INT); + if (possibleValues.size() == 1) { + this.value = possibleValues.iterator().next(); + this.information = true; + } else { + this.information = false; + } + } + + /** + * Constructor for IntValue with a range. + * @param lowerBound the lower bound of the range. + * @param upperBound the upper bound of the range. + */ + public IntValue(int lowerBound, int upperBound) { + super(Type.INT); + assert lowerBound <= upperBound; + if (lowerBound == upperBound) { + this.value = lowerBound; + this.information = true; + } else { + this.information = false; + } + } + + private IntValue(int value, boolean information) { + super(Type.INT); + this.value = value; + this.information = information; + } + + /** + * @return whether exact information is available + */ + public boolean getInformation() { + return information; + } + + /** + * @return the exact value. Only valid if getInformation() returns true. + */ + public double getValue() { + assert information; + return value; + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (!(other instanceof INumberValue)) { + other = new IntValue(); + } + INumberValue otherNumber = (INumberValue) other; + switch (operator) { + case "+" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(this.value + otherNumber.getValue()); + } else { + return new IntValue(); + } + } + case "<" -> { + if (information && otherNumber.getInformation()) { + return new BooleanValue(this.value < otherNumber.getValue()); + } else { + return new BooleanValue(); + } + } + case ">" -> { + if (information && otherNumber.getInformation()) { + return new BooleanValue(this.value > otherNumber.getValue()); + } else { + return new BooleanValue(); + } + } + case "-" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(this.value - otherNumber.getValue()); + } else { + return new IntValue(); + } + } + case "!=" -> { + if (information && otherNumber.getInformation()) { + return new BooleanValue(this.value != otherNumber.getValue()); + } else { + return new BooleanValue(); + } + } + case "==" -> { + if (information && otherNumber.getInformation()) { + return new BooleanValue(this.value == otherNumber.getValue()); + } else { + return new BooleanValue(); + } + } + case "*" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(this.value * otherNumber.getValue()); + } else { + return new IntValue(); + } + } + case "/" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(this.value / otherNumber.getValue()); + } else { + return new IntValue(); + } + } + case "<=" -> { + if (information && otherNumber.getInformation()) { + return new BooleanValue(this.value <= otherNumber.getValue()); + } else { + return new BooleanValue(); + } + } + case ">=" -> { + if (information && otherNumber.getInformation()) { + return new BooleanValue(this.value >= otherNumber.getValue()); + } else { + return new BooleanValue(); + } + } + case "max" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(Math.max(this.value, otherNumber.getValue())); + } else { + return new IntValue(); + } + } + case "min" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(Math.min(this.value, otherNumber.getValue())); + } else { + return new IntValue(); + } + } + case "%" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(this.value % otherNumber.getValue()); + } else { + return new IntValue(); + } + } + case ">>" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(this.value >> (int) otherNumber.getValue()); + } else { + return new IntValue(); + } + } + case "<<" -> { + if (information && otherNumber.getInformation()) { + return new IntValue(this.value << (int) otherNumber.getValue()); + } else { + return new IntValue(); + } + } + default -> throw new UnsupportedOperationException( + "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + } + + @Override + @Impure + public IValue unaryOperation(@NotNull String operator) { + switch (operator) { + case "++" -> { + if (information) { + this.value += 1; + return new IntValue(this.value); + } else { + return new IntValue(); + } + } + case "--" -> { + if (information) { + this.value -= 1; + return new IntValue(this.value); + } else { + return new IntValue(); + } + } + case "-" -> { + if (information) { + this.value = -this.value; + return new IntValue(this.value); + } else { + return new IntValue(); + } + } + case "abs" -> { + if (information) { + return new IntValue(Math.abs(this.value)); + } else { + return new IntValue(); + } + } + case "sin" -> { + if (information) { + return Value.valueFactory(Math.sin(this.value)); + } else { + return Value.valueFactory(Type.FLOAT); + } + } + default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType()); + } + } + + @NotNull + @Override + public Value copy() { + return new IntValue(value, information); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + this.information = false; + return; + } + if (other instanceof JavaObject javaObject) { // could be an Integer object + if (javaObject.accessField("value") instanceof IntValue intValue) { + other = intValue; + } else { + this.information = false; + return; + } + } + if (other instanceof CharValue charValue) { // cannot merge different types + if (information && charValue.getInformation() && this.value == charValue.getValue()) { + // keep information + } else { + this.information = false; + } + return; + } + assert other instanceof INumberValue; + INumberValue otherInt = (INumberValue) other; + if (this.information && otherInt.getInformation() && this.value == otherInt.getValue()) { + // keep information + } else { + this.information = false; + } + } + + @Override + public void setToUnknown() { + this.information = false; + } + + @Override + public void setInitialValue() { + value = 0; + information = true; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/NumberSetValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/NumberSetValue.java new file mode 100644 index 0000000000..ffe55f52f5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/NumberSetValue.java @@ -0,0 +1,282 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +import java.util.Iterator; +import java.util.TreeSet; + +import org.checkerframework.dataflow.qual.Impure; +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.numbers.helpers.Interval; + +/** + * Abstract base class for numeric values represented as sets of intervals. + * @param The type of number (Integer, Double, etc.) + * @param The interval type for this number + */ +public abstract class NumberSetValue, I extends Interval> extends Value implements INumberValue { + + protected TreeSet values; + + protected NumberSetValue(Type type) { + super(type); + values = new TreeSet<>(); + } + + protected NumberSetValue(Type type, TreeSet values) { + super(type); + this.values = values; + } + + protected abstract I createFullInterval(); + + protected abstract I createInterval(T lowerBound, T upperBound); + + protected abstract NumberSetValue createInstance(TreeSet values); + + @Override + public boolean getInformation() { + return values.size() == 1 && values.getFirst().getInformation(); + } + + @Override + public double getValue() { + assert getInformation(); + return values.getFirst().getValue().doubleValue(); + } + + @Override + @SuppressWarnings("unchecked") + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (!(other instanceof NumberSetValue)) { + other = createInstance(new TreeSet<>()); + } + NumberSetValue otherValue = (NumberSetValue) other; + switch (operator) { + case "+" -> { + TreeSet newValues = new TreeSet<>(); + for (I interval : otherValue.values) { + for (I value : values) { + newValues.add((I) interval.plus(value)); + } + } + NumberSetValue newValue = createInstance(newValues); + newValue.mergeOverlappingIntervals(); + return newValue; + } + case "-" -> { + TreeSet newValues = new TreeSet<>(); + for (I interval : otherValue.values) { + for (I value : values) { + newValues.add((I) value.minus(interval)); + } + } + NumberSetValue newValue = createInstance(newValues); + newValue.mergeOverlappingIntervals(); + return newValue; + } + case "<" -> { + if (values.getLast().getUpperBound().compareTo(otherValue.values.getFirst().getLowerBound()) < 0) { + return new BooleanValue(true); + } else if (values.getFirst().getLowerBound().compareTo(otherValue.values.getLast().getUpperBound()) >= 0) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + case ">" -> { + if (values.getFirst().getLowerBound().compareTo(otherValue.values.getLast().getUpperBound()) > 0) { + return new BooleanValue(true); + } else if (values.getLast().getUpperBound().compareTo(otherValue.values.getFirst().getLowerBound()) <= 0) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + case "<=" -> { + if (values.getLast().getUpperBound().compareTo(otherValue.values.getFirst().getLowerBound()) <= 0) { + return new BooleanValue(true); + } else if (values.getFirst().getLowerBound().compareTo(otherValue.values.getLast().getUpperBound()) > 0) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + case ">=" -> { + if (values.getFirst().getLowerBound().compareTo(otherValue.values.getLast().getUpperBound()) >= 0) { + return new BooleanValue(true); + } else if (values.getLast().getUpperBound().compareTo(otherValue.values.getFirst().getLowerBound()) < 0) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + case "==" -> { + if (values.size() != otherValue.values.size()) { + return new BooleanValue(false); + } + BooleanValue equal = null; + Iterator otherInterval = otherValue.values.iterator(); + for (I interval : values) { + BooleanValue result = interval.equal(otherInterval.next()); + if (!result.getInformation()) { + return new BooleanValue(); + } + if (equal == null) { + equal = result; + } else if (equal.getValue() != result.getValue()) { + return new BooleanValue(); + } + } + return equal; + } + case "!=" -> { + BooleanValue result = (BooleanValue) this.binaryOperation("==", other); + if (result.getInformation()) { + return new BooleanValue(!result.getValue()); + } + return new BooleanValue(); + } + case "*" -> { + TreeSet newValues = new TreeSet<>(); + for (I interval : otherValue.values) { + for (I value : values) { + newValues.add((I) interval.times(value)); + } + } + NumberSetValue newValue = createInstance(newValues); + newValue.mergeOverlappingIntervals(); + return newValue; + } + case "/" -> { + TreeSet newValues = new TreeSet<>(); + for (I interval : otherValue.values) { + for (I value : values) { + newValues.add((I) value.divided(interval)); + } + } + NumberSetValue newValue = createInstance(newValues); + newValue.mergeOverlappingIntervals(); + return newValue; + } + case "min" -> { + T upper = values.getLast().getUpperBound().compareTo(otherValue.values.getLast().getUpperBound()) < 0 + ? values.getLast().getUpperBound() + : otherValue.values.getLast().getUpperBound(); + // include all intervals in the result but all are capped at upper + TreeSet newValues = new TreeSet<>(); + for (I interval : otherValue.values) { + T upperBound = interval.getUpperBound().compareTo(upper) < 0 ? interval.getUpperBound() : upper; + T lowerBound = interval.getLowerBound().compareTo(upperBound) < 0 ? interval.getLowerBound() : upperBound; + newValues.add(createInterval(lowerBound, upperBound)); + } + for (I interval : values) { + T upperBound = interval.getUpperBound().compareTo(upper) < 0 ? interval.getUpperBound() : upper; + T lowerBound = interval.getLowerBound().compareTo(upperBound) < 0 ? interval.getLowerBound() : upperBound; + newValues.add(createInterval(lowerBound, upperBound)); + } + NumberSetValue newValue = createInstance(newValues); + newValue.mergeOverlappingIntervals(); + return newValue; + } + case "max" -> { + T lower = values.getFirst().getLowerBound().compareTo(otherValue.values.getFirst().getLowerBound()) > 0 + ? values.getFirst().getLowerBound() + : otherValue.values.getFirst().getLowerBound(); + // include all intervals in the result, but all are floored at lower + TreeSet newValues = new TreeSet<>(); + try { + for (I interval : otherValue.values) { + T lowerBound = interval.getLowerBound().compareTo(lower) > 0 ? interval.getLowerBound() : lower; + T upperBound = interval.getUpperBound().compareTo(lowerBound) > 0 ? interval.getUpperBound() : lowerBound; + newValues.add(createInterval(lowerBound, upperBound)); + } + for (I interval : values) { + T lowerBound = interval.getLowerBound().compareTo(lower) > 0 ? interval.getLowerBound() : lower; + T upperBound = interval.getUpperBound().compareTo(lowerBound) > 0 ? interval.getUpperBound() : lowerBound; + newValues.add(createInterval(lowerBound, upperBound)); + } + } catch (Exception e) { + throw new RuntimeException("Failed to create interval instance", e); + } + NumberSetValue newValue = createInstance(newValues); + newValue.mergeOverlappingIntervals(); + return newValue; + } + default -> throw new UnsupportedOperationException( + "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + } + + @Override + @SuppressWarnings("unchecked") + public void merge(@NotNull IValue other) { + assert other.getClass().equals(this.getClass()); + TreeSet otherValues = ((NumberSetValue) other).values; + this.values.addAll(otherValues); + mergeOverlappingIntervals(); + } + + @Override + public void setToUnknown() { + values = new TreeSet<>(); + values.add(createFullInterval()); + } + + @Override + @Impure + @SuppressWarnings("unchecked") + public IValue unaryOperation(@NotNull String operator) { + switch (operator) { + case "++" -> { + values.forEach(Interval::plusPlus); + return this.copy(); + } + case "--" -> { + values.forEach(Interval::minusMinus); + return this.copy(); + } + case "-" -> { + TreeSet newValues = new TreeSet<>(); + for (I interval : values) { + newValues.add((I) interval.unaryMinus()); + } + return createInstance(newValues); + } + case "abs" -> { + TreeSet newValues = new TreeSet<>(); + for (I interval : values) { + newValues.add((I) interval.abs()); + } + NumberSetValue newValue = createInstance(newValues); + newValue.mergeOverlappingIntervals(); + return newValue; + } + default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType()); + } + } + + protected void mergeOverlappingIntervals() { + if (values.size() < 2) { + return; + } + TreeSet newValues = new TreeSet<>(); + newValues.add(values.first()); + values.remove(values.first()); + for (I interval : values) { + I lastInterval = newValues.last(); + if (lastInterval.getUpperBound().compareTo(interval.getLowerBound()) >= 0) { + T maxUpper = lastInterval.getUpperBound().compareTo(interval.getUpperBound()) > 0 ? lastInterval.getUpperBound() + : interval.getUpperBound(); + lastInterval.setUpperBound(maxUpper); + } else { + newValues.add(interval); + } + } + this.values = newValues; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/DoubleInterval.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/DoubleInterval.java new file mode 100644 index 0000000000..621bf66404 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/DoubleInterval.java @@ -0,0 +1,244 @@ +package de.jplag.java_cpg.ai.variables.values.numbers.helpers; + +import org.checkerframework.dataflow.qual.Impure; +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.values.BooleanValue; + +/** + * Interval implementation for Double values. + * @author ujiqk + * @version 1.0 + */ +public class DoubleInterval extends Interval { + + /** + * The maximum value for Double intervals. + */ + public static final double MAX_VALUE = Double.MAX_VALUE; + /** + * The minimum value for Double intervals. + */ + public static final double MIN_VALUE = -Double.MAX_VALUE; + + /** + * Creates a new Double interval representing the whole range of Double values. + */ + public DoubleInterval() { + this.lowerBound = MIN_VALUE; + this.upperBound = MAX_VALUE; + } + + /** + * Creates a new Double interval representing a single number. + * @param number the number + */ + public DoubleInterval(double number) { + this.lowerBound = number; + this.upperBound = number; + } + + /** + * Creates a new Double interval with the given bounds. + * @param lowerBound the lower bound. + * @param upperBound the upper bound. + */ + public DoubleInterval(double lowerBound, double upperBound) { + assert lowerBound <= upperBound; + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + @Override + public boolean getInformation() { + return lowerBound.equals(upperBound); + } + + @Override + public Double getValue() { + assert lowerBound.equals(upperBound); + return lowerBound; + } + + @Override + public DoubleInterval copy() { + return new DoubleInterval(lowerBound, upperBound); + } + + @Override + public void setToUnknown() { + this.lowerBound = MIN_VALUE; + this.upperBound = MAX_VALUE; + } + + @Pure + @Override + public DoubleInterval plus(@NotNull Interval other) { + double lo = lowerBound + other.lowerBound; + double hi = upperBound + other.upperBound; + return new DoubleInterval(lo, hi); + } + + @Pure + @Override + public DoubleInterval minus(@NotNull Interval other) { + double lo = lowerBound - other.upperBound; + double hi = upperBound - other.lowerBound; + return new DoubleInterval(lo, hi); + } + + @Pure + @Override + public DoubleInterval times(@NotNull Interval other) { + double p1 = lowerBound * other.lowerBound; + double p2 = lowerBound * other.upperBound; + double p3 = upperBound * other.lowerBound; + double p4 = upperBound * other.upperBound; + double lo = Math.min(Math.min(p1, p2), Math.min(p3, p4)); + double hi = Math.max(Math.max(p1, p2), Math.max(p3, p4)); + return new DoubleInterval(lo, hi); + } + + @Pure + @Override + public DoubleInterval divided(@NotNull Interval other) { + if (other.lowerBound <= 0 && other.upperBound >= 0) { + return new DoubleInterval(MIN_VALUE, MAX_VALUE); + } + double p1 = lowerBound / other.lowerBound; + double p2 = lowerBound / other.upperBound; + double p3 = upperBound / other.lowerBound; + double p4 = upperBound / other.upperBound; + double lo = Math.min(Math.min(p1, p2), Math.min(p3, p4)); + double hi = Math.max(Math.max(p1, p2), Math.max(p3, p4)); + return new DoubleInterval(lo, hi); + } + + @Pure + @Override + public BooleanValue equal(@NotNull Interval other) { + if (lowerBound.equals(upperBound) && other.lowerBound.equals(other.upperBound) && lowerBound.equals(other.lowerBound)) { + return new BooleanValue(true); + } else if (upperBound < other.lowerBound || lowerBound > other.upperBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue notEqual(@NotNull Interval other) { + if (upperBound < other.lowerBound || lowerBound > other.upperBound) { + return new BooleanValue(true); + } else if (lowerBound.equals(upperBound) && other.lowerBound.equals(other.upperBound) && lowerBound.equals(other.lowerBound)) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue smaller(@NotNull Interval other) { + if (upperBound < other.lowerBound) { + return new BooleanValue(true); + } else if (lowerBound >= other.upperBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue smallerEqual(@NotNull Interval other) { + if (upperBound <= other.lowerBound) { + return new BooleanValue(true); + } else if (lowerBound > other.upperBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue bigger(@NotNull Interval other) { + if (lowerBound > other.upperBound) { + return new BooleanValue(true); + } else if (upperBound <= other.lowerBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue biggerEqual(@NotNull Interval other) { + if (lowerBound >= other.upperBound) { + return new BooleanValue(true); + } else if (upperBound < other.lowerBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Impure + @Override + public DoubleInterval plusPlus() { + lowerBound += 1.0; + upperBound += 1.0; + return this.copy(); + } + + @Impure + @Override + public DoubleInterval minusMinus() { + lowerBound -= 1.0; + upperBound -= 1.0; + return this.copy(); + } + + @Pure + @Override + public DoubleInterval unaryMinus() { + return new DoubleInterval(-upperBound, -lowerBound); + } + + @Pure + @Override + public DoubleInterval abs() { + if (upperBound < 0) { + return new DoubleInterval(Math.abs(upperBound), Math.abs(lowerBound)); + } else if (lowerBound >= 0) { + return new DoubleInterval(lowerBound, upperBound); + } else { + double max = Math.max(Math.abs(lowerBound), Math.abs(upperBound)); + return new DoubleInterval(0, max); + } + } + + @Impure + @Override + public void merge(@NotNull Interval other) { + double smallerLowerBound = Math.min(this.lowerBound, other.lowerBound); + double largerUpperBound = Math.max(this.upperBound, other.upperBound); + assert smallerLowerBound <= largerUpperBound; + this.lowerBound = smallerLowerBound; + this.upperBound = largerUpperBound; + } + + @Override + public int compareTo(@NotNull Interval o) { + if (!this.lowerBound.equals(o.lowerBound)) { + return Double.compare(this.lowerBound, o.lowerBound); + } else { + return Double.compare(this.upperBound, o.upperBound); + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/IntInterval.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/IntInterval.java new file mode 100644 index 0000000000..9c38628c1a --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/IntInterval.java @@ -0,0 +1,265 @@ +package de.jplag.java_cpg.ai.variables.values.numbers.helpers; + +import org.checkerframework.dataflow.qual.Impure; +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.values.BooleanValue; + +/** + * Interval implementation for Integer values. + * @author ujiqk + * @version 1.0 + */ +public class IntInterval extends Interval { + + /** + * The maximum value for Integer intervals. + */ + public static final int MAX_VALUE = Integer.MAX_VALUE; + /** + * The minimum value for Integer intervals. + */ + public static final int MIN_VALUE = Integer.MIN_VALUE; + + /** + * Creates a new Integer interval representing the whole range of Integer values. + */ + public IntInterval() { + this.lowerBound = MIN_VALUE; + this.upperBound = MAX_VALUE; + } + + /** + * Creates a new Integer interval representing a single number. + * @param number the number + */ + public IntInterval(int number) { + this.lowerBound = number; + this.upperBound = number; + } + + /** + * Creates a new Integer interval with the given bounds. + * @param lowerBound the lower bound. + * @param upperBound the upper bound. + */ + public IntInterval(int lowerBound, int upperBound) { + assert lowerBound <= upperBound; + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + private static int safeAbs(int x) { + if (x == Integer.MIN_VALUE) { + return Integer.MAX_VALUE; + } + return Math.abs(x); + } + + @Override + public boolean getInformation() { + return lowerBound.equals(upperBound); + } + + @Override + public Integer getValue() { + assert lowerBound.equals(upperBound); + return lowerBound; + } + + @Override + public IntInterval copy() { + return new IntInterval(lowerBound, upperBound); + } + + @Override + public void setToUnknown() { + this.lowerBound = MIN_VALUE; + this.upperBound = MAX_VALUE; + } + + @Pure + @Override + public IntInterval plus(@NotNull Interval other) { + long loSum = (long) lowerBound + (long) other.lowerBound; + long hiSum = (long) upperBound + (long) other.upperBound; + int lo = loSum > MAX_VALUE ? MAX_VALUE : (loSum < MIN_VALUE ? MIN_VALUE : (int) loSum); + int hi = hiSum > MAX_VALUE ? MAX_VALUE : (hiSum < MIN_VALUE ? MIN_VALUE : (int) hiSum); + return new IntInterval(lo, hi); + } + + @Pure + @Override + public IntInterval minus(@NotNull Interval other) { + long loSum = (long) lowerBound - (long) other.lowerBound; + long hiSum = (long) upperBound - (long) other.upperBound; + int lo = loSum > MAX_VALUE ? MAX_VALUE : (loSum < MIN_VALUE ? MIN_VALUE : (int) loSum); + int hi = hiSum > MAX_VALUE ? MAX_VALUE : (hiSum < MIN_VALUE ? MIN_VALUE : (int) hiSum); + return new IntInterval(lo, hi); + } + + @Pure + @Override + public IntInterval times(@NotNull Interval other) { + long p1 = (long) lowerBound * other.lowerBound; + long p2 = (long) lowerBound * other.upperBound; + long p3 = (long) upperBound * other.lowerBound; + long p4 = (long) upperBound * other.upperBound; + long loLong = Math.min(Math.min(p1, p2), Math.min(p3, p4)); + long hiLong = Math.max(Math.max(p1, p2), Math.max(p3, p4)); + int lo = loLong > MAX_VALUE ? MAX_VALUE : (loLong < MIN_VALUE ? MIN_VALUE : (int) loLong); + int hi = hiLong > MAX_VALUE ? MAX_VALUE : (hiLong < MIN_VALUE ? MIN_VALUE : (int) hiLong); + return new IntInterval(lo, hi); + } + + @Pure + @Override + public IntInterval divided(@NotNull Interval other) { + if (other.lowerBound <= 0 && other.upperBound >= 0) { + return new IntInterval(MIN_VALUE, MAX_VALUE); + } + long p1 = (lowerBound == MIN_VALUE && other.lowerBound == -1) ? (long) MAX_VALUE : (long) lowerBound / (long) other.lowerBound; + long p2 = (lowerBound == MIN_VALUE && other.upperBound == -1) ? (long) MAX_VALUE : (long) lowerBound / (long) other.upperBound; + long p3 = (upperBound == MIN_VALUE && other.lowerBound == -1) ? (long) MAX_VALUE : (long) upperBound / (long) other.lowerBound; + long p4 = (upperBound == MIN_VALUE && other.upperBound == -1) ? (long) MAX_VALUE : (long) upperBound / (long) other.upperBound; + long loLong = Math.min(Math.min(p1, p2), Math.min(p3, p4)); + long hiLong = Math.max(Math.max(p1, p2), Math.max(p3, p4)); + int lo = loLong > MAX_VALUE ? MAX_VALUE : (loLong < MIN_VALUE ? MIN_VALUE : (int) loLong); + int hi = hiLong > MAX_VALUE ? MAX_VALUE : (hiLong < MIN_VALUE ? MIN_VALUE : (int) hiLong); + return new IntInterval(lo, hi); + } + + @Pure + @Override + public BooleanValue equal(@NotNull Interval other) { + if (lowerBound.equals(upperBound) && other.lowerBound.equals(other.upperBound) && lowerBound.equals(other.lowerBound)) { + return new BooleanValue(true); + } else if (upperBound < other.lowerBound || lowerBound > other.upperBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue notEqual(@NotNull Interval other) { + if (upperBound < other.lowerBound || lowerBound > other.upperBound) { + return new BooleanValue(true); + } else if (lowerBound.equals(upperBound) && other.lowerBound.equals(other.upperBound) && lowerBound.equals(other.lowerBound)) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue smaller(@NotNull Interval other) { + if (upperBound < other.lowerBound) { + return new BooleanValue(true); + } else if (lowerBound >= other.upperBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue smallerEqual(@NotNull Interval other) { + if (upperBound <= other.lowerBound) { + return new BooleanValue(true); + } else if (lowerBound > other.upperBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue bigger(@NotNull Interval other) { + if (lowerBound > other.upperBound) { + return new BooleanValue(true); + } else if (upperBound <= other.lowerBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue biggerEqual(@NotNull Interval other) { + if (upperBound <= other.lowerBound) { + return new BooleanValue(true); + } else if (lowerBound > other.upperBound) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Impure + @Override + public IntInterval plusPlus() { + long newLower = (long) lowerBound + 1; + long newUpper = (long) upperBound + 1; + lowerBound = newLower > MAX_VALUE ? MAX_VALUE : (int) newLower; + upperBound = newUpper > MAX_VALUE ? MAX_VALUE : (int) newUpper; + return this.copy(); + } + + @Impure + @Override + public IntInterval minusMinus() { + long newLower = (long) lowerBound - 1; + long newUpper = (long) upperBound - 1; + lowerBound = newLower < MIN_VALUE ? MIN_VALUE : (int) newLower; + upperBound = newUpper < MIN_VALUE ? MIN_VALUE : (int) newUpper; + return this.copy(); + } + + @Pure + @Override + public IntInterval unaryMinus() { + int newLower = (upperBound == Integer.MIN_VALUE) ? Integer.MAX_VALUE : -upperBound; + int newUpper = (lowerBound == Integer.MIN_VALUE) ? Integer.MAX_VALUE : -lowerBound; + return new IntInterval(newLower, newUpper); + } + + @Pure + @Override + public IntInterval abs() { + if (upperBound < 0) { + return new IntInterval(safeAbs(upperBound), safeAbs(lowerBound)); + } else if (lowerBound >= 0) { + return new IntInterval(lowerBound, upperBound); + } else { + int max = Math.max(safeAbs(lowerBound), safeAbs(upperBound)); + return new IntInterval(0, max); + } + } + + @Impure + @Override + public void merge(@NotNull Interval other) { + int smallerLowerBound = Math.min(this.lowerBound, other.lowerBound); + int largerUpperBound = Math.max(this.upperBound, other.upperBound); + assert smallerLowerBound <= largerUpperBound; + this.lowerBound = smallerLowerBound; + this.upperBound = largerUpperBound; + } + + @Override + public int compareTo(@NotNull Interval o) { + if (!this.lowerBound.equals(o.lowerBound)) { + return Integer.compare(this.lowerBound, o.lowerBound); + } else { + return Integer.compare(this.upperBound, o.upperBound); + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/Interval.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/Interval.java new file mode 100644 index 0000000000..a014560b37 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/Interval.java @@ -0,0 +1,172 @@ +package de.jplag.java_cpg.ai.variables.values.numbers.helpers; + +import org.checkerframework.dataflow.qual.Impure; +import org.checkerframework.dataflow.qual.Pure; +import org.jetbrains.annotations.NotNull; + +import de.jplag.java_cpg.ai.variables.values.BooleanValue; + +/** + * Abstract class representing an interval of numbers. + * @author ujiqk + * @version 1.0 + * @param the type of number (e.g., Integer, Double) + */ +public abstract class Interval> implements Comparable> { + + protected T lowerBound; + protected T upperBound; + + /** + * @return if exact information is available. + */ + public abstract boolean getInformation(); + + /** + * @return the exact value. Only valid if getInformation() returns true. + */ + public abstract T getValue(); + + /** + * Creates a deep copy of this interval. + * @return a new Interval instance with the same bounds. + */ + public abstract Interval copy(); + + /** + * Sets this interval to represent an unknown value. + */ + public abstract void setToUnknown(); + + /** + * @param other the other interval to add. + * @return the resulting interval + */ + @Pure + public abstract Interval plus(@NotNull Interval other); + + /** + * @param other the other interval to subtract. + * @return the resulting interval + */ + @Pure + public abstract Interval minus(@NotNull Interval other); + + /** + * @param other the other interval to multiply. + * @return the resulting interval + */ + @Pure + public abstract Interval times(@NotNull Interval other); + + /** + * @param other the other interval to divide. + * @return the resulting interval + */ + @Pure + public abstract Interval divided(@NotNull Interval other); + + /** + * @param other the other interval to check equality. + * @return if the intervals are equal + */ + @Pure + public abstract BooleanValue equal(@NotNull Interval other); + + /** + * @param other the other interval to check inequality. + * @return if the intervals are not equal + */ + @Pure + public abstract BooleanValue notEqual(@NotNull Interval other); + + /** + * @param other the other interval to compare. + * @return if this interval is smaller than the other + */ + @Pure + public abstract BooleanValue smaller(@NotNull Interval other); + + /** + * @param other the other interval to compare. + * @return if this interval is smaller than or equal to the other + */ + @Pure + public abstract BooleanValue smallerEqual(@NotNull Interval other); + + /** + * @param other the other interval to compare. + * @return if this interval is bigger than the other + */ + @Pure + public abstract BooleanValue bigger(@NotNull Interval other); + + /** + * @param other the other interval to compare. + * @return if this interval is bigger than or equal to the other + */ + @Pure + public abstract BooleanValue biggerEqual(@NotNull Interval other); + + /** + * @return the interval with all values incremented by one + */ + @Impure + public abstract Interval plusPlus(); + + /** + * @return the interval with all values decremented by one + */ + @Impure + public abstract Interval minusMinus(); + + /** + * @return the negated interval + */ + @Pure + public abstract Interval unaryMinus(); + + /** + * @return the absolute value of the interval + */ + @Pure + public abstract Interval abs(); + + /** + * Merges another interval into this one. + * @param other the other interval to merge + */ + @Impure + public abstract void merge(@NotNull Interval other); + + /** + * @return the lower bound of this interval + */ + public T getLowerBound() { + return lowerBound; + } + + /** + * @param lowerBound the new lower bound + */ + public void setLowerBound(@NotNull T lowerBound) { + assert lowerBound.compareTo(upperBound) <= 0; + this.lowerBound = lowerBound; + } + + /** + * @return the upper bound of this interval + */ + public T getUpperBound() { + return upperBound; + } + + /** + * @param upperBound the new upper bound + */ + public void setUpperBound(T upperBound) { + assert lowerBound.compareTo(upperBound) <= 0; + this.upperBound = upperBound; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/IStringValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/IStringValue.java new file mode 100644 index 0000000000..18e9786fea --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/IStringValue.java @@ -0,0 +1,25 @@ +package de.jplag.java_cpg.ai.variables.values.string; + +import org.jetbrains.annotations.Nullable; + +import de.jplag.java_cpg.ai.variables.values.IJavaObject; + +/** + * Strings are objects with added functionality. + * @author ujiqk + * @version 1.0 + */ +public interface IStringValue extends IJavaObject { + + /** + * @return true if the string value has definite information (i.e., a known value), false otherwise. + */ + boolean getInformation(); + + /** + * @return if known, the string value. + */ + @Nullable + String getValue(); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringCharInclValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringCharInclValue.java new file mode 100644 index 0000000000..e6d5bd9631 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringCharInclValue.java @@ -0,0 +1,270 @@ +package de.jplag.java_cpg.ai.variables.values.string; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +/** + * String representation using character inclusion sets. + * @author ujiqk + * @version 1.0 + */ +public class StringCharInclValue extends JavaObject implements IStringValue { + + // String=null <--> certainContained=null + /** + * Characters that are definitely contained in the string. Null if string is null. + */ + @Nullable + Set certainContained; + /** + * Characters that may be contained in the string. + */ + Set maybeContained; + + /** + * A string value with no information. + */ + public StringCharInclValue() { + super(Type.STRING); + certainContained = new HashSet<>(); + maybeContained = allCharactersSet(); + } + + /** + * Constructor for StringCharInclValue with exact information. + * @param value the string value. + */ + public StringCharInclValue(@Nullable String value) { + super(Type.STRING); + maybeContained = new HashSet<>(); + if (value == null) { + certainContained = null; + return; + } + certainContained = new HashSet<>(); + for (char c : value.toCharArray()) { + certainContained.add(c); + } + } + + /** + * Constructor for StringCharInclValue with possible values. + * @param possibleValues the set of possible string values. + */ + public StringCharInclValue(@NotNull Set possibleValues) { + super(Type.STRING); + certainContained = new HashSet<>(); + maybeContained = new HashSet<>(); + for (String value : possibleValues) { + for (char c : value.toCharArray()) { + maybeContained.add(c); + } + } + } + + private StringCharInclValue(@Nullable Set certainContained, Set maybeContained) { + super(Type.STRING); + this.certainContained = certainContained; + this.maybeContained = maybeContained; + } + + @Override + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "length" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(Type.INT); + } + case "parseInt" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof IStringValue; + return Value.valueFactory(Type.INT); + } + case "parseDouble" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof IStringValue; + return Value.valueFactory(Type.FLOAT); + } + case "startsWith" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof IStringValue; + return Value.valueFactory(Type.BOOLEAN); + } + case "equals" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, new StringCharInclValue()); + } + StringCharInclValue other = (StringCharInclValue) paramVars.getFirst(); + if (!Objects.equals(this.certainContained, other.certainContained) && this.maybeContained.isEmpty() + && other.maybeContained.isEmpty()) { + return Value.valueFactory(false); + } else { + return Value.valueFactory(Type.BOOLEAN); + } + } + case "toUpperCase" -> { + Set newCertain = new HashSet<>(); + if (this.certainContained != null) { + for (Character c : certainContained) { + newCertain.add(Character.toUpperCase(c)); + } + } + Set newMaybe = new HashSet<>(); + for (Character c : maybeContained) { + newMaybe.add(Character.toUpperCase(c)); + } + return new StringCharInclValue(newCertain, newMaybe); + } + case "charAt" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof INumberValue; + return Value.valueFactory(Type.CHAR); + } + case "trim" -> { + return new StringCharInclValue(); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @Override + public Value accessField(@NotNull String fieldName) { + throw new UnsupportedOperationException("Access field not supported in StringValue"); + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (other instanceof VoidValue) { + return new StringCharInclValue(); + } + if (operator.equals("+") && other instanceof INumberValue inumbervalue) { + if (inumbervalue.getInformation()) { // ToDo: what to do with intervals? + Set newCertain = certainContained == null ? null : new HashSet<>(certainContained); + assert newCertain != null; + newCertain.addAll(doubleToCharSet(inumbervalue.getValue())); + return new StringCharInclValue(newCertain, new HashSet<>(maybeContained)); + } else { + return new StringCharInclValue(); + } + } else if (operator.equals("+") && other instanceof StringCharInclValue stringValue) { + assert !(this.certainContained == null || stringValue.certainContained == null); + Set newCertain = new HashSet<>(this.certainContained); + newCertain.addAll(stringValue.certainContained); + Set newMaybe = new HashSet<>(this.maybeContained); + newMaybe.addAll(stringValue.maybeContained); + return new StringCharInclValue(newCertain, newMaybe); + } + throw new UnsupportedOperationException("Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + + @NotNull + @Override + public JavaObject copy() { + return new StringCharInclValue(certainContained == null ? null : new HashSet<>(certainContained), new HashSet<>(maybeContained)); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + this.certainContained = new HashSet<>(); + this.maybeContained = allCharactersSet(); + return; + } + assert other instanceof StringCharInclValue; + StringCharInclValue otherString = (StringCharInclValue) other; + if (this.certainContained == null || otherString.certainContained == null) { + this.certainContained = null; + } else { + Set removed = new HashSet<>(this.certainContained); + this.certainContained.retainAll(otherString.certainContained); + removed.removeAll(this.certainContained); + this.maybeContained.addAll(removed); + Set otherCertainNotInThis = new HashSet<>(otherString.certainContained); + otherCertainNotInThis.removeAll(this.certainContained); + this.maybeContained.addAll(otherCertainNotInThis); + this.maybeContained.addAll(otherString.maybeContained); + } + } + + @Override + public void setToUnknown() { + this.certainContained = new HashSet<>(); + this.maybeContained = allCharactersSet(); + } + + @Override + public void setInitialValue() { + this.certainContained = null; + this.maybeContained = new HashSet<>(); + } + + @NotNull + private Set allCharactersSet() { + Set allChars = new HashSet<>(); + for (char c = Character.MIN_VALUE; c < Character.MAX_VALUE; c++) { + allChars.add(c); + } + return allChars; + } + + @NotNull + private Set doubleToCharSet(double value) { + Set charSet = new HashSet<>(); + String str = Double.toString(value); + for (char c : str.toCharArray()) { + charSet.add(c); + } + return charSet; + } + + /** + * @return true if the string value has definite information (i.e., a known value), false otherwise. + */ + public boolean getInformation() { + // always false since order is never known + return false; + } + + /** + * @return if known, the string value. + */ + public String getValue() { + assert getInformation(); + return null; + } + + /** + * Only for testing purposes. + * @return set of certainly contained characters. + */ + @Nullable + @TestOnly + public Set getCertainContained() { + return this.certainContained; + } + + /** + * Only for testing purposes. + * @return set of maybe contained characters. + */ + @TestOnly + public Set getMaybeContained() { + return this.maybeContained; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringRegexValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringRegexValue.java new file mode 100644 index 0000000000..add9b95e90 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringRegexValue.java @@ -0,0 +1,511 @@ +package de.jplag.java_cpg.ai.variables.values.string; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.IJavaObject; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.NullValue; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.ai.variables.values.string.regex.RegexChar; +import de.jplag.java_cpg.ai.variables.values.string.regex.RegexChars; +import de.jplag.java_cpg.ai.variables.values.string.regex.RegexItem; + +/** + * String representation using regex-like structures. Strings are objects with added functionality. + * @author ujiqk + * @version 1.0 + */ +public class StringRegexValue extends JavaObject implements IStringValue { + + // String=null <--> contentRegex=null + @Nullable + private List contentRegex; + private boolean unknown; + + /** + * A string value with no information. + */ + public StringRegexValue() { + super(Type.STRING); + unknown = true; + } + + /** + * A string value with exact information. + * @param value The exact string value, null for null. + */ + public StringRegexValue(@Nullable String value) { + super(Type.STRING); + if (value == null) { + contentRegex = null; + return; + } + unknown = false; + contentRegex = new ArrayList<>(); + for (char c : value.toCharArray()) { + contentRegex.add(new RegexChar(c)); + } + } + + /** + * A string value with possible values. + * @param possibleValues The possible string values. + */ + public StringRegexValue(@NotNull Set possibleValues) { + super(Type.STRING); + unknown = false; + contentRegex = new ArrayList<>(); + for (String possibleValue : possibleValues) { + List possibleValueRegex = new ArrayList<>(); + for (char c : possibleValue.toCharArray()) { + possibleValueRegex.add(new RegexChar(c)); + } + appendAtPos(contentRegex, possibleValueRegex, contentRegex.size() - 1); + } + } + + private StringRegexValue(@Nullable List contentRegex, boolean unknown) { + super(Type.STRING); + this.contentRegex = contentRegex; + this.unknown = unknown; + } + + @NotNull + private static List appendAtPos(@NotNull List original, @NotNull List other, int i) { + int length = original.size(); + i++; + for (int j = 0; j < other.size(); j++) { + if (i < length) { + original.get(i).merge(other.get(j)); + } else { + original.add(other.get(j)); + } + i++; + } + return original; + } + + @Override + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "length" -> { + assert paramVars == null || paramVars.isEmpty(); + if (unknown || contentRegex == null) { + return Value.valueFactory(Type.INT); + } + if ((contentRegex.getLast() instanceof RegexChars chars && chars.canBeEmpty())) { + return Value.valueFactory(Type.INT); // ToDo could return int interval + } + return Value.valueFactory(contentRegex.size()); + } + case "parseInt" -> { + assert paramVars.size() == 1; + if (unknown || contentRegex == null) { + return Value.valueFactory(Type.INT); + } + List possibleChars = new ArrayList<>(); + for (RegexItem item : contentRegex) { + if (item instanceof RegexChars) { + return Value.valueFactory(Type.INT); + } else if (item instanceof RegexChar regexChar) { + possibleChars.add(regexChar.getContent()); + } + } + int number = Integer.parseInt(possibleChars.toString()); + return Value.valueFactory(number); + } + case "parseBoolean" -> { + assert paramVars.size() == 1; + if (unknown || contentRegex == null) { + return Value.valueFactory(Type.BOOLEAN); + } + List possibleChars = new ArrayList<>(); + for (RegexItem item : contentRegex) { + if (item instanceof RegexChars) { + return Value.valueFactory(Type.BOOLEAN); + } else if (item instanceof RegexChar regexChar) { + possibleChars.add(regexChar.getContent()); + } + } + String str = possibleChars.toString().toLowerCase(); + if (str.equals("true")) { + return Value.valueFactory(true); + } else if (str.equals("false")) { + return Value.valueFactory(false); + } else { + return Value.valueFactory(Type.BOOLEAN); + } + } + case "parseDouble" -> { + assert paramVars.size() == 1; + if (unknown || contentRegex == null) { + return Value.valueFactory(Type.INT); + } + List possibleChars = new ArrayList<>(); + for (RegexItem item : contentRegex) { + if (item instanceof RegexChars) { + return Value.valueFactory(Type.INT); + } else if (item instanceof RegexChar regexChar) { + possibleChars.add(regexChar.getContent()); + } + } + double number = Double.parseDouble(possibleChars.toString()); + return Value.valueFactory(number); + } + case "startsWith" -> { + assert paramVars.size() == 1; + StringRegexValue other = (StringRegexValue) paramVars.getFirst(); + if (this.unknown || other.unknown) { + return Value.valueFactory(Type.BOOLEAN); + } + assert this.contentRegex != null && other.contentRegex != null; + if (this.contentRegex.size() < other.contentRegex.size()) { + return Value.valueFactory(false); + } + boolean unknownMatch = false; + for (int i = 0; i < other.contentRegex.size(); i++) { + RegexItem thisItem = this.contentRegex.get(i); + RegexItem otherItem = other.contentRegex.get(i); + if (thisItem instanceof RegexChar thisChar && otherItem instanceof RegexChar otherChar) { + if (thisChar.getContent() == otherChar.getContent()) { + // match + } else { + return Value.valueFactory(false); + } + } else { + unknownMatch = true; + } + } + if (unknownMatch) { + return Value.valueFactory(Type.BOOLEAN); + } else { + return Value.valueFactory(true); + } + } + case "equals" -> { + assert paramVars.size() == 1; + StringRegexValue other = (StringRegexValue) paramVars.getFirst(); + if (this.unknown || other.unknown) { + return Value.valueFactory(Type.BOOLEAN); + } + assert this.contentRegex != null && other.contentRegex != null; + if (this.contentRegex.size() != other.contentRegex.size()) { + return Value.valueFactory(false); + } + boolean unknownMatch = false; + for (int i = 0; i < other.contentRegex.size(); i++) { + RegexItem thisItem = this.contentRegex.get(i); + RegexItem otherItem = other.contentRegex.get(i); + if (thisItem instanceof RegexChar thisChar && otherItem instanceof RegexChar otherChar) { + if (thisChar.getContent() == otherChar.getContent()) { + // match + } else { + return Value.valueFactory(false); + } + } else { + unknownMatch = true; + } + } + if (unknownMatch) { + return Value.valueFactory(Type.BOOLEAN); + } else { + return Value.valueFactory(true); + } + } + case "toUpperCase" -> { + if (unknown) { + return new StringRegexValue(null, true); + } + if (contentRegex == null) { + return new StringRegexValue(null, false); + } + List newContentRegex = new ArrayList<>(); + for (RegexItem item : contentRegex) { + if (item instanceof RegexChar regexChar) { + newContentRegex.add(new RegexChar(Character.toUpperCase(regexChar.getContent()))); + } else if (item instanceof RegexChars regexChars) { + List upperChars = new ArrayList<>(); + for (Character c : regexChars.getContent()) { + if (c != null) { + upperChars.add(Character.toUpperCase(c)); + } else { + upperChars.add(null); + } + } + newContentRegex.add(new RegexChars(upperChars)); + } + } + return new StringRegexValue(newContentRegex, false); + } + case "isBlank" -> { // all whitespace or empty or null + if (unknown) { + return Value.valueFactory(Type.BOOLEAN); + } + if (contentRegex == null) { + return Value.valueFactory(true); + } + boolean unknownMatch = false; + for (RegexItem item : contentRegex) { + if (item instanceof RegexChar regexChar) { + if (!Character.isWhitespace(regexChar.getContent())) { + return Value.valueFactory(false); + } + } else if (item instanceof RegexChars regexChars) { + for (Character c : regexChars.getContent()) { + if (c == null || !Character.isWhitespace(c)) { + unknownMatch = true; + } + } + } + } + if (unknownMatch) { + return Value.valueFactory(Type.BOOLEAN); + } else { + return Value.valueFactory(true); + } + } + case "indexOf" -> { // Returns the index within this string of the first occurrence of the specified character. -1 if not found. + if (unknown) { + return Value.valueFactory(Type.INT); + } + if (contentRegex == null) { + return Value.valueFactory(-1); + } + boolean unknownMatch = false; + for (RegexItem item : contentRegex) { + if (item instanceof RegexChar regexChar) { + if (!Character.isWhitespace(regexChar.getContent())) { + return Value.valueFactory(false); + } + } else if (item instanceof RegexChars regexChars) { + for (Character c : regexChars.getContent()) { + if (c == null || !Character.isWhitespace(c)) { + unknownMatch = true; + } + } + } + } + if (unknownMatch) { + return Value.valueFactory(Type.BOOLEAN); + } else { + return Value.valueFactory(true); + } + } + case "substring" -> { // (int begin) or (int begin, int end) + assert paramVars != null && (paramVars.size() == 1 || paramVars.size() == 2); + if (unknown) { + return new StringRegexValue(null, true); + } + if (contentRegex == null) { + return new StringRegexValue(null, false); + } + int begin = (int) ((INumberValue) paramVars.get(0)).getValue(); + int end = contentRegex.size(); + if (paramVars.size() == 2) { + end = (int) ((INumberValue) paramVars.get(1)).getValue(); + } + begin = Math.clamp(begin, 0, contentRegex.size()); + end = Math.clamp(end, begin, contentRegex.size()); + List sub = new ArrayList<>(); + for (int i = begin; i < end; i++) { + sub.add(contentRegex.get(i)); + } + return new StringRegexValue(sub, false); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @Override + public Value accessField(@NotNull String fieldName) { + throw new UnsupportedOperationException("Access field not supported in StringRegexValue"); + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (other instanceof VoidValue) { + return new StringRegexValue(); + } + if (operator.equals("+") && other instanceof INumberValue inumbervalue) { + assert this.contentRegex != null; + if (this.unknown || !inumbervalue.getInformation()) { + this.unknown = true; + return new StringRegexValue(null, true); + } + List newContentRegex = new ArrayList<>(contentRegex); + appendAtPos(newContentRegex, doubleToRegex(inumbervalue.getValue()), contentRegex.size() - 1); + for (int i = contentRegex.size() - 1; i >= 0; i--) { + if (newContentRegex.get(i) instanceof RegexChars chars && chars.canBeEmpty()) { + appendAtPos(newContentRegex, doubleToRegex(inumbervalue.getValue()), i); + } else { + break; + } + } + return new StringRegexValue(newContentRegex, false); + } else if (operator.equals("+") && other instanceof StringRegexValue stringValue) { + if (this.contentRegex == null || stringValue.contentRegex == null) { + return new StringRegexValue(null, false); + } + if (this.unknown || stringValue.unknown) { + this.unknown = true; + return new StringRegexValue(null, true); + } + // put at the end of each possible list end (without empty chars) + List newContentRegex = new ArrayList<>(contentRegex); + appendAtPos(newContentRegex, stringValue.contentRegex, contentRegex.size() - 1); + for (int i = contentRegex.size() - 1; i >= 0; i--) { + if (newContentRegex.get(i) instanceof RegexChars chars && chars.canBeEmpty()) { + appendAtPos(newContentRegex, stringValue.contentRegex, i); + } else { + break; + } + } + return new StringRegexValue(newContentRegex, false); + } else if (operator.equals("==") && other instanceof NullValue) { + if (!unknown) { + return Value.valueFactory(this.contentRegex == null); + } else { + return Value.valueFactory(Type.BOOLEAN); + } + } + throw new UnsupportedOperationException("Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + + @NotNull + @Override + public JavaObject copy() { + return new StringRegexValue(contentRegex == null ? null : new ArrayList<>(contentRegex), unknown); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue || other instanceof IJavaObject) { + contentRegex = new ArrayList<>(); + unknown = true; + return; + } + if (other instanceof NullValue) { + if (contentRegex == null) { + // keep null + } else { + this.unknown = true; + } + return; + } + assert other instanceof StringRegexValue; + StringRegexValue otherString = (StringRegexValue) other; + if (this.contentRegex == null || otherString.contentRegex == null) { + this.contentRegex = null; + return; + } + if (this.unknown || otherString.unknown) { + this.unknown = true; + return; + } + int maxLength = Math.max(this.contentRegex.size(), otherString.contentRegex.size()); + int minLength = Math.min(this.contentRegex.size(), otherString.contentRegex.size()); + for (int i = 0; i < minLength; i++) { + this.contentRegex.get(i).merge(otherString.contentRegex.get(i)); + } + if (this.contentRegex.size() < otherString.contentRegex.size()) { + for (int i = minLength; i < maxLength; i++) { + RegexItem otherRegx = otherString.contentRegex.get(i); + otherRegx.merge(null); + this.contentRegex.add(otherRegx); + } + } else { + for (int i = minLength; i < maxLength; i++) { + this.contentRegex.get(i).merge(null); + } + } + } + + @Override + public void setToUnknown() { + contentRegex = new ArrayList<>(); + unknown = true; + } + + @Override + public void setInitialValue() { + this.contentRegex = null; + unknown = false; + } + + @NotNull + private Set allCharactersSet() { + Set allChars = new HashSet<>(); + for (char c = Character.MIN_VALUE; c < Character.MAX_VALUE; c++) { + allChars.add(c); + } + return allChars; + } + + @NotNull + private List doubleToRegex(double value) { + List regexList = new ArrayList<>(); + String str = Double.toString(value); + for (char c : str.toCharArray()) { + regexList.add(new RegexChar(c)); + } + return regexList; + } + + /** + * @return true if the string value has definite information (i.e., a known value), false otherwise. + */ + public boolean getInformation() { + if (unknown) { + return false; + } + if (contentRegex == null) { + return true; // null is a definite value + } + for (RegexItem item : contentRegex) { + if (item instanceof RegexChars) { + return false; + } + } + return true; + } + + /** + * @return if known, the string value. + */ + public String getValue() { + assert getInformation(); + if (contentRegex == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (RegexItem item : contentRegex) { + if (item instanceof RegexChar regexChar) { + sb.append(regexChar.getContent()); + } + } + return sb.toString(); + } + + /** + * Should only be used in tests! + * @return the regex representation of the string content. Null if the string is null. + */ + @Nullable + @TestOnly + public List getContentRegex() { + return contentRegex; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringValue.java new file mode 100644 index 0000000000..e0fd114f64 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringValue.java @@ -0,0 +1,294 @@ +package de.jplag.java_cpg.ai.variables.values.string; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.variables.Type; +import de.jplag.java_cpg.ai.variables.values.BooleanValue; +import de.jplag.java_cpg.ai.variables.values.IJavaObject; +import de.jplag.java_cpg.ai.variables.values.IValue; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.NullValue; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.arrays.IJavaArray; +import de.jplag.java_cpg.ai.variables.values.arrays.JavaArray; +import de.jplag.java_cpg.ai.variables.values.chars.ICharValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +/** + * String representation. Strings are objects with added functionality. + * @author ujiqk + * @version 1.0 + */ +public class StringValue extends JavaObject implements IStringValue { + + private boolean information; + private String value; + + /** + * A string value with no information. + */ + public StringValue() { + super(Type.STRING); + information = false; + } + + /** + * A string value with exact information. + * @param value the string value + */ + public StringValue(String value) { + super(Type.STRING); + this.value = value; + information = true; + } + + private StringValue(String value, boolean information) { + super(Type.STRING); + this.value = value; + this.information = information; + } + + @Override + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) { + switch (methodName) { + case "length" -> { + assert paramVars == null || paramVars.isEmpty(); + if (information) { + return Value.valueFactory(value.length()); + } else { + return Value.valueFactory(Type.INT); + } + } + case "parseInt" -> { + assert paramVars.size() == 1; + StringValue str = (StringValue) paramVars.getFirst(); + if (str.getInformation()) { + return Value.valueFactory(Integer.parseInt(str.getValue())); + } else { + return Value.valueFactory(Type.INT); + } + } + case "parseDouble" -> { + assert paramVars.size() == 1; + StringValue str = (StringValue) paramVars.getFirst(); + if (str.getInformation()) { + return Value.valueFactory(Double.parseDouble(str.getValue())); + } else { + return Value.valueFactory(Type.FLOAT); + } + } + case "startsWith" -> { + assert paramVars.size() == 1; + StringValue prefix = (StringValue) paramVars.getFirst(); + if (information && prefix.getInformation()) { + return Value.valueFactory(value.startsWith(prefix.getValue())); + } else { + return Value.valueFactory(Type.BOOLEAN); + } + } + case "equals" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(Type.STRING)); + } + StringValue other = (StringValue) paramVars.getFirst(); + if (information && other.getInformation()) { + return Value.valueFactory(value.equals(other.getValue())); + } else { + return Value.valueFactory(Type.BOOLEAN); + } + } + case "split" -> { // public String[] split(String regex) + assert paramVars.size() == 1; + StringValue regexValue = (StringValue) paramVars.getFirst(); + if (!information || !regexValue.getInformation()) { + return Value.getNewArayValue(Type.STRING); + } + assert regexValue.getValue() != null && value != null; + String[] parts = value.split(regexValue.getValue()); + IJavaArray array = Value.getNewArayValue(Type.STRING); + for (int i = 0; i < parts.length; i++) { + array.arrayAssign((INumberValue) Value.valueFactory(i), new StringValue(parts[i])); + } + return array; + } + case "charAt" -> { // public char charAt(int index) + assert paramVars.size() == 1; + INumberValue indexValue = (INumberValue) paramVars.getFirst(); + if (!information || !indexValue.getInformation()) { + return Value.valueFactory(Type.CHAR); + } + assert value != null; + double index = indexValue.getValue(); + if (index < 0 || index >= value.length()) { + return new VoidValue(); + } + return Value.valueFactory(value.charAt((int) index)); + } + case "toCharArray" -> { // public char[] toCharArray() + assert paramVars == null || paramVars.isEmpty(); + if (!information) { + return new JavaArray(Type.CHAR); + } + assert value != null; + char[] chars = value.toCharArray(); + JavaArray array = new JavaArray(Type.CHAR); + for (int i = 0; i < chars.length; i++) { + array.arrayAssign((INumberValue) Value.valueFactory(i), Value.valueFactory(chars[i])); + } + return array; + } + case "concat" -> { // public String concat(String str) + assert paramVars.size() == 1; + StringValue str = (StringValue) paramVars.getFirst(); + if (information && str.getInformation()) { + return new StringValue(this.value + str.getValue()); + } else { + return new StringValue(); + } + } + case "substring" -> { // public String substring(int beginIndex, int endIndex) + assert paramVars.size() == 2; + INumberValue beginIndexValue = (INumberValue) paramVars.get(0); + INumberValue endIndexValue = (INumberValue) paramVars.get(1); + if (information && beginIndexValue.getInformation() && endIndexValue.getInformation()) { + int beginIndex = (int) beginIndexValue.getValue(); + int endIndex = (int) endIndexValue.getValue(); + if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) { + return new VoidValue(); + } + return new StringValue(this.value.substring(beginIndex, endIndex)); + } else { + return new StringValue(); + } + } + case "trim" -> { // public String trim() + assert paramVars == null || paramVars.isEmpty(); + if (information) { + return new StringValue(this.value.trim()); + } else { + return new StringValue(); + } + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @Override + public Value accessField(@NotNull String fieldName) { + throw new UnsupportedOperationException("Access field not supported in StringValue"); + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (other instanceof VoidValue) { + return new VoidValue(); + } + if (operator.equals("+") && other instanceof INumberValue inumbervalue) { + if (information && inumbervalue.getInformation()) { + return new StringValue(this.value + inumbervalue.getValue()); + } else { + return new StringValue(); + } + } else if (operator.equals("+") && other instanceof IStringValue stringValue) { + if (information && stringValue.getInformation()) { + return new StringValue(this.value + stringValue.getValue()); + } else { + return new StringValue(); + } + } else if (operator.equals("+") && other instanceof ICharValue charValue) { + if (information && charValue.getInformation()) { + return new StringValue(this.value + charValue.getValue()); + } else { + return new StringValue(); + } + } else if (operator.equals("+") && other instanceof BooleanValue boolValue) { + if (information && boolValue.getInformation()) { + return new StringValue(this.value + boolValue.getValue()); + } else { + return new StringValue(); + } + } else if (operator.equals("==") && other instanceof NullValue) { + if (information) { + return Value.valueFactory(this.value == null); + } else { + return Value.valueFactory(Type.BOOLEAN); + } + } else if (operator.equals("!=") && other instanceof NullValue) { + if (information) { + return Value.valueFactory(this.value != null); + } else { + return Value.valueFactory(Type.BOOLEAN); + } + } else if (operator.equals("==") && other instanceof IStringValue otherString) { + if (information && otherString.getInformation()) { + return Value.valueFactory(java.util.Objects.equals(this.value, otherString.getValue())); + } else { + return Value.valueFactory(Type.BOOLEAN); + } + } else if (operator.equals("+") && other instanceof IJavaObject javaObject) { + // case: JavaObject with toString method + IValue toStringResult = javaObject.callMethod("toString", List.of(), null); + if (toStringResult instanceof IStringValue otherStringFromObject && information && otherStringFromObject.getInformation()) { + return new StringValue(this.value + otherStringFromObject.getValue()); + } + return new StringValue(); + } + throw new UnsupportedOperationException("Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + + @NotNull + @Override + public JavaObject copy() { + return new StringValue(value, information); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + this.information = false; + this.value = null; + return; + } + assert other instanceof StringValue; + StringValue otherString = (StringValue) other; + if (this.information && otherString.information && java.util.Objects.equals(this.value, otherString.value)) { + // keep value + } else { + this.information = false; + this.value = null; + } + } + + @Override + public void setToUnknown() { + this.information = false; + this.value = null; + } + + @Override + public void setInitialValue() { + information = true; + value = null; + } + + /** + * @return true if the string value has definite information (i.e., a known value), false otherwise. + */ + public boolean getInformation() { + return information; + } + + /** + * @return if known, the string value. + */ + public String getValue() { + assert information; + return value; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexChar.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexChar.java new file mode 100644 index 0000000000..8b53e5cf41 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexChar.java @@ -0,0 +1,63 @@ +package de.jplag.java_cpg.ai.variables.values.string.regex; + +import java.util.Arrays; +import java.util.List; + +/** + * {@link RegexItem} representing a single character. + * @author ujiqk + * @version 1.0 + */ +public class RegexChar extends RegexItem { + + private final char content; + + /** + * Constructor for {@link RegexChar}. + * @param content the character. + */ + public RegexChar(char content) { + this.content = content; + } + + /** + * @return the character. + */ + public char getContent() { + return content; + } + + @Override + public RegexItem merge(RegexItem other) { + switch (other) { + case null -> { + return new RegexChars(Arrays.asList(this.content, null)); + } + case RegexChars o -> { + return o.merge(this); + } + case RegexChar o -> { + if (o.content == this.content) { + return this; + } + return new RegexChars(List.of(this.content, o.content)); + } + default -> throw new IllegalStateException("Unexpected value: " + other); + } + } + + @Override + public int hashCode() { + return content; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + + RegexChar regexChar = (RegexChar) o; + return content == regexChar.content; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexChars.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexChars.java new file mode 100644 index 0000000000..2edd935b0d --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexChars.java @@ -0,0 +1,61 @@ +package de.jplag.java_cpg.ai.variables.values.string.regex; + +import java.util.List; + +/** + * {@link RegexItem} representing multiple characters. + * @author ujiqk + * @version 1.0 + */ +public class RegexChars extends RegexItem { + + // null: represents an empty-non existent char + private final List content; + + /** + * Constructor for {@link RegexChars}. + * @param content the list of characters; null represents an empty-non-existent char. + */ + public RegexChars(List content) { + this.content = content; + } + + /** + * @return the list of characters; null represents an empty-non-existent char. + */ + public List getContent() { + return content; + } + + @Override + public RegexItem merge(RegexItem other) { + switch (other) { + case null -> { + content.add(null); + return this; + } + case RegexChars o -> { + content.addAll(o.getContent()); + return this; + } + case RegexChar o -> { + content.add(o.getContent()); + return this; + } + default -> throw new IllegalStateException("Unexpected value: " + other); + } + } + + /** + * @return whether this regex item can represent an empty string. + */ + public boolean canBeEmpty() { + for (Character c : content) { + if (c == null) { + return true; + } + } + return false; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexItem.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexItem.java new file mode 100644 index 0000000000..9d3f463c6a --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/regex/RegexItem.java @@ -0,0 +1,29 @@ +package de.jplag.java_cpg.ai.variables.values.string.regex; + +import org.jetbrains.annotations.NotNull; + +/** + * Simplified regex syntax with only single or multiple characters. + * @author ujiqk + * @version 1.0 + */ +public abstract class RegexItem { + + /** + * Merges two regex items. + * @param one the first regex item. + * @param two the second regex item. + * @return the merged regex item. + */ + public static RegexItem merge(@NotNull RegexItem one, @NotNull RegexItem two) { + return one.merge(two); + } + + /** + * Merges this regex item with another regex item. + * @param other the other regex item. + * @return the merged regex item. + */ + public abstract RegexItem merge(RegexItem other); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/ATransformationPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/ATransformationPass.kt new file mode 100644 index 0000000000..972782cc6e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/ATransformationPass.kt @@ -0,0 +1,84 @@ +package de.jplag.java_cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.passes.TranslationResultPass +import de.jplag.java_cpg.transformation.GraphTransformation +import de.jplag.java_cpg.transformation.matching.CpgIsomorphismDetector +import de.jplag.java_cpg.transformation.matching.pattern.GraphPattern +import de.jplag.java_cpg.transformation.matching.pattern.Match +import de.jplag.java_cpg.transformation.operations.DummyNeighbor +import org.slf4j.Logger +import org.slf4j.LoggerFactory + + +/** + * A ATransformationPass is an abstract transformation pass. All transformation passes function the same way, but need + * to be separate classes to work with the CPG pipeline. + */ +abstract class ATransformationPass(ctx: TranslationContext) : TranslationResultPass(ctx) { + + val logger: Logger = LoggerFactory.getLogger(this::class.java) + + override fun accept(t: TranslationResult) { + val detector = CpgIsomorphismDetector() + val transformations = getPhaseSpecificTransformations() + for (transformation: GraphTransformation in transformations) { + detector.loadGraph(t) + instantiate(transformation, detector) + } + + } + + abstract fun getPhaseSpecificTransformations(): List + + /** + * Applies the given transformation to all the matches that the detector can find. + * @param The concrete node type of the target node/GraphTransformation/Match + */ + private fun instantiate(transformation: GraphTransformation, detector: CpgIsomorphismDetector) { + val sourcePattern: GraphPattern = transformation.sourcePattern + + var count = 0 + var invalidated: Boolean + do { + invalidated = false + var matches: List = detector.getMatches(sourcePattern) + + if (transformation.executionOrder == GraphTransformation.ExecutionOrder.DESCENDING_LOCATION) { + matches = matches.reversed(); + } + + for (match: Match in matches) { + // transformations may lead to other matches being invalidated + if (detector.validateMatch(match, sourcePattern)) { + count++ + transformation.apply(match, ctx) + } else { + invalidated = true + } + } + } while (invalidated) + + logger.info("%s: Found %d matches".format(transformation.name, count)) + } + + override fun cleanup() { + val dummy = DummyNeighbor.getInstance() + dummy.nextEOGEdges.removeIf { it.end == dummy } + dummy.prevEOGEdges.removeIf { it.start == dummy } + + dummy.nextEOGEdges.map { it.end }.toList().forEach { + val successors = it.nextEOG.distinct() + val predecessors = it.prevEOG.distinct() + if (successors.size == 1 && successors[0] == dummy + && predecessors.size == 1 && predecessors[0] == dummy + ) { + logger.debug("The node %s got isolated and will likely be removed.".format(it)) + dummy.nextEOGEdges.removeIf { e -> e.end == it } + dummy.prevEOGEdges.removeIf { e -> e.start == it } + } + } + } + +} \ No newline at end of file diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/AstTransformationPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/AstTransformationPass.kt new file mode 100644 index 0000000000..14c3a25d30 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/AstTransformationPass.kt @@ -0,0 +1,42 @@ +package de.jplag.java_cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass +import de.fraunhofer.aisec.cpg.passes.ImportResolver +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore +import de.jplag.java_cpg.transformation.GraphTransformation + +/** + * This pass handles the transformations working on the AST, i.e. before the EOG or DFG are built. + */ +@DependsOn(ImportResolver::class) +@ExecuteBefore(EvaluationOrderGraphPass::class) +class AstTransformationPass(ctx: TranslationContext) : ATransformationPass(ctx) { + + + override fun getPhaseSpecificTransformations(): List { + return transformations.toList() + } + + companion object { + @JvmStatic + val transformations: MutableList = ArrayList() + + @JvmStatic + fun registerTransformation(transformation: GraphTransformation) { + transformations.add(transformation) + } + + @JvmStatic + fun registerTransformations(newTransformations: Array) { + transformations.addAll(newTransformations) + } + + @JvmStatic + fun clearTransformations() { + transformations.clear() + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/CpgTransformationPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/CpgTransformationPass.kt new file mode 100644 index 0000000000..198135d3a1 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/CpgTransformationPass.kt @@ -0,0 +1,37 @@ +package de.jplag.java_cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.jplag.java_cpg.transformation.GraphTransformation + +/** + * This pass handles the transformations working on the CPG. + */ +@DependsOn(FixAstPass::class) +class CpgTransformationPass(ctx: TranslationContext) : ATransformationPass(ctx) { + + override fun getPhaseSpecificTransformations(): List { + return transformations.toList() + } + + companion object { + @JvmStatic + val transformations: MutableList = ArrayList() + + @JvmStatic + fun registerTransformation(transformation: GraphTransformation) { + transformations.add(transformation) + } + + @JvmStatic + fun registerTransformations(newTransformations: Array) { + transformations.addAll(newTransformations) + } + + @JvmStatic + fun clearTransformations() { + transformations.clear() + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/DfgSortPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/DfgSortPass.kt new file mode 100644 index 0000000000..08a77353e0 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/DfgSortPass.kt @@ -0,0 +1,1022 @@ +package de.jplag.java_cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.* +import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.statements.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.IncompleteType +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass +import de.fraunhofer.aisec.cpg.passes.TranslationUnitPass +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import de.jplag.java_cpg.token.CpgNodeListener +import de.jplag.java_cpg.token.CpgTokenType +import de.jplag.java_cpg.transformation.TransformationException +import de.jplag.java_cpg.transformation.matching.edges.CpgNthEdge +import de.jplag.java_cpg.transformation.matching.edges.Edges.BLOCK__STATEMENTS +import de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.desc +import de.jplag.java_cpg.transformation.operations.DummyNeighbor +import de.jplag.java_cpg.transformation.operations.RemoveOperation +import de.jplag.java_cpg.transformation.operations.TransformationUtil +import de.jplag.java_cpg.visitor.NodeOrderStrategy +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.* + +/** + * This pass sorts independent statements, removes statement that can be (conservatively) determined as useless, and builds + * the DFG. The original DFG of the CPG library is reset. + */ +@DependsOn(EvaluationOrderGraphPass::class) +class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + + private var state: State = State() + private var stateSafe: MutableMap = HashMap() + private val assignments: MutableMap, Assignment> = HashMap() + private val logger: Logger = LoggerFactory.getLogger(DfgSortPass::class.java) + + override fun accept(tu: TranslationUnitDeclaration) { + val functions = tu.functions.filter { it.body != null && it.name.localName.isNotEmpty() } + functions.forEach { calculateVariableDependencies(it) } + } + + private fun calculateVariableDependencies(root: FunctionDeclaration) { + logger.debug("Analyze %s".format(desc(root))) + + val childNodes = SubgraphWalker.flattenAST(root.body) + childNodes.forEach { + it.prevDFGEdges.clear() + it.nextDFGEdges.clear() + } + + val returnStatements = childNodes + .filter { it is ReturnStatement || it is UnaryOperator && it.operatorCode == "throw" } + state = State() + stateSafe = HashMap() + + /* + * handle() sets DFG edges between assignments and references of the same variable + * These DFG edges are then used in analyzeDfg() + */ + worklist( + returnStatements, + { val change = handle(it); change }, + { it.prevEOG.sortedBy { it.location?.region }.reversed() }, + succFilter = { _, change -> change } + ) + + val parentInfo = mutableMapOf() + val movableStatements = getMovableStatements(root, null, parentInfo) + + val finalState = stateSafe[root] + ?: throw TransformationException("EOG traverse did not reach the start - cannot sort statements") + + /* + * Sets DFG edges between statements + */ + analyzeDfg(finalState, movableStatements, parentInfo) + + /* + * Determines statements that must be kept, and in order relative to one another. + */ + val essentialNodesOut = mutableListOf() + extractEssentialNodes(root.body as Statement, essentialNodesOut) + essentialNodesOut.sortBy { it.location?.region } + + /* + * Determines statements on which essential statements are DFG-dependent. + */ + val relevantStatements = extractRelevantStatements(essentialNodesOut, parentInfo) + relevantStatements.removeIf { it !in movableStatements } + + removeIrrelevantStatements(relevantStatements, movableStatements, parentInfo) + + // At this point, the parentInfo map breaks. I do not know why. + // Restore map: + parentInfo.entries.toList().forEach { parentInfo[it.key] = it.value } + + + /* + * Sets DFG edges between statements that contain dfg-related statements in inner blocks + * e.g. DeclarationStatement --> WhileStatement using the declared variable + */ + extractTransitiveDependencies(relevantStatements, parentInfo) + + // loop dependencies are only needed to determine relevant statements, but disturb the reordering + relevantStatements.forEach { + it.prevDFGEdges.removeIf { edge -> + relevantStatements.indexOf(edge.start) == -1 || edge.getProperty( + Properties.NAME + ).toString().contains("loop") + } + it.nextDFGEdges.removeIf { edge -> + relevantStatements.indexOf(edge.end) == -1 || edge.getProperty(Properties.NAME).toString() + .contains("loop") + } + } + + reorderStatements(essentialNodesOut, relevantStatements, parentInfo, root.body as Block) + + } + + private fun extractTransitiveDependencies( + relevantStatements: MutableList, + parentInfo: MutableMap, + ) { + val depth = { node: Node -> parentInfo[node]?.depth ?: 0 } + val parent = { node: Node -> parentInfo[node]?.parent ?: node } + + val dfgEdges = relevantStatements.filterIsInstance() + .flatMap { statement -> statement.prevDFGEdges } + .filter { edge -> edge.start in relevantStatements } + .filter { edge -> edge.end in relevantStatements } + + dfgEdges.forEach { edge -> + val dfgPredecessor = edge.start + val statement = edge.end + + var properties: MutableMap + val (predBlock, stmtBlock, name) = getSiblingAncestors(dfgPredecessor, statement, depth, parent) + + // no self-dependencies + if (predBlock == stmtBlock) return@forEach + + if (stmtBlock is ReturnStatement || locationBefore(predBlock, stmtBlock)) { + if (stmtBlock in predBlock.nextDFG) return@forEach + // write-read dependency + properties = mutableMapOf(Pair(Properties.NAME, name)) + predBlock.addNextDFG(stmtBlock) + } else { + // the name is used to filter these edges out later + properties = mutableMapOf(Pair(Properties.NAME, "loop$name")) + if (stmtBlock in predBlock.nextDFG) { + edge.addProperties(properties) + } else { + // this edge shows that the value reaches the next iteration + predBlock.addNextDFG(stmtBlock) + } + // read-write dependency + properties = mutableMapOf(Pair(Properties.NAME, name)) + stmtBlock.addNextDFG(predBlock) + } + } + } + + private fun analyzeDfg( + variableData: MutableMap, + movableStatements: List, + parentInfo: MutableMap, + ) { + + val depth = { node: Node -> parentInfo[node]?.depth ?: 0 } + + val subtreeNodes = movableStatements.associateWith { SubgraphWalker.flattenAST(it) } + + val allReferences = variableData.values + .flatMap { it.assignments.toMutableList().let { lst -> lst.addAll(it.references); lst } } + + val referenceParentStatement = allReferences + .associateWith { ref -> + // find parent statement with the greatest depth -> immediate parent statement + val parentStatements = movableStatements.filter { subtreeNodes[it]?.contains(ref) ?: false } + val immediateParent = parentStatements.maxByOrNull { depth(it) } + immediateParent + } + + for (statement: Statement in movableStatements) { + + val childReferences = getImmediateChildReferences(statement, subtreeNodes) + + // map to assignments/declarations/... where current value might be set + val valueDependencies = childReferences.flatMap { it.prevDFG } + //only references to values, not methods + .filterNot { it is MethodDeclaration } + .distinct() + //only local variables, not class members etc. + .filter { assignment -> isLocal(assignment, statement) } + + // map to movable statements that contain these value definitions + val dependentStatements = valueDependencies.flatMap { dfgPredecessor -> + when (dfgPredecessor) { + is ParameterDeclaration, is AssignExpression -> listOf(dfgPredecessor) + is FieldDeclaration -> listOf() + else -> listOfNotNull(referenceParentStatement[dfgPredecessor]) + // but no self-dependencies + + } + }.filterNot { it == statement } + + dependentStatements + .forEach { dfgPredecessor -> + dfgPredecessor.addNextDFG(statement) + } + } + + } + + private fun getImmediateChildReferences( + statement: Statement, + subtreeNodes: Map>, + ): List { + val statements: List = when (statement) { + is WhileStatement -> listOf(statement.condition) + is DoStatement -> listOf(statement.condition) + is IfStatement -> listOf(statement.condition) + is ForStatement -> listOf( + statement.initializerStatement, + statement.condition, + statement.iterationStatement + ) + + is ForEachStatement -> listOf(statement.iterable, statement.variable) + + is TryStatement -> statement.resources + else -> listOf(statement) + } + + return statements.filterNotNull().flatMap { subtreeNodes[it] ?: listOf() } + .filter { it is Reference || it is AssignExpression } + } + + private fun getSiblingAncestors( + dfgPredecessor: Node, + statement: Node, + depth: (Node) -> Int, + parent: (Node) -> Node, + ): Triple { + var predBlock: Node = dfgPredecessor + var stmtBlock: Node = statement + var name = "Dependency" + if (!sameBlock(predBlock, stmtBlock)) { + //find common ancestor, create transitive dependency + name = "transitive$name($dfgPredecessor,$statement)" + while (true) { + val diff = depth(predBlock) - depth(stmtBlock) + + if (diff >= 1) { + predBlock = parent(predBlock) + } else if (diff <= -1) { + stmtBlock = parent(stmtBlock) + } else if (parent(predBlock) != parent(stmtBlock)) { + predBlock = parent(predBlock) + stmtBlock = parent(stmtBlock) + } else { + break + } + + } + } + return Triple(predBlock, stmtBlock, name) + } + + private fun locationBefore(a: Node, b: Node): Boolean { + if (a.location == null || b.location == null) { + return eogBefore(a, b) + } + return a.location!!.region <= b.location!!.region + } + + private fun eogBefore(a: Node, b: Node): Boolean { + val walker = SubgraphWalker.IterativeGraphWalker() + walker.strategy = Strategy::EOG_FORWARD + var found = false + walker.registerOnNodeVisit { node, _ -> if (node == b) found = true } + walker.iterate(a) + return found + } + + private fun sameBlock(a: Node, b: Node) = a.scope == b.scope + + class ParentInfo(val parent: Node, val depth: Int) + + private fun removeIrrelevantStatements( + relevantStatements: Collection, + statements: List, + parentInfo: MutableMap, + ) { + val irrelevantStatements = statements.filter { it !in relevantStatements } + .filter { parentInfo[it]?.parent is Block } + .distinct() + + irrelevantStatements.forEach { + if (parentInfo[it]?.parent == null) { + System.err.println("Parent info missing for ${desc(it)}, skipping removal of this irrelevant statement") + return + } + val block = parentInfo[it]?.parent as Block + val index = block.statements.indexOf(it) + val cpgNthEdge: CpgNthEdge = CpgNthEdge(BLOCK__STATEMENTS, index) + RemoveOperation.apply(block, it, cpgNthEdge, true) + if (it is DeclarationStatement) { + val deletedVariables = it.declarations.filterIsInstance() + block.localEdges.removeIf { it.end in deletedVariables } + } + DummyNeighbor.getInstance().clear() + } + + } + + private fun getBlocks(statement: Statement): List { + val statements = when (statement) { + is Block -> listOf(statement) + is DoStatement -> listOfNotNull(statement.statement) + is WhileStatement -> listOfNotNull(statement.statement) + is ForStatement -> listOfNotNull(statement.statement) + is ForEachStatement -> listOfNotNull(statement.statement) + is IfStatement -> + listOfNotNull(statement.thenStatement, statement.elseStatement) + + else -> listOf(statement) + } + return statements.filterIsInstance() + } + + private fun reorderStatements( + essentialNodesOut: MutableList, + relevantStatements: MutableList, + parentMap: MutableMap, + parent: Block, + ) { + // save entry point to keep EOG graph intact at the end + val entry = TransformationUtil.getEogBorders(parent.statements[0]).entries[0] + val eogPred = TransformationUtil.getEntryEdges(parent, entry, false) + .map { it.start } + + val exit = TransformationUtil.getEogBorders(parent.statements.last()).exits[0] + val eogSucc = TransformationUtil.getExitEdges(parent, listOf(exit), false) + .map { it.end } + + val worklist = mutableListOf() + val done = mutableListOf() + + val relevantStatementsInThisBlock = + relevantStatements.filter { parentMap[it]?.let { it.parent == parent } == true } + val allSuccessorsDone: (Node) -> Boolean = { + it.nextDFG.none { e -> + e !in done && + relevantStatementsInThisBlock.indexOf(e) >= 0 + } + } + relevantStatementsInThisBlock + .filter(allSuccessorsDone) + .map { it as Statement } + .let { them -> worklist.addAll(them) } + + while (worklist.isNotEmpty()) { + worklist.sortWith(compareStatement(essentialNodesOut)) + val element = worklist.removeLast() + if (!allSuccessorsDone(element)) { + continue + } + // insert before last element + if (done.isNotEmpty()) { + + // Implicit return statement has no location, but should remain the last element + val newSuccessor = done[0] + if (!TransformationUtil.isAstSuccessor(element, newSuccessor)) { + TransformationUtil.insertBefore(element, newSuccessor) + } + // TODO: Make it so that this is not necessary + DummyNeighbor.getInstance().clear() + } + done.add(0, element) + if (isBlockStatement(element)) { + val innerBlocks = getBlocks(element) + innerBlocks.forEach { + reorderStatements(essentialNodesOut, relevantStatements, parentMap, it) + } + } + + val newElements = + element.prevDFG + .asSequence() + .distinct() + .filter { it !in done && relevantStatementsInThisBlock.indexOf(it) >= 0 && it !in worklist } + .filter(allSuccessorsDone) + .map { it as Statement } + .toList() + worklist.addAll(newElements) + + } + assert(done.size == relevantStatementsInThisBlock.size && done.containsAll(relevantStatementsInThisBlock)) + + parent.statementEdges.clear() + done.forEach { parent.addStatement(it) } + + val newEntry = TransformationUtil.getEogBorders(parent.statements[0]).entries[0] + + newEntry.prevEOGEdges.filter { it.start is DummyNeighbor }.forEach { + it.start.nextEOGEdges.remove(it) + it.end.prevEOGEdges.remove(it) + } + + //rebuild EOG edges into the block + eogPred.filterNot { it is DummyNeighbor } + .filterNot { TransformationUtil.isEogSuccessor(it, newEntry) } + .forEach { + val edge = it.nextEOGEdges.find { e -> e.end is DummyNeighbor }!! + DummyNeighbor.getInstance().prevEOGEdges.remove(edge) + edge.end = newEntry + newEntry.addPrevEOG(edge) + } + + //rebuild EOG edges out of the block + val newExit = TransformationUtil.getEogBorders((parent.statements.last())).exits[0] + if (exit != newExit) { + newExit.nextEOGEdges.filter { it.end is DummyNeighbor }.forEach { + it.start.nextEOGEdges.remove(it) + it.end.prevEOGEdges.remove(it) + } + eogSucc.filter { succ -> !TransformationUtil.isEogSuccessor(exit, succ) } + .forEach { + val edge = it.prevEOGEdges.find { e -> e.start is DummyNeighbor }!! + edge.start = newExit + newExit.addNextEOG(edge) + } + } + } + + private fun extractRelevantStatements( + essentialNodesOut: MutableList, + parentInfo: MutableMap, + ): MutableList { + val relevantNodes: MutableSet = mutableSetOf() + + // block statements may have non-essential incoming dfg edges + worklist( + essentialNodesOut.filter { !isBlockStatement(it) }, + { relevantNodes.add(it) }, + { it.prevDFG.filterIsInstance() }, + succFilter = { it, _ -> it !in relevantNodes && it !is Block } + ) + + // block statements containing relevant nodes are relevant by extension + worklist( + relevantNodes.map { parentInfo[it]?.parent }.distinct().filterNotNull(), + { relevantNodes.add(it) }, + { parentInfo[it]?.parent.let { parent -> if (parent == null) listOf() else listOf(parent) } }, + succFilter = { node: Node, _: Boolean -> node is Statement } + ) + + return relevantNodes.sortedBy { it.location?.region }.toMutableList() + } + + private fun worklist( + initList: List, f: (T) -> R, successors: (T) -> Collection?, + succFilter: (T, R) -> Boolean = { _: T, _: R -> true }, + + ) { + val worklist = mutableListOf() + worklist.addAll(initList) + while (worklist.isNotEmpty()) { + val el: T = worklist.removeAt(0) + val result = f(el) + successors(el)?.filter { it !in worklist } + ?.filter { succFilter(it, result) } + ?.let { them -> worklist.addAll(0, them) } + } + } + + + private fun isBlockStatement(node: Node): Boolean { + return when (node) { + is Block, is WhileStatement, is DoStatement, is ForStatement, is ForEachStatement, is IfStatement -> { + true + } + + else -> false + } + + } + + private fun isLocal(node: Node, reference: Node): Boolean { + return node.location != null && node.location!!.artifactLocation == reference.location?.artifactLocation + + } + + private fun extractEssentialNodes( + node: Statement, + essentialNodes: MutableList, + ): Boolean { + /* + Mark the following nodes as essential: + - method calls + - assignments to fields + - ...nodes with side effects + - and blocks that contain essential nodes + + not necessarily essential (includes, but not limited to): + - control statements + - variable declarations + */ + + when (node) { + // Block, ForEachStatement + is StatementHolder -> { + val essentialChildren = node.statements + .filter { extractEssentialNodes(it, essentialNodes) } + val isEssential = essentialChildren.isNotEmpty() + if (isEssential) { + essentialNodes.add(node) + for (i in essentialChildren.indices) { + for (j in i + 1 until essentialChildren.size) { + val properties: MutableMap = + mutableMapOf(Pair(Properties.NAME, "essentialsDependency")) + essentialChildren[i].addNextDFG(essentialChildren[j]) + } + } + } + return isEssential + } + + is WhileStatement -> { + val children = listOfNotNull(node.condition, node.statement) + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + node.condition?.let { essentialNodes.add(it) } + } + return isEssential + } + + is DoStatement -> { + val children = listOfNotNull(node.condition, node.statement) + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + node.condition?.let { essentialNodes.add(it) } + } + return isEssential + } + + is ReturnStatement, is CallExpression -> { + essentialNodes.add(node) + return true + } + + is BinaryOperator -> { + val children = listOfNotNull(node.lhs, node.rhs) + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + } + return isEssential + } + + is UnaryOperator -> { + if (node.operatorCode == "throw" || extractEssentialNodes(node.input, essentialNodes)) { + essentialNodes.add(node) + return true + } + return false + } + + is AssignmentHolder -> { + val containsFieldAssignment = node.assignments + .filterNot { it.target == node } + .any { assignment -> + extractEssentialNodes(assignment.target as Statement, essentialNodes) + } + if (containsFieldAssignment) { + essentialNodes.add(node) + return true + } + + val containsEssentialExpression = node.assignments.any { assignment -> + extractEssentialNodes(assignment.value, essentialNodes) + } + if (containsEssentialExpression) { + essentialNodes.add(node) + return true + } + return false + } + + is Reference -> { + // is this reference used to write a non-local value? Maybe there is a better way to do it + val isEssential = node is MemberExpression + && node.access in listOf(AccessValues.WRITE, AccessValues.READWRITE) + + if (isEssential) essentialNodes.add(node) + return isEssential + } + + is SubscriptExpression -> { + return true + } + + is IfStatement -> { + val children = listOfNotNull(node.condition, node.thenStatement, node.elseStatement) + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + node.condition?.let { essentialNodes.add(it) } + } + return isEssential + } + + is ForStatement -> { + val children = + listOfNotNull(node.initializerStatement, node.condition, node.iterationStatement, node.statement) + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + listOfNotNull(node, node.initializerStatement, node.condition, node.iterationStatement) + .forEach { essentialNodes.add(it) } + } + return isEssential + } + + is TryStatement -> { + val children = mutableListOf() + children.addAll(node.resources) + node.tryBlock?.let { children.add(it) } + children.addAll(node.catchClauses) + node.finallyBlock?.let { children.add(it) } + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + essentialNodes.addAll(children) + } + return isEssential + } + + is CatchClause -> { + return node.body != null && extractEssentialNodes(node.body!!, essentialNodes) + } + + is BreakStatement, is ContinueStatement -> { + essentialNodes.add(node) + return true + } + + is ConditionalExpression -> { + val children = listOfNotNull(node.condition, node.thenExpression, node.elseExpression) + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + return true + } + } + + is DeclarationStatement -> { + val children = node.declarations.filterIsInstance().mapNotNull { it.initializer } + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + return true + } + } + + is NewArrayExpression -> { + val children = listOfNotNull(node.initializer) + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + return true + } + } + + is CastExpression -> { + val children = listOfNotNull(node.expression) + val isEssential = children.map { extractEssentialNodes(it, essentialNodes) }.any { it } + if (isEssential) { + essentialNodes.add(node) + return true + } + } + + else -> return false + } + return false + } + + private fun compareStatement(essentialNodes: MutableList): (Statement, Statement) -> Int { + return lambda@{ n1, n2 -> + val byType = getSortIndexByType(n1, essentialNodes) - getSortIndexByType(n2, essentialNodes) + if (byType != 0) return@lambda byType + val byTokens = compareTokenSequence(n1, n2) + if (byTokens != 0) return@lambda byTokens + else return@lambda NodeOrderStrategy.flattenStatement(n1).size - NodeOrderStrategy.flattenStatement(n2).size + } + } + + private fun getSortIndexByType(node: Node, essentialNodes: MutableList): Int = + when (node) { + is ReturnStatement -> 2 + !in essentialNodes -> 1 + else -> 0 + } + + private fun compareTokenSequence(n1: Statement, n2: Statement): Int { + val nodes1 = NodeOrderStrategy.flattenStatement(n1).iterator() + val nodes2 = NodeOrderStrategy.flattenStatement(n2).iterator() + + val tokens1 = CpgNodeListener.tokenIterator(nodes1); + val tokens2 = CpgNodeListener.tokenIterator(nodes2); + + while (tokens1.hasNext() && tokens2.hasNext()) { + val next1 = tokens1.next().type.let { if (it is CpgTokenType) it.ordinal else 0 } + val next2 = tokens2.next().type.let { if (it is CpgTokenType) it.ordinal else 0 } + val compare = next1 - next2 + if (compare != 0) return compare + } + return if (tokens1.hasNext()) 1; + else if (tokens2.hasNext()) -1; + else 0 + + } + + + private fun getMovableStatements( + statement: Node?, + parent: Node?, + parentInfo: MutableMap, + depth: Int = 0, + ): List { + if (statement == null) return mutableListOf() + + val statements: List = when (statement) { + is FunctionDeclaration -> { + val children = statement.parameters.toMutableList() + children.add(statement.body!!) + children.flatMap { getMovableStatements(it, statement, parentInfo, depth + 1) } + } + + is Block -> { + val result = + statement.statements.flatMap { getMovableStatements(it, statement, parentInfo, depth + 1) } + .toMutableList() + if (parent is Block) { + // inner block without control statement that would enforce it + result.add(statement) + } + result + } + + is WhileStatement -> { + val result: MutableList = + listOfNotNull(statement, statement.statement!!, statement.condition as Statement).toMutableList() + result.addAll(getMovableStatements(statement.statement, statement, parentInfo, depth + 1)) + result + } + + is DoStatement -> { + val result: MutableList = + listOfNotNull(statement, statement.statement, statement.condition as Statement).toMutableList() + result.addAll(getMovableStatements(statement.statement, statement, parentInfo, depth + 1)) + result + } + + is ForStatement -> { + val result: MutableList = listOfNotNull( + statement, + statement.initializerStatement, + statement.condition, + statement.iterationStatement + ).toMutableList() + result.addAll(getMovableStatements(statement.statement, statement, parentInfo, depth + 1)) + result + } + + is ForEachStatement -> { + val result: MutableList = listOfNotNull( + statement, + statement.variable, + statement.iterable + ).toMutableList() + result.addAll(getMovableStatements(statement.statement, statement, parentInfo, depth + 1)) + result + } + + is IfStatement -> { + val result = mutableListOf(statement, statement.condition as Statement) + result.addAll(getMovableStatements(statement.thenStatement, statement, parentInfo, depth + 1)) + result.addAll(getMovableStatements(statement.elseStatement, statement, parentInfo, depth + 1)) + result + } + + is TryStatement -> { + val result = mutableListOf(statement) + val children = mutableListOf() + listOfNotNull(statement.tryBlock, statement.finallyBlock).forEach { children.add(it) } + children.addAll(statement.resources) + children.addAll(statement.catchClauses) + children.flatMap { getMovableStatements(it, statement, parentInfo, depth + 1) } + .forEach { result.add(it) } + result + } + + is LambdaExpression -> { + getMovableStatements(statement.function, statement, parentInfo, depth + 1) + } + + is CallExpression -> { + val children = mutableListOf() + statement.callee.let { if (it != null) children.add(it) } + children.addAll(statement.parameters) + val result = + children.flatMap { getMovableStatements(it, statement, parentInfo, depth + 1) }.toMutableList() + result.add(statement) + result + } + + is Statement -> { + listOf(statement) + } + + + // only here to create entry in parentInfo + else -> listOf() + + } + if (parent != null) { + parentInfo[statement] = ParentInfo(parent, depth) + } + statements.filter { !parentInfo.containsKey(it) }.forEach { parentInfo[it] = ParentInfo(statement, depth + 1) } + + return statements + } + + private fun handle(node: Node): Boolean { + var change = false + + state = State() + // base state is the combination of all nextEOG states + node.nextEOG.mapNotNull { stateSafe[it] } + .forEach { state.putAll(it) } + + when (node) { + is Reference -> { + change = state.registerReference(node) + } + + // NewExpression is AssignmentHolder, but not usable for this analysis + is NewExpression -> {} + + + is AssignmentHolder -> { + if (node is VariableDeclaration && node.initializer == null) { + // include a fake assignment with the value null + change = state.registerAssignment( + Assignment( + Literal().let { it.value = null; it }, + node, + node + ), node + ) + } else { + change = + node.assignments + // cannot handle Array accesses + .filterNot { it.target is SubscriptExpression } + .map { assignment: Assignment -> state.registerAssignment(assignment, node) } + .fold(false, Boolean::or) + } + } + + is UnaryOperator -> { + handleUnaryOperator(node) + } + } + + if (!change && (node in stateSafe && stateSafe[node] == state)) { + // no change + return false + } + + stateSafe[node] = state.copy() + return true + } + + private fun handleUnaryOperator(node: UnaryOperator) { + if (!arrayOf("++", "--").contains(node.operatorCode)) return + val reference = node.input + if (reference !is Reference) return + val declaration = reference.refersTo + if (declaration !is ValueDeclaration) return + + val pair = Pair(node, declaration) + val assignment = assignments.computeIfAbsent(pair) { + val expr = AssignExpression() + expr.lhs = listOf(reference) + expr.rhs = listOf(node) + Assignment(it.first, it.second, expr) + } + state.registerAssignment(assignment, node) + } + + + override fun cleanup() { + } + + /** + * This class saves all data concerning the visibility and usage of variables at a statement in a method. + */ + private class State : HashMap { + constructor() + + constructor(copyMap: Map) : super(copyMap) + + + override fun get(key: Declaration): VariableData { + return super.computeIfAbsent(key) { VariableData() } + } + + override fun putAll(from: Map) { + from.entries.forEach { + val info: VariableData = this[it.key] + info.merge(it.value) + } + } + + fun copy(): State { + val copyMap: Map = this.toMap() + return State(copyMap) + } + + fun registerAssignment(assignment: Assignment, node: de.fraunhofer.aisec.cpg.graph.Node): Boolean { + val target = assignment.target.let { if (it is Reference) it.refersTo else it } + return this[target]?.resolve(node) ?: false + } + + fun registerReference(reference: Reference): Boolean { + // may be class or enum reference + if (reference.refersTo !is ValueDeclaration || reference.refersTo is FunctionDeclaration) return false + return this[reference.refersTo]?.register(reference) ?: false + } + + } + + /** + * This class saves all data concerning a local variable. + */ + private class VariableData { + private val currentReferences: MutableSet = mutableSetOf() + private var currentAssignments: MutableList = mutableListOf() + private var currentDeclaration: ValueDeclaration? = null + val references: MutableList = mutableListOf() + val assignments: MutableList = mutableListOf() + + fun resolve(node: Node): Boolean { + val change = currentReferences + .filterNot { reference: Reference -> node in reference.prevDFG } + .map { reference -> + node.addNextDFG(reference) + true + }.any() + + if (node !in assignments) assignments.add(node) + currentAssignments.filter { node != it }.forEach { node.addNextDFG(it) } + currentAssignments.clear() + + // if it IS a declaration, then from this point on the variable is undefined yet + if (node !is ValueDeclaration) currentAssignments.add(node) + + currentReferences.clear() + return change + } + + fun register(reference: Reference): Boolean { + currentReferences.add(reference) + if (reference !in references) { + references.add(reference) + } + + return currentAssignments.map { + // reference must resolve before the next assignment + if (reference in it.prevDFG) { + return false + } + reference.addNextDFG(it) // read-write dependency + return true + }.fold(false, Boolean::or) + } + + fun merge(other: VariableData?) { + if (Objects.isNull(currentDeclaration)) { + currentDeclaration = other!!.currentDeclaration + } + currentReferences.addAll(other!!.currentReferences.filter { it !in currentReferences }) + currentAssignments.addAll(other.currentAssignments.filter { it !in currentAssignments }) + references.addAll(other.references.filter { it !in references }) + assignments.addAll(other.assignments.filter { it !in assignments }) + } + + + override fun equals(other: Any?): Boolean { + return other is VariableData && + currentReferences == other.currentReferences && + currentAssignments == other.currentAssignments && + references == other.references && + assignments == other.assignments + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/FixAstPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/FixAstPass.kt new file mode 100644 index 0000000000..ac7f561ff5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/FixAstPass.kt @@ -0,0 +1,86 @@ +package de.jplag.java_cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.Component +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration +import de.fraunhofer.aisec.cpg.graph.fields +import de.fraunhofer.aisec.cpg.graph.methods +import de.fraunhofer.aisec.cpg.graph.refs +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration +import de.fraunhofer.aisec.cpg.passes.ComponentPass +import de.fraunhofer.aisec.cpg.passes.DynamicInvokeResolver +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import org.slf4j.LoggerFactory + +/** + * This pass associates record member references with superclass member definitions, if these member references have no + * other definition associated to them. + */ +@DependsOn(DynamicInvokeResolver::class) +class FixAstPass(ctx: TranslationContext) : ComponentPass(ctx) { + private val logger = LoggerFactory.getLogger(FixAstPass::class.java) + + override fun accept(t: Component) { + val defectiveReferences = t.refs + .filterIsInstance() + .filter { it.refersTo != null && it.refersTo?.location == null } + .map { Pair(it.refersTo!!, it) } + .let { logger.info("Found ${it.size} defective field references."); it } + .groupBy { it.first.javaClass.simpleName } + + val superFieldReferences : List>? = defectiveReferences["FieldDeclaration"] + val successFields = superFieldReferences?.associateWith { fixSuperFieldReference(it.first as FieldDeclaration, it.second) } + ?.count { it.value } ?: 0 + + val superMethodReferences : List>? = defectiveReferences["MethodDeclaration"] + val successMethods = superMethodReferences?.associateWith { fixSuperMethodReference(it.first as MethodDeclaration, it.second) } + ?.count { it.value } ?: 0 + logger.info("Fixed $successFields fields and $successMethods methods, sum: ${successFields + successMethods}.") + } + + override fun cleanup() { + // all clean already + } + + private fun fixSuperFieldReference(field: FieldDeclaration, ref: MemberExpression): Boolean { + return fixReference(ref, field) { it.recordDeclaration.fields } + } + + private fun fixSuperMethodReference(method: MethodDeclaration, ref: MemberExpression): Boolean { + return fixReference(ref, method) { it.recordDeclaration.methods } + } + + private fun fixReference( + ref: MemberExpression, + method: T, + getCandidates: (Type) -> List + ): Boolean { + val subClassType = ref.base.type; + val superTypes = mutableListOf() + superTypes.add(subClassType) + + var match: T? = null + while (superTypes.isNotEmpty()) { + val superType = superTypes.removeFirst() + match = getCandidates(superType).find { it.name.localName == method.name.localName } + if (match != null) { + break + } + superTypes.addAll(superType.superTypes) + } + + if (match == null) { + return false + } + ref.refersTo = match + return true + } + + + + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/JTokenizationPass.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/JTokenizationPass.java new file mode 100644 index 0000000000..a518d30e9a --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/JTokenizationPass.java @@ -0,0 +1,106 @@ +package de.jplag.java_cpg.passes; + +import java.io.File; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.TranslationResult; +import de.fraunhofer.aisec.cpg.graph.Name; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker; +import de.fraunhofer.aisec.cpg.passes.TranslationResultPass; +import de.jplag.Token; +import de.jplag.TokenType; +import de.jplag.java_cpg.token.CpgNodeListener; +import de.jplag.java_cpg.token.CpgToken; +import de.jplag.java_cpg.token.CpgTokenConsumer; +import de.jplag.java_cpg.visitor.NodeOrderStrategy; + +import kotlin.Unit; + +/** + * This pass tokenizes the {@link de.fraunhofer.aisec.cpg.TranslationResult}. It is a duplicate of + * de.jplag.java_cpg.passes.TokenizationPass implemented in Java as a workaround to make JavaDoc work. If the package + * 'passes' contains only Kotlin files, JavaDoc fails. + */ +public class JTokenizationPass extends TranslationResultPass { + + private static final Logger logger = LoggerFactory.getLogger(JTokenizationPass.class); + private final ArrayList tokenList = new ArrayList<>(); + private final CpgTokenConsumer consumer = new ConcreteCpgTokenConsumer(); + private final NodeOrderStrategy strategy = new NodeOrderStrategy(); + Consumer> callback = null; + + /** + * Constructs a new JTokenizationPass. + * @param ctx the current {@link de.fraunhofer.aisec.cpg.TranslationContext} + */ + public JTokenizationPass(@NotNull TranslationContext ctx) { + super(ctx); + } + + // @Override + // public void accept(TranslationResult translationResult) { + // tokenList.clear(); + // CpgNodeListener listener = new CpgNodeListener(consumer); + // SubgraphWalker.IterativeGraphWalker walker = new SubgraphWalker.IterativeGraphWalker(); + // walker.setStrategy(strategy::getIterator); + // //walker.registerOnNodeVisit(listener::visit); + // walker.registerOnNodeVisit((node, parent) -> { + // listener.visit(node); + // return Unit.INSTANCE; + // }); + // walker.registerOnNodeExit(listener::exit); + // walker.iterate(translationResult); + // callback.accept(tokenList); + // } + + /** + * Updated for the (new CPG version), the old + * version commented out above. + */ + @Override + public void accept(TranslationResult translationResult) { + tokenList.clear(); + CpgNodeListener listener = new CpgNodeListener(consumer); + SubgraphWalker.IterativeGraphWalker walker = new SubgraphWalker.IterativeGraphWalker(); + walker.setStrategy(strategy::getIterator); + ArrayDeque stack = new ArrayDeque<>(); + walker.registerOnNodeVisit((node, parent) -> { + // pop and emit exits until the top of the stack matches the parent + while (!stack.isEmpty() && stack.peek() != parent) { + Node exited = stack.pop(); + listener.exit(exited); + } + listener.visit(node); + stack.push(node); + return Unit.INSTANCE; + }); + walker.iterate(translationResult); + // flush remaining exits after traversal completes + while (!stack.isEmpty()) { + listener.exit(stack.pop()); + } + callback.accept(tokenList); + } + + @Override + public void cleanup() { + logger.info("Found {} tokens", tokenList.size()); + } + + private class ConcreteCpgTokenConsumer extends CpgTokenConsumer { + public void addToken(TokenType type, File file, int rowBegin, int colBegin, int length, Name name) { + CpgToken token = new CpgToken(type, file, rowBegin, colBegin, length, name); + tokenList.add(token); + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/PrepareTransformationPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/PrepareTransformationPass.kt new file mode 100644 index 0000000000..1a23a75fd5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/PrepareTransformationPass.kt @@ -0,0 +1,40 @@ +package de.jplag.java_cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.passes.ImportResolver +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore +import de.jplag.java_cpg.transformation.GraphTransformation + +/** + * This pass handles the transformations that are required in the pipeline of the Tokenization process. + */ +@DependsOn(ImportResolver::class) +@ExecuteBefore(AstTransformationPass::class) +class PrepareTransformationPass(ctx: TranslationContext) : ATransformationPass(ctx) { + + override fun getPhaseSpecificTransformations(): List { + return transformations.toList(); + } + + companion object { + @JvmStatic + val transformations: MutableList = ArrayList() + + @JvmStatic + fun registerTransformation(transformation: GraphTransformation) { + transformations.add(transformation) + } + + @JvmStatic + fun registerTransformations(newTransformations: Array) { + transformations.addAll(newTransformations) + } + + @JvmStatic + fun clearTransformations() { + transformations.clear() + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/PrintPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/PrintPass.kt new file mode 100644 index 0000000000..8133836a9e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/PrintPass.kt @@ -0,0 +1,35 @@ +package de.jplag.java_cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.TranslationUnitPass +import java.util.* + +/** + * A pass that prints all found nodes to the console. + */ +class PrintPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + + + override fun accept(translationUnitDeclaration: TranslationUnitDeclaration) { + val graphWalker = SubgraphWalker.IterativeGraphWalker() + graphWalker.registerOnNodeVisit { node: Node, _ -> + var code = node.code + if (Objects.isNull(code)) { + code = "~no code~" + } else if (code!!.length > 20) { + code = code.substring(0, 20) + "..." + } + System.out.printf("%s[%s]%n", node.javaClass, code) + } + for (node in translationUnitDeclaration.astChildren) { + graphWalker.iterate(node) + } + } + + override fun cleanup() { + // Nothing to do + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/TokenizationPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/TokenizationPass.kt new file mode 100644 index 0000000000..542ebc0f41 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/passes/TokenizationPass.kt @@ -0,0 +1,97 @@ +package de.jplag.java_cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.TranslationResultPass +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.jplag.Token +import de.jplag.TokenType +import de.jplag.java_cpg.token.CpgNodeListener +import de.jplag.java_cpg.token.CpgToken +import de.jplag.java_cpg.token.CpgTokenConsumer +import de.jplag.java_cpg.visitor.NodeOrderStrategy +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File +import java.util.function.Consumer + +/** + * This pass tokenizes the [TranslationResult]. + */ +@DependsOn(CpgTransformationPass::class) +class TokenizationPass(ctx: TranslationContext) : TranslationResultPass(ctx) { + + private val tokenList = ArrayList() + private val consumer: CpgTokenConsumer + + init { + this.consumer = ConcreteCpgTokenConsumer() + } + + override fun cleanup() { + logger.info("Found %d tokens".format(tokenList.size)) + } + + private val strategy = NodeOrderStrategy() + +// override fun accept(translationResult: TranslationResult) { +// tokenList.clear() +// val listener = CpgNodeListener(consumer) +// val walker: SubgraphWalker.IterativeGraphWalker = SubgraphWalker.IterativeGraphWalker() +// walker.strategy = { strategy.getIterator(it) } +// walker.registerOnNodeVisit {node, _ -> listener.visit(node) } +// walker.registerOnNodeExit { listener.exit(it) } +// walker.iterate(translationResult) +// callback!!.accept(tokenList) +// } + + /** + * Updated for the new CPG version (https://github.com/Fraunhofer-AISEC/cpg/pull/1571/files), + * the old version commented out above. + */ + override fun accept(translationResult: TranslationResult) { + tokenList.clear() + val listener = CpgNodeListener(consumer) + val walker: SubgraphWalker.IterativeGraphWalker = SubgraphWalker.IterativeGraphWalker() + walker.strategy = { strategy.getIterator(it) } + val stack = java.util.ArrayDeque() + walker.registerOnNodeVisit { node, parent -> + // pop and emit exits until the top of the stack matches the parent + while (stack.isNotEmpty() && stack.peek() != parent) { + val exited = stack.pop() + listener.exit(exited) + } + listener.visit(node) + stack.push(node) + } + walker.iterate(translationResult) + // flush remaining exits after traversal completes + while (stack.isNotEmpty()) { + val exited = stack.pop() + listener.exit(exited) + } + callback!!.accept(tokenList) + } + + private inner class ConcreteCpgTokenConsumer : CpgTokenConsumer() { + override fun addToken( + type: TokenType, + file: File, + rowBegin: Int, + colBegin: Int, + length: Int, + name: Name + ) { + val token = CpgToken(type, file, rowBegin, colBegin, length, name) + tokenList.add(token) + } + } + + companion object { + var callback: Consumer>? = null + val logger: Logger = LoggerFactory.getLogger(TokenizationPass::class.java) + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/ACpgNodeListener.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/ACpgNodeListener.java new file mode 100644 index 0000000000..759c61eef1 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/ACpgNodeListener.java @@ -0,0 +1,637 @@ +package de.jplag.java_cpg.token; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration; +import de.fraunhofer.aisec.cpg.graph.declarations.DeclarationSequence; +import de.fraunhofer.aisec.cpg.graph.declarations.EnumConstantDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.EnumDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionTemplateDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.IncludeDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.ProblemDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordTemplateDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TupleDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TypeParameterDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement; +import de.fraunhofer.aisec.cpg.graph.statements.BreakStatement; +import de.fraunhofer.aisec.cpg.graph.statements.CaseStatement; +import de.fraunhofer.aisec.cpg.graph.statements.CatchClause; +import de.fraunhofer.aisec.cpg.graph.statements.ContinueStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DefaultStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DoStatement; +import de.fraunhofer.aisec.cpg.graph.statements.EmptyStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement; +import de.fraunhofer.aisec.cpg.graph.statements.GotoStatement; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.fraunhofer.aisec.cpg.graph.statements.LabelStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement; +import de.fraunhofer.aisec.cpg.graph.statements.Statement; +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement; +import de.fraunhofer.aisec.cpg.graph.statements.SynchronizedStatement; +import de.fraunhofer.aisec.cpg.graph.statements.TryStatement; +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConditionalExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeleteExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ExpressionList; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.KeyValueExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.LambdaExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.RangeExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ShortCircuitOperator; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.SubscriptExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.TypeExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.TypeIdExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator; + +/** + * This class provides empty dummy implementations for {@link CpgNodeListener}s. + */ +public abstract class ACpgNodeListener extends VisitorExitor { + + /** + * Creates a new {@link ACpgNodeListener}. + */ + protected ACpgNodeListener() { + // empty dummy implementation + } + + void exit(AssertStatement assertStatement) { + // empty dummy implementation + } + + void exit(AssignExpression assignExpression) { + // empty dummy implementation + } + + void exit(BinaryOperator binaryoperator) { + // empty dummy implementation + } + + void exit(Block block) { + // empty dummy implementation + } + + void exit(BreakStatement breakStatement) { + // empty dummy implementation + } + + void exit(CallExpression callExpression) { + // empty dummy implementation + } + + void exit(CaseStatement caseStatement) { + // empty dummy implementation + } + + void exit(CastExpression castExpression) { + // empty dummy implementation + } + + void exit(CatchClause catchclause) { + // empty dummy implementation + } + + void exit(ConditionalExpression conditionalExpression) { + // empty dummy implementation + } + + void exit(ConstructExpression constructExpression) { + // empty dummy implementation + } + + void exit(ConstructorDeclaration constructorDeclaration) { + // empty dummy implementation + } + + void exit(ContinueStatement continueStatement) { + // empty dummy implementation + } + + void exit(Declaration declaration) { + // empty dummy implementation + } + + void exit(DeclarationSequence declarationsequence) { + // empty dummy implementation + } + + void exit(DeclarationStatement declarationStatement) { + // empty dummy implementation + } + + void exit(DefaultStatement defaultStatement) { + // empty dummy implementation + } + + void exit(DeleteExpression deleteExpression) { + // empty dummy implementation + } + + void exit(DoStatement doStatement) { + // empty dummy implementation + } + + void exit(EmptyStatement emptyStatement) { + // empty dummy implementation + } + + void exit(EnumConstantDeclaration enumConstantDeclaration) { + // empty dummy implementation + } + + void exit(EnumDeclaration enumDeclaration) { + // empty dummy implementation + } + + void exit(Expression expression) { + // empty dummy implementation + } + + void exit(ExpressionList expressionlist) { + // empty dummy implementation + } + + void exit(FieldDeclaration fieldDeclaration) { + // empty dummy implementation + } + + void exit(ForEachStatement forEachStatement) { + // empty dummy implementation + } + + void exit(ForStatement forStatement) { + // empty dummy implementation + } + + void exit(FunctionDeclaration functionDeclaration) { + // empty dummy implementation + } + + void exit(FunctionTemplateDeclaration functionTemplateDeclaration) { + // empty dummy implementation + } + + void exit(GotoStatement gotoStatement) { + // empty dummy implementation + } + + void exit(IfStatement ifStatement) { + // empty dummy implementation + } + + void exit(IncludeDeclaration includeDeclaration) { + // empty dummy implementation + } + + void exit(InitializerListExpression initializerListExpression) { + // empty dummy implementation + } + + void exit(KeyValueExpression keyValueExpression) { + // empty dummy implementation + } + + void exit(LabelStatement labelStatement) { + // empty dummy implementation + } + + void exit(LambdaExpression lambdaExpression) { + // empty dummy implementation + } + + void exit(Literal literal) { + // empty dummy implementation + } + + void exit(MemberCallExpression memberCallExpression) { + // empty dummy implementation + } + + void exit(MemberExpression memberExpression) { + // empty dummy implementation + } + + void exit(MethodDeclaration methodDeclaration) { + // empty dummy implementation + } + + void exit(NamespaceDeclaration namespaceDeclaration) { + // empty dummy implementation + } + + void exit(NewArrayExpression newArrayExpression) { + // empty dummy implementation + } + + void exit(NewExpression newExpression) { + // empty dummy implementation + } + + void exit(ParameterDeclaration parameterDeclaration) { + // empty dummy implementation + } + + void exit(ProblemDeclaration problemDeclaration) { + // empty dummy implementation + } + + void exit(ProblemExpression problemExpression) { + // empty dummy implementation + } + + void exit(RangeExpression rangeExpression) { + // empty dummy implementation + } + + void exit(RecordDeclaration recordDeclaration) { + // empty dummy implementation + } + + void exit(RecordTemplateDeclaration recordTemplateDeclaration) { + // empty dummy implementation + } + + void exit(Reference reference) { + // empty dummy implementation + } + + void exit(ReturnStatement returnStatement) { + // empty dummy implementation + } + + void exit(ShortCircuitOperator shortCircuitOperator) { + // empty dummy implementation + } + + void exit(Statement statement) { + // empty dummy implementation + } + + void exit(SubscriptExpression subscriptExpression) { + // empty dummy implementation + } + + void exit(SwitchStatement switchStatement) { + // empty dummy implementation + } + + void exit(SynchronizedStatement synchronizedStatement) { + // empty dummy implementation + } + + void exit(TemplateDeclaration templateDeclaration) { + // empty dummy implementation + } + + void exit(TranslationUnitDeclaration translationUnitDeclaration) { + // empty dummy implementation + } + + void exit(TryStatement tryStatement) { + // empty dummy implementation + } + + void exit(TupleDeclaration tupleDeclaration) { + // empty dummy implementation + } + + void exit(TypedefDeclaration typedefDeclaration) { + // empty dummy implementation + } + + void exit(TypeExpression typeExpression) { + // empty dummy implementation + } + + void exit(TypeIdExpression typeIdExpression) { + // empty dummy implementation + } + + void exit(TypeParameterDeclaration typeParameterDeclaration) { + // empty dummy implementation + } + + void exit(UnaryOperator unaryoperator) { + // empty dummy implementation + } + + // void exit(UsingDeclaration usingDeclaration) { + // // empty dummy implementation + // } + + void exit(ValueDeclaration valueDeclaration) { + // empty dummy implementation + } + + void exit(VariableDeclaration variableDeclaration) { + // empty dummy implementation + } + + void exit(WhileStatement whileStatement) { + // empty dummy implementation + } + + void visit(AssertStatement assertStatement) { + // empty dummy implementation + } + + void visit(AssignExpression assignExpression) { + // empty dummy implementation + } + + void visit(BinaryOperator binaryoperator) { + // empty dummy implementation + } + + void visit(Block block) { + // empty dummy implementation + } + + void visit(BreakStatement breakStatement) { + // empty dummy implementation + } + + void visit(CallExpression callExpression) { + // empty dummy implementation + } + + void visit(CaseStatement caseStatement) { + // empty dummy implementation + } + + void visit(CastExpression castExpression) { + // empty dummy implementation + } + + void visit(CatchClause catchclause) { + // empty dummy implementation + } + + void visit(ConditionalExpression conditionalExpression) { + // empty dummy implementation + } + + void visit(ConstructExpression constructExpression) { + // empty dummy implementation + } + + void visit(ConstructorDeclaration constructorDeclaration) { + // empty dummy implementation + } + + void visit(ContinueStatement continueStatement) { + // empty dummy implementation + } + + void visit(Declaration declaration) { + // empty dummy implementation + } + + void visit(DeclarationSequence declarationsequence) { + // empty dummy implementation + } + + void visit(DeclarationStatement declarationStatement) { + // empty dummy implementation + } + + void visit(DefaultStatement defaultStatement) { + // empty dummy implementation + } + + void visit(DeleteExpression deleteExpression) { + // empty dummy implementation + } + + void visit(DoStatement doStatement) { + // empty dummy implementation + } + + void visit(EmptyStatement emptyStatement) { + // empty dummy implementation + } + + void visit(EnumConstantDeclaration enumConstantDeclaration) { + // empty dummy implementation + } + + void visit(EnumDeclaration enumDeclaration) { + // empty dummy implementation + } + + void visit(Expression expression) { + // empty dummy implementation + } + + void visit(ExpressionList expressionlist) { + // empty dummy implementation + } + + void visit(FieldDeclaration fieldDeclaration) { + // empty dummy implementation + } + + void visit(ForEachStatement forEachStatement) { + // empty dummy implementation + } + + void visit(ForStatement forStatement) { + // empty dummy implementation + } + + void visit(FunctionDeclaration functionDeclaration) { + // empty dummy implementation + } + + void visit(FunctionTemplateDeclaration functionTemplateDeclaration) { + // empty dummy implementation + } + + void visit(GotoStatement gotoStatement) { + // empty dummy implementation + } + + void visit(IfStatement ifStatement) { + // empty dummy implementation + } + + void visit(IncludeDeclaration includeDeclaration) { + // empty dummy implementation + } + + void visit(InitializerListExpression initializerListExpression) { + // empty dummy implementation + } + + void visit(KeyValueExpression keyValueExpression) { + // empty dummy implementation + } + + void visit(LabelStatement labelStatement) { + // empty dummy implementation + } + + void visit(LambdaExpression lambdaExpression) { + // empty dummy implementation + } + + void visit(Literal literal) { + // empty dummy implementation + } + + void visit(MemberCallExpression memberCallExpression) { + // empty dummy implementation + } + + void visit(MemberExpression memberExpression) { + // empty dummy implementation + } + + void visit(MethodDeclaration methodDeclaration) { + // empty dummy implementation + } + + void visit(NamespaceDeclaration namespaceDeclaration) { + // empty dummy implementation + } + + void visit(NewArrayExpression newArrayExpression) { + // empty dummy implementation + } + + void visit(NewExpression newExpression) { + // empty dummy implementation + } + + void visit(ParameterDeclaration parameterDeclaration) { + // empty dummy implementation + } + + void visit(ProblemDeclaration problemDeclaration) { + // empty dummy implementation + } + + void visit(ProblemExpression problemExpression) { + // empty dummy implementation + } + + void visit(RangeExpression rangeExpression) { + // empty dummy implementation + } + + void visit(RecordDeclaration recordDeclaration) { + // empty dummy implementation + } + + void visit(RecordTemplateDeclaration recordTemplateDeclaration) { + // empty dummy implementation + } + + void visit(Reference reference) { + // empty dummy implementation + } + + void visit(ReturnStatement returnStatement) { + // empty dummy implementation + } + + void visit(ShortCircuitOperator shortCircuitOperator) { + // empty dummy implementation + } + + void visit(Statement statement) { + // empty dummy implementation + } + + void visit(SubscriptExpression subscriptExpression) { + // empty dummy implementation + } + + void visit(SwitchStatement switchStatement) { + // empty dummy implementation + } + + void visit(SynchronizedStatement synchronizedStatement) { + // empty dummy implementation + } + + void visit(TemplateDeclaration templateDeclaration) { + // empty dummy implementation + } + + void visit(TranslationUnitDeclaration translationUnitDeclaration) { + // empty dummy implementation + } + + void visit(TryStatement tryStatement) { + // empty dummy implementation + } + + void visit(TupleDeclaration tupleDeclaration) { + // empty dummy implementation + } + + void visit(TypedefDeclaration typedefDeclaration) { + // empty dummy implementation + } + + void visit(TypeExpression typeExpression) { + // empty dummy implementation + } + + void visit(TypeIdExpression typeIdExpression) { + // empty dummy implementation + } + + void visit(TypeParameterDeclaration typeParameterDeclaration) { + // empty dummy implementation + } + + void visit(UnaryOperator unaryoperator) { + // empty dummy implementation + } + + // void visit(UsingDeclaration usingDeclaration) { + // // empty dummy implementation + // } + + void visit(ValueDeclaration valueDeclaration) { + // empty dummy implementation + } + + void visit(VariableDeclaration variableDeclaration) { + // empty dummy implementation + } + + void visit(WhileStatement whileStatement) { + // empty dummy implementation + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgNodeListener.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgNodeListener.java new file mode 100644 index 0000000000..d11a7b5788 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgNodeListener.java @@ -0,0 +1,403 @@ +package de.jplag.java_cpg.token; + +import static de.jplag.SharedTokenType.FILE_END; +import static de.jplag.java_cpg.token.CpgTokenType.ASSERT_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.ASSIGNMENT; +import static de.jplag.java_cpg.token.CpgTokenType.BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.BREAK; +import static de.jplag.java_cpg.token.CpgTokenType.CASE_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.CATCH_CLAUSE_BEGIN; +import static de.jplag.java_cpg.token.CpgTokenType.CATCH_CLAUSE_END; +import static de.jplag.java_cpg.token.CpgTokenType.CONSTRUCTOR_CALL; +import static de.jplag.java_cpg.token.CpgTokenType.CONTINUE; +import static de.jplag.java_cpg.token.CpgTokenType.DEFAULT_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.DO_WHILE_BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.DO_WHILE_BLOCK_START; +import static de.jplag.java_cpg.token.CpgTokenType.DO_WHILE_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.ELSE_BLOCK_BEGIN; +import static de.jplag.java_cpg.token.CpgTokenType.ELSE_BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.ENUM_DECL_BEGIN; +import static de.jplag.java_cpg.token.CpgTokenType.ENUM_DECL_END; +import static de.jplag.java_cpg.token.CpgTokenType.ENUM_ELEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.FIELD_DECL; +import static de.jplag.java_cpg.token.CpgTokenType.FOR_BLOCK_BEGIN; +import static de.jplag.java_cpg.token.CpgTokenType.FOR_BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.FOR_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.GOTO_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.IF_BLOCK_BEGIN; +import static de.jplag.java_cpg.token.CpgTokenType.IF_BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.IF_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.INCLUDE; +import static de.jplag.java_cpg.token.CpgTokenType.LAMBDA_EXPRESSION; +import static de.jplag.java_cpg.token.CpgTokenType.METHOD_BODY_BEGIN; +import static de.jplag.java_cpg.token.CpgTokenType.METHOD_BODY_END; +import static de.jplag.java_cpg.token.CpgTokenType.METHOD_CALL; +import static de.jplag.java_cpg.token.CpgTokenType.METHOD_DECL_BEGIN; +import static de.jplag.java_cpg.token.CpgTokenType.METHOD_PARAM; +import static de.jplag.java_cpg.token.CpgTokenType.NEW_ARRAY; +import static de.jplag.java_cpg.token.CpgTokenType.RECORD_DECL_BEGIN; +import static de.jplag.java_cpg.token.CpgTokenType.RECORD_DECL_END; +import static de.jplag.java_cpg.token.CpgTokenType.RETURN; +import static de.jplag.java_cpg.token.CpgTokenType.SWITCH_BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.SWITCH_BLOCK_START; +import static de.jplag.java_cpg.token.CpgTokenType.SWITCH_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.SYNCHRONIZED_BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.SYNCHRONIZED_BLOCK_START; +import static de.jplag.java_cpg.token.CpgTokenType.SYNCHRONIZED_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.THROW; +import static de.jplag.java_cpg.token.CpgTokenType.TRY_BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.TRY_BLOCK_START; +import static de.jplag.java_cpg.token.CpgTokenType.TRY_STATEMENT; +import static de.jplag.java_cpg.token.CpgTokenType.VARIABLE_DECL; +import static de.jplag.java_cpg.token.CpgTokenType.WHILE_BLOCK_END; +import static de.jplag.java_cpg.token.CpgTokenType.WHILE_BLOCK_START; +import static de.jplag.java_cpg.token.CpgTokenType.WHILE_STATEMENT; + +import java.io.File; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Objects; + +import de.fraunhofer.aisec.cpg.graph.Name; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.EnumConstantDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.EnumDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.IncludeDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement; +import de.fraunhofer.aisec.cpg.graph.statements.BreakStatement; +import de.fraunhofer.aisec.cpg.graph.statements.CaseStatement; +import de.fraunhofer.aisec.cpg.graph.statements.CatchClause; +import de.fraunhofer.aisec.cpg.graph.statements.ContinueStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DefaultStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DoStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement; +import de.fraunhofer.aisec.cpg.graph.statements.GotoStatement; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement; +import de.fraunhofer.aisec.cpg.graph.statements.Statement; +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement; +import de.fraunhofer.aisec.cpg.graph.statements.SynchronizedStatement; +import de.fraunhofer.aisec.cpg.graph.statements.TryStatement; +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.LambdaExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator; +import de.jplag.Token; +import de.jplag.TokenType; + +/** + * This class specifies for which {@link Node}s a {@link CpgToken} shall be created. + */ +public class CpgNodeListener extends ACpgNodeListener { + + private final CpgTokenConsumer tokenConsumer; + private final LinkedList openBlocks; + private final LinkedList expectedBlocks; + + /** + * Creates a new {@link CpgNodeListener}. + * @param consumer the {@link CpgTokenConsumer} that receives the tokens. + */ + public CpgNodeListener(CpgTokenConsumer consumer) { + this.tokenConsumer = consumer; + this.expectedBlocks = new LinkedList<>(); + this.openBlocks = new LinkedList<>(); + } + + /** + * This is used to iterate over {@link Token} list, comparing them one {@link Token} at a time. + * @param nodes an iterator for {@link Node}s + * @return an iterator for {@link Token}s + */ + public static Iterator tokenIterator(Iterator nodes) { + return new Iterator<>() { + + Token next; + final CpgTokenConsumer consumer = new CpgTokenConsumer() { + @Override + public void addToken(TokenType type, File file, int startLine, int startColumn, int length, Name name) { + next = new CpgToken(type, file, startLine, startColumn, length, name); + } + }; + + final CpgNodeListener listener = new CpgNodeListener(consumer); + + @Override + public boolean hasNext() { + while (Objects.isNull(next) && nodes.hasNext()) { + listener.visit(nodes.next()); + } + return !Objects.isNull(next); + } + + @Override + public Token next() { + if (hasNext()) { + Token saveNext = next; + next = null; + return saveNext; + } + return null; + } + }; + } + + @Override + public void exit(Block block) { + TokenType blockEndToken = openBlocks.pop(); + if (blockEndToken == BLOCK_END) + return; + + tokenConsumer.addToken(blockEndToken, block, true); + } + + @Override + public void exit(CatchClause catchclause) { + tokenConsumer.addToken(CATCH_CLAUSE_END, catchclause, true); + } + + @Override + public void exit(DoStatement doStatement) { + tokenConsumer.addToken(DO_WHILE_BLOCK_END, doStatement, true); + } + + @Override + public void exit(EnumDeclaration enumDeclaration) { + tokenConsumer.addToken(ENUM_DECL_END, enumDeclaration, true); + } + + @Override + public void exit(RecordDeclaration recordDeclaration) { + tokenConsumer.addToken(RECORD_DECL_END, recordDeclaration, true); + } + + @Override + public void exit(TranslationUnitDeclaration translationUnitDeclaration) { + tokenConsumer.addToken(FILE_END, new File(translationUnitDeclaration.getName().toString()), -1, -1, -1, translationUnitDeclaration.getName()); + } + + @Override + public void visit(AssertStatement assertStatement) { + tokenConsumer.addToken(ASSERT_STATEMENT, assertStatement, false); + } + + @Override + public void visit(AssignExpression assignExpression) { + tokenConsumer.addToken(ASSIGNMENT, assignExpression, false); + } + + @Override + public void visit(Block block) { + if (expectedBlocks.isEmpty()) { + // Do not add BLOCK_START and BLOCK_END, otherwise that is a vulnerability + openBlocks.addFirst(BLOCK_END); + } else { + BlockTokens blockTokens = expectedBlocks.pop(); + tokenConsumer.addToken(blockTokens.opening, block, false); + openBlocks.addFirst(blockTokens.closing); + } + } + + @Override + public void visit(BreakStatement breakStatement) { + tokenConsumer.addToken(BREAK, breakStatement, false); + } + + @Override + public void visit(CallExpression callExpression) { + tokenConsumer.addToken(METHOD_CALL, callExpression, false); + } + + @Override + public void visit(CaseStatement caseStatement) { + tokenConsumer.addToken(CASE_STATEMENT, caseStatement, false); + } + + @Override + public void visit(CatchClause catchclause) { + tokenConsumer.addToken(CATCH_CLAUSE_BEGIN, catchclause, false); + } + + @Override + public void visit(ConstructExpression constructorCallExpression) { + tokenConsumer.addToken(CONSTRUCTOR_CALL, constructorCallExpression, false); + } + + @Override + public void visit(ConstructorDeclaration constructorDeclaration) { + // Constructor may be the implicit standard constructor + tokenConsumer.addToken(METHOD_DECL_BEGIN, constructorDeclaration, false); + expect(METHOD_BODY_BEGIN, METHOD_BODY_END); + } + + @Override + public void visit(ContinueStatement continueStatement) { + tokenConsumer.addToken(CONTINUE, continueStatement, false); + } + + @Override + public void visit(DefaultStatement defaultStatement) { + tokenConsumer.addToken(DEFAULT_STATEMENT, defaultStatement, false); + } + + @Override + public void visit(DoStatement doStatement) { + tokenConsumer.addToken(DO_WHILE_STATEMENT, doStatement, false); + expect(DO_WHILE_BLOCK_START, DO_WHILE_BLOCK_END); + } + + @Override + public void visit(EnumConstantDeclaration enumConstantDeclaration) { + tokenConsumer.addToken(ENUM_ELEMENT, enumConstantDeclaration, false); + } + + @Override + public void visit(EnumDeclaration enumDeclaration) { + tokenConsumer.addToken(ENUM_DECL_BEGIN, enumDeclaration, false); + } + + @Override + public void visit(FieldDeclaration fieldDeclaration) { + tokenConsumer.addToken(FIELD_DECL, fieldDeclaration, false); + } + + @Override + public void visit(ForEachStatement forEachStatement) { + tokenConsumer.addToken(FOR_STATEMENT, forEachStatement, false); + expect(FOR_STATEMENT, FOR_BLOCK_END); + } + + @Override + public void visit(ForStatement forStatement) { + tokenConsumer.addToken(FOR_STATEMENT, forStatement, false); + expect(FOR_BLOCK_BEGIN, FOR_BLOCK_END); + } + + @Override + public void visit(GotoStatement gotoStatement) { + tokenConsumer.addToken(GOTO_STATEMENT, gotoStatement, false); + } + + @Override + public void visit(IfStatement ifStatement) { + tokenConsumer.addToken(IF_STATEMENT, ifStatement, false); + + Statement elseStatement = ifStatement.getElseStatement(); + if (!Objects.isNull(elseStatement) && elseStatement instanceof Block) { + expect(ELSE_BLOCK_BEGIN, ELSE_BLOCK_END); + } + + if (ifStatement.getThenStatement() instanceof Block) { + expect(IF_BLOCK_BEGIN, IF_BLOCK_END); + } + + } + + @Override + public void visit(IncludeDeclaration includeDeclaration) { + tokenConsumer.addToken(INCLUDE, includeDeclaration, false); + } + + @Override + public void visit(LambdaExpression lambdaExpression) { + tokenConsumer.addToken(LAMBDA_EXPRESSION, lambdaExpression, false); + } + + @Override + public void visit(MemberCallExpression memberCallExpression) { + tokenConsumer.addToken(METHOD_CALL, memberCallExpression, false); + } + + @Override + public void visit(MethodDeclaration methodDeclaration) { + tokenConsumer.addToken(METHOD_DECL_BEGIN, methodDeclaration, false); + expect(METHOD_BODY_BEGIN, METHOD_BODY_END); + } + + @Override + public void visit(NewArrayExpression newArrayExpression) { + tokenConsumer.addToken(NEW_ARRAY, newArrayExpression, false); + } + + @Override + public void visit(ParameterDeclaration parameterDeclaration) { + tokenConsumer.addToken(METHOD_PARAM, parameterDeclaration, false); + } + + @Override + public void visit(RecordDeclaration recordDeclaration) { + tokenConsumer.addToken(RECORD_DECL_BEGIN, recordDeclaration, false); + } + + @Override + public void visit(ReturnStatement returnStatement) { + if (Objects.isNull(returnStatement.getLocation())) { + // implicit return without return value + return; + } + tokenConsumer.addToken(RETURN, returnStatement, false); + } + + @Override + public void visit(SwitchStatement switchStatement) { + tokenConsumer.addToken(SWITCH_STATEMENT, switchStatement, false); + expect(SWITCH_BLOCK_START, SWITCH_BLOCK_END); + } + + @Override + public void visit(SynchronizedStatement synchronizedStatement) { + tokenConsumer.addToken(SYNCHRONIZED_STATEMENT, synchronizedStatement, false); + expect(SYNCHRONIZED_BLOCK_START, SYNCHRONIZED_BLOCK_END); + } + + @Override + public void visit(TryStatement tryStatement) { + tokenConsumer.addToken(TRY_STATEMENT, tryStatement, false); + expect(TRY_BLOCK_START, TRY_BLOCK_END); + } + + @Override + void visit(UnaryOperator unaryoperator) { + + String operatorCode = unaryoperator.getOperatorCode(); + if (Objects.isNull(operatorCode)) + return; + if (Objects.equals(operatorCode, "throw")) { + tokenConsumer.addToken(THROW, unaryoperator, false); + } + + super.visit(unaryoperator); + } + + @Override + public void visit(VariableDeclaration variableDeclaration) { + tokenConsumer.addToken(VARIABLE_DECL, variableDeclaration, false); + } + + @Override + public void visit(WhileStatement whileStatement) { + tokenConsumer.addToken(WHILE_STATEMENT, whileStatement, false); + expect(WHILE_BLOCK_START, WHILE_BLOCK_END); + } + + private void expect(CpgTokenType opening, CpgTokenType closing) { + expectedBlocks.addFirst(new BlockTokens(opening, closing)); + } + + private record BlockTokens(CpgTokenType opening, CpgTokenType closing) { + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgToken.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgToken.java new file mode 100644 index 0000000000..ae7df9c174 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgToken.java @@ -0,0 +1,51 @@ +package de.jplag.java_cpg.token; + +import java.io.File; +import java.util.Objects; +import java.util.Optional; + +import de.fraunhofer.aisec.cpg.graph.Name; +import de.jplag.Token; +import de.jplag.TokenType; + +/** + * This class represents a Token in the context of the CPG module of JPlag. + */ +public class CpgToken extends Token { + private final Name name; + + /** + * Creates a new {@link CpgToken}. + * @param tokenType the {@link TokenType} + * @param file the {@link File} that contains the represented piece of code + * @param startLine the starting line of the represented code + * @param startColumn the starting column of the represented code + * @param length the length of the represented code + * @param name the name of the represented CPG node + */ + public CpgToken(TokenType tokenType, File file, int startLine, int startColumn, int length, Name name) { + super(tokenType, file, startLine, startColumn, length); + this.name = name; + } + + @Override + public String toString() { + return Objects.isNull(name) ? super.toString() : "%s(%s)".formatted(this.getType(), this.name); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CpgToken otherToken)) { + return false; + } + return this.getType().equals(otherToken.getType()); + } + + @Override + public int hashCode() { + return Optional.of(name).map(Name::hashCode).orElse(1) * getType().hashCode(); + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgTokenConsumer.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgTokenConsumer.java new file mode 100644 index 0000000000..5197e21822 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgTokenConsumer.java @@ -0,0 +1,72 @@ +package de.jplag.java_cpg.token; + +import java.io.File; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.graph.Name; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation; +import de.fraunhofer.aisec.cpg.sarif.Region; +import de.jplag.Token; +import de.jplag.TokenType; + +/** + * A {@link CpgTokenConsumer} can create appropriate {@link CpgToken}s for {@link TokenType}s. + */ +public abstract class CpgTokenConsumer implements TokenConsumer { + + private static final Logger logger = LoggerFactory.getLogger(CpgTokenConsumer.class); + /** + * This is used as the Token's length if the token spans multiple lines. The value is supposed to be greater than the + * length of any sensible line of code. + */ + private static final int MULTILINE_TOKEN_LENGTH = 1024; + private File currentFile; + + private static int calculateLength(Region region) { + if (region.getEndLine() == region.startLine) { + return region.getEndColumn() - region.startColumn + 1; + } else + return MULTILINE_TOKEN_LENGTH; + } + + /** + * Adds a new {@link Token} for the given {@link TokenType} and {@link Node}. + * @param type the {@link TokenType} + * @param node the represented {@link Node} + * @param isEndToken true iff the token represents the end of a block + */ + public void addToken(TokenType type, Node node, boolean isEndToken) { + logger.debug("{} / {}", type, node); + PhysicalLocation location = node.getLocation(); + + File file; + Region region; + int length; + if (Objects.isNull(location)) { + file = currentFile; + region = new Region(); + length = 0; + } else { + file = new File(location.getArtifactLocation().getUri()); + currentFile = file; + region = location.getRegion(); + length = calculateLength(region); + } + int line; + int column; + if (isEndToken) { + line = region.getEndLine(); + column = region.getEndColumn(); + } else { + line = region.startLine; + column = region.startColumn; + } + Name name = node.getName(); + addToken(type, file, line, column, length, name); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgTokenType.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgTokenType.java new file mode 100644 index 0000000000..3c31d369d5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/CpgTokenType.java @@ -0,0 +1,103 @@ +package de.jplag.java_cpg.token; + +import de.jplag.TokenType; + +/** + * A {@link CpgTokenType} represents a type of {@link CpgToken} related to a syntactic element of code. + */ +public enum CpgTokenType implements TokenType { + + INCLUDE("INCLUDE"), + + RECORD_DECL_BEGIN("CLASS{"), + RECORD_DECL_END("}CLASS"), + + ENUM_DECL_BEGIN("ENUM{"), + ENUM_DECL_END("}ENUM"), + ENUM_ELEMENT("ENUM_ELEMENT"), + + FIELD_DECL("FIELD_DECL", false), + + METHOD_DECL_BEGIN("METHOD_DECL"), + METHOD_PARAM("PARAM"), + METHOD_BODY_BEGIN("METHOD_BODY{"), + METHOD_BODY_END("}METHOD_BODY"), + + VARIABLE_DECL("VAR_DECL"), + ASSIGNMENT("ASSIGN"), + + METHOD_CALL("APPLY"), + METHOD_ARGUMENT("ARG"), + + CONSTRUCTOR_CALL("NEW()"), + NEW_ARRAY("NEW_ARRAY"), + + IF_STATEMENT("IF"), + IF_BLOCK_BEGIN("THEN{"), + IF_BLOCK_END("}THEN"), + ELSE_BLOCK_BEGIN("ELSE{"), + ELSE_BLOCK_END("}ELSE"), + + WHILE_STATEMENT("WHILE"), + WHILE_BLOCK_START("W_DO{"), + WHILE_BLOCK_END("}W_DO"), + + DO_WHILE_STATEMENT("DO_WHILE"), + DO_WHILE_BLOCK_START("DW_DO{"), + DO_WHILE_BLOCK_END("}DW_DO"), + + FOR_STATEMENT("FOR"), + FOR_BLOCK_BEGIN("FOR{"), + FOR_BLOCK_END("}FOR"), + SWITCH_STATEMENT("SWITCH{"), + SWITCH_BLOCK_START("SWITCH{"), + SWITCH_BLOCK_END("}SWITCH"), + CASE_STATEMENT("CASE"), + DEFAULT_STATEMENT("DEFAULT"), + + BREAK("BREAK"), + CONTINUE("CONTINUE"), + RETURN("RETURN"), + + GOTO_STATEMENT("GOTO"), + + ASSERT_STATEMENT("ASSERT"), + + TRY_STATEMENT("TRY{"), + TRY_BLOCK_START("TRY{"), + TRY_BLOCK_END("}TRY"), + THROW("THROW"), + CATCH_CLAUSE_BEGIN("CATCH{"), + CATCH_CLAUSE_END("}CATCH"), + FINALLY_CLAUSE_BEGIN("FINALLY{"), + FINALLY_CLAUSE_END("}FINALLY"), + + SYNCHRONIZED_STATEMENT("SYNC"), + SYNCHRONIZED_BLOCK_START("SYNC{"), + SYNCHRONIZED_BLOCK_END("}SYNC"), + LAMBDA_EXPRESSION("LAMBDA"), + BLOCK_BEGIN("{", false), + BLOCK_END("}", false); + + private final boolean isExcluded; + private final String description; + + CpgTokenType(String description) { + this(description, false); + } + + CpgTokenType(String description, boolean excluded) { + this.description = description; + this.isExcluded = excluded; + } + + @Override + public String getDescription() { + return this.description; + } + + @Override + public Boolean isExcludedFromMatching() { + return this.isExcluded; + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/TokenConsumer.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/TokenConsumer.java new file mode 100644 index 0000000000..0170d05188 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/TokenConsumer.java @@ -0,0 +1,26 @@ +package de.jplag.java_cpg.token; + +import java.io.File; + +import de.fraunhofer.aisec.cpg.graph.Name; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.Token; +import de.jplag.TokenType; + +/** + * This interface represents classes that can consume and save {@link Token}s. + */ +public interface TokenConsumer { + + /** + * Creates a new token to be consumed by this token consumer. + * @param type the token type + * @param file the file that contains the represented code + * @param startLine the line where the represented code starts + * @param startColumn the column where the represented code starts + * @param length The length of the represented code + * @param name the name of the represented {@link Node} + */ + void addToken(TokenType type, File file, int startLine, int startColumn, int length, Name name); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/VisitorExitor.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/VisitorExitor.java new file mode 100644 index 0000000000..f8d74c9a95 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/token/VisitorExitor.java @@ -0,0 +1,32 @@ +package de.jplag.java_cpg.token; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.processing.IVisitable; +import de.fraunhofer.aisec.cpg.processing.IVisitor; + +/** + * This class adds exit methods to the {@link IVisitor} class. This implementation is supposed to be a close replication + * of {@link IVisitor}. + * @param the object type to visit and exit + */ +public abstract class VisitorExitor> extends IVisitor { + + private static final String EXIT_METHOD_IDENTIFIER = "exit"; + + /** + * Calls the most specific implementation of exit for the given {@link Node}. + * @param node the node + */ + public void exit(V node) { + try { + Method mostSpecificExit = this.getClass().getMethod(EXIT_METHOD_IDENTIFIER, node.getClass()); + mostSpecificExit.invoke(this, node); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + // Do nothing, just like in IVisitor + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/GraphTransformation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/GraphTransformation.java new file mode 100644 index 0000000000..4d15b0b5e7 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/GraphTransformation.java @@ -0,0 +1,355 @@ +package de.jplag.java_cpg.transformation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgNthEdge; +import de.jplag.java_cpg.transformation.matching.pattern.GraphPattern; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.MultiGraphPattern; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; +import de.jplag.java_cpg.transformation.matching.pattern.SimpleGraphPattern; +import de.jplag.java_cpg.transformation.matching.pattern.relation.Relation; +import de.jplag.java_cpg.transformation.operations.CreateNodeOperation; +import de.jplag.java_cpg.transformation.operations.DummyNeighbor; +import de.jplag.java_cpg.transformation.operations.GraphOperation; +import de.jplag.java_cpg.transformation.operations.InsertOperation; +import de.jplag.java_cpg.transformation.operations.RemoveOperation; +import de.jplag.java_cpg.transformation.operations.ReplaceOperation; +import de.jplag.java_cpg.transformation.operations.SetOperation; + +/** + * This saves all information related to a transformation on a graph. + */ +public interface GraphTransformation { + + /** + * Applies the transformation to the Graph represented by the given {@link Match} which indicates which {@link Node}s + * shall be involved in the transformation. + * @param match the match of this {@link GraphTransformation}'s source pattern to a concrete graph + * @param ctx the current {@link TranslationContext} + */ + void apply(Match match, TranslationContext ctx); + + /** + * Gets the {@link ExecutionOrder} for this {@link GraphTransformation}. + * @return the execution order + */ + ExecutionOrder getExecutionOrder(); + + /** + * Gets the name for this {@link GraphTransformation}. + * @return the name + */ + String getName(); + + /** + * Gets the {@link ExecutionPhase} for this {@link GraphTransformation}. + * @return the execution phase + */ + ExecutionPhase getPhase(); + + /** + * Gets the source {@link GraphPattern} for this {@link GraphTransformation}. + * @return the source pattern + */ + GraphPattern getSourcePattern(); + + /** + * Determines in which transformation pass this transformation is executed. + */ + enum ExecutionPhase { + + /** + * Executes right after the construction of the AST, to ensure its well-formedness. + */ + OBLIGATORY(false), + + /** + * Executes before the EOG is constructed. Used for AST-altering transformations. + */ + AST_TRANSFORM(false), + /** + * Executes after the EOG is constructed, right before the TokenizationPass. Usages: + *

    + *
  • Transformations that rely on usage, type information
  • + *
  • Removing elements that shall be excluded from Tokenization
  • + *
+ */ + CPG_TRANSFORM(true); + + public final boolean disconnectEog; + + ExecutionPhase(boolean disconnectEog) { + this.disconnectEog = disconnectEog; + } + } + + /** + * Determines the order in which matches of this {@link GraphTransformation} should be processed in order to ensure + * correct and efficient processing. + */ + enum ExecutionOrder { + + ASCENDING_LOCATION, + DESCENDING_LOCATION + } + + class GraphTransformationImpl implements GraphTransformation { + private static final Logger logger = LoggerFactory.getLogger(GraphTransformationImpl.class); + protected final GraphPattern sourcePattern; + protected final GraphPattern targetPattern; + private final List> newNodes; + private final List operations; + private final String name; + private final ExecutionPhase phase; + private final ExecutionOrder executionOrder; + + public GraphTransformationImpl(GraphPattern sourcePattern, GraphPattern targetPattern, String name, ExecutionPhase phase, + List> newNodes, List operations, ExecutionOrder executionOrder) { + this.sourcePattern = sourcePattern; + this.targetPattern = targetPattern; + this.name = name; + this.phase = phase; + this.newNodes = newNodes; + this.operations = operations; + this.executionOrder = executionOrder; + } + + @Override + public void apply(Match match, TranslationContext ctx) { + List concreteOperations = instantiate(operations, match); + + // create nodes of the target graph missing from the source graph + newNodes.forEach(op -> op.resolveAndApply(match, ctx)); + + logger.debug("Apply {} to node {}", name, match); + // apply other operations + apply(match, concreteOperations, ctx); + } + + @Override + public ExecutionOrder getExecutionOrder() { + return this.executionOrder; + } + + @Override + public String getName() { + return name; + } + + @Override + public ExecutionPhase getPhase() { + return phase; + } + + @Override + public GraphPattern getSourcePattern() { + return sourcePattern; + } + + /** + * Applies the given list of {@link GraphOperation}s to the {@link Match}, following the structure of the + * {@link NodePattern}. + * @param match the match of the graph transformations source pattern to the concrete CPG + * @param operations the list of transformations to apply + * @param ctx the translation context of the current translation + */ + protected void apply(Match match, List operations, TranslationContext ctx) { + for (GraphOperation op : operations) { + try { + op.resolveAndApply(match, ctx); + } catch (RuntimeException e) { + throw new TransformationException(e); + } + } + DummyNeighbor.getInstance().clear(); + } + + private List instantiate(List operations, Match match) { + return operations.stream().map((GraphOperation op) -> { + if (op.isWildcarded()) { + return op.instantiateWildcard(match); + } else if (op.isMultiEdged()) { + return op.instantiateAnyOfNEdge(match); + } + return op; + }).toList(); + } + + @Override + public String toString() { + return getName(); + } + } + + /** + * A {@link Builder} computes the steps of a {@link GraphTransformation} from the source and target + * {@link SimpleGraphPattern}s. + */ + class Builder { + private final GraphPattern sourcePattern; + private final GraphPattern targetPattern; + private final String name; + private final ExecutionPhase phase; + private ExecutionOrder executionOrder; + + private Builder(GraphPattern sourcePattern, GraphPattern targetPattern, String transformationName, ExecutionPhase phase) { + this.sourcePattern = sourcePattern; + this.targetPattern = targetPattern; + this.name = transformationName; + this.phase = phase; + this.executionOrder = ExecutionOrder.DESCENDING_LOCATION; + } + + /** + * Returns a {@link Builder} for a {@link GraphTransformation} based on the given source and target + * {@link SimpleGraphPattern}s. + * @param the common root {@link Node} type of the {@link SimpleGraphPattern}s + * @param sourcePattern the source {@link SimpleGraphPattern} + * @param targetPattern the target {@link SimpleGraphPattern} + * @param name the transformation name + * @param phase determines when to apply the transformation + * @return a {@link Builder} for a {@link GraphTransformation} between source and target + */ + public static Builder from(SimpleGraphPattern sourcePattern, SimpleGraphPattern targetPattern, String name, + ExecutionPhase phase) { + return new Builder(sourcePattern, targetPattern, name, phase); + } + + public static Builder from(MultiGraphPattern sourcePattern, MultiGraphPattern targetPattern, String name, ExecutionPhase phase) { + return new Builder(sourcePattern, targetPattern, name, phase); + } + + public GraphTransformation build() { + return this.calculateTransformation(); + } + + private GraphTransformation calculateTransformation() { + List> newNodes = this.createNewNodes(sourcePattern, targetPattern); + List ops = new ArrayList<>(); + sourcePattern.compareTo(targetPattern, (srcPattern, tgtPattern) -> compare(srcPattern, tgtPattern, null, ops, null)); + + return new GraphTransformationImpl(sourcePattern, targetPattern, name, phase, newNodes, ops, executionOrder); + } + + /** + * @param

(super)type of the parent node, specified by the incoming edge + * @param common type of the current source and target node, defined by the incoming edge + * @param actual concrete type of the source node + * @param actual concrete type of the target node + * @param source current node pattern of the source graph + * @param target current node pattern of the target graph + * @param parent current node pattern of the parent node + * @param ops list to save transformation operations in + * @param incomingEdge edge by which this node was visited + */ + private void compare(NodePattern source, NodePattern target, + NodePattern parent, List ops, CpgEdge incomingEdge) { + + Role srcRole = sourcePattern.getRole(source); + Role tgtRole = targetPattern.getRole(target); + + NodePattern newSource; + if (Objects.equals(srcRole, tgtRole)) { + // equal role name indicates type compatibility + newSource = (NodePattern) source; + } else { + + boolean disconnectEog = this.phase.disconnectEog && incomingEdge.isAst(); + + /* + * Three cases: 1. Source and target not null -> replace 2. Source not null, target null -> remove 3. Source null, + * target not null -> insert/set + */ + + if (!Objects.isNull(tgtRole) && !Objects.isNull(srcRole)) { + newSource = sourcePattern.getPattern(tgtRole, target.getRootClass()); + ops.add(new ReplaceOperation<>(parent, incomingEdge, newSource, disconnectEog)); + } else if (Objects.isNull(srcRole)) { + newSource = sourcePattern.getPattern(tgtRole, target.getRootClass()); + if (incomingEdge instanceof CpgNthEdge nthEdge) { + ops.add(new InsertOperation<>(parent, nthEdge, newSource, disconnectEog)); + } else { + ops.add(new SetOperation<>(parent, incomingEdge, newSource)); + } + } else { + // tgtRole == null + ops.add(new RemoveOperation<>(parent, incomingEdge, disconnectEog)); + return; + } + + } + if (newSource.shouldStopRecursion()) { + return; + } + newSource.markStopRecursion(); + + handleRelationships(newSource, target, ops); + + } + + private List> createNewNodes(GraphPattern sourcePattern, GraphPattern targetPattern) { + List newRoles = new ArrayList<>(targetPattern.getAllRoles()); + newRoles.removeAll(sourcePattern.getAllRoles()); + + List> newNodes = new ArrayList<>(); + for (Role role : newRoles) { + // new node pattern needed for the transformation calculation + NodePattern newPattern = sourcePattern.addNode(role, targetPattern.getPattern(role)); + + // new nodes needed for the transformation application + CreateNodeOperation createNodeOperation = new CreateNodeOperation<>(sourcePattern, role, newPattern); + newNodes.add(createNodeOperation); + } + return newNodes; + } + + private void handleRelationships(NodePattern source, NodePattern target, List ops) { + source.handleRelationships(target, RelationComparisonFunction.from(this, ops)); + } + + public GraphTransformation.Builder setExecutionOrder(ExecutionOrder executionOrder) { + this.executionOrder = executionOrder; + return this; + } + + @FunctionalInterface + public interface RelationComparisonFunction { + + static RelationComparisonFunction from(Builder builder, List ops) { + return new RelationComparisonFunction() { + @Override + public void compare(NodePattern source, NodePattern target, + NodePattern parent, CpgEdge incomingEdge) { + builder.compare(source, target, parent, ops, incomingEdge); + } + }; + } + + default void castAndCompare(Relation source, Relation target, + NodePattern.NodePatternImpl

parent) { + Relation castTarget = (Relation) target; + CpgEdge edge = switch (source.getEdge()) { + case CpgMultiEdge multiEdge -> multiEdge.getAnyOfNEdgeTo(source.pattern); + case CpgEdge singleEdge -> singleEdge; + default -> throw new TransformationException("Relation edge must be CpgEdge or CpgMultiEdge"); + }; + + compare(source.pattern, castTarget.pattern, parent, edge); + } + + void compare(NodePattern source, NodePattern target, + NodePattern parent, CpgEdge incomingEdge); + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/Role.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/Role.java new file mode 100644 index 0000000000..2a60e44a1e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/Role.java @@ -0,0 +1,108 @@ +package de.jplag.java_cpg.transformation; + +public record Role(String name) { + public static final Role ARGUMENT = new Role("argument"); + public static final Role ASSIGN_EXPRESSION = new Role("assignExpression"); + + public static final Role BODY = new Role("body"); + + public static final Role CLASS_DECLARATION = new Role("classDeclaration"); + + public static final Role CONDITION = new Role("condition"); + + public static final Role CONSTRUCTOR_DECLARATION = new Role("constructorDeclaration"); + + public static final Role CONTAINING_FILE = new Role("containingFile"); + public static final Role CONTAINING_STATEMENT = new Role("containingStatement"); + + public static final Role CONTAINING_RECORD = new Role("containingRecord"); + + public static final Role DECLARATION = new Role("declaration"); + + public static final Role DECLARATION_CONTAINER = new Role("declarationContainer"); + + public static final Role DECLARATION_STATEMENT = new Role("declarationStatement"); + + public static final Role DEFINING_RECORD = new Role("definingRecord"); + + public static final Role DEFINING_RECORD_REFERENCE = new Role("definingRecordReference"); + + public static final Role DO_STATEMENT = new Role("doStatement"); + + public static final Role ELSE_STATEMENT = new Role("elseStatement"); + + public static final Role EMPTY_FILE = new Role("emptyFile"); + + public static final Role EMPTY_RECORD = new Role("emptyRecord"); + + public static final Role FIELD_DECLARATION = new Role("fieldDeclaration"); + + public static final Role FIELD_USAGE = new Role("fieldUsage"); + public static final Role FIELD_REFERENCE = new Role("fieldReference"); + + public static final Role FIELD_VALUE = new Role("fieldValue"); + public static final Role FIELD_TYPE = new Role("fieldType"); + + public static final Role FIRST_CONSTANT_USAGE = new Role("firstConstantUsage"); + + public static final Role FOR_STATEMENT = new Role("forStatement"); + + public static final Role GETTER_METHOD_REFERENCE = new Role("getMethodReference"); + + public static final Role IF_STATEMENT = new Role("ifStatement"); + + public static final Role INITIALIZATION_STATEMENT = new Role("initializationStatement"); + + public static final Role INNER_CONDITION = new Role("innerCondition"); + + public static final Role ITERATION_STATEMENT = new Role("iterationStatement"); + + public static final Role MEMBER_CALL = new Role("memberCall"); + + public static final Role METHOD_BLOCK = new Role("methodBlock"); + public static final Role METHOD_BODY = new Role("methodBody"); + + public static final Role METHOD_DECLARATION = new Role("methodDeclaration"); + + public static final Role METHOD_TYPE = new Role("methodType"); + + public static final Role OPTIONAL_CLASS = new Role("optionalClass"); + + public static final Role OPTIONAL_OBJECT = new Role("optionalObject"); + public static final Role PARAMETER_DECLARATION = new Role("parameterDeclaration"); + public static final Role PARAMETER_REFERENCE = new Role("parameterReference"); + + public static final Role PROJECT = new Role("project"); + + public static final Role RECORD_DECLARATION = new Role("recordDeclaration"); + + public static final Role RETURN_STATEMENT = new Role("returnStatement"); + + public static final Role RETURN_TYPE = new Role("returnType"); + + public static final Role RETURN_VALUE = new Role("returnValue"); + + public static final Role SCOPE_BLOCK = new Role("scopeBlock"); + + public static final Role SURROUNDING_BLOCK = new Role("surroundingBlock"); + + public static final Role THEN_STATEMENT = new Role("thenStatement"); + + public static final Role THROW_EXCEPTION = new Role("throwException"); + + public static final Role USING_RECORD = new Role("usingRecord"); + + public static final Role VARIABLE_DECLARATION = new Role("variableDeclaration"); + + public static final Role VARIABLE_VALUE = new Role("variableValue"); + public static final Role VOID_TYPE = new Role("voidType"); + + public static final Role WHILE_STATEMENT = new Role("whileStatement"); + + public static final Role WHILE_STATEMENT_BODY = new Role("whileStatementBody"); + + public static final Role VARIABLE_USAGE = new Role("variableUsage"); + + public static final Role WHILE_BLOCK = new Role("whileBlock"); + public static final Role WRAPPING_BLOCK = new Role("wrappingBlock"); +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/TransformationException.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/TransformationException.java new file mode 100644 index 0000000000..e0d505fd02 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/TransformationException.java @@ -0,0 +1,15 @@ +package de.jplag.java_cpg.transformation; + +/** + * An {@link Exception} that relates to the Transformation process. + */ +public class TransformationException extends RuntimeException { + + public TransformationException(String msg) { + super(msg); + } + + public TransformationException(Exception e) { + super(e); + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/TransformationRepository.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/TransformationRepository.java new file mode 100644 index 0000000000..0e44e7d921 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/TransformationRepository.java @@ -0,0 +1,828 @@ +package de.jplag.java_cpg.transformation; + +import static de.jplag.java_cpg.transformation.GraphTransformation.ExecutionOrder.ASCENDING_LOCATION; +import static de.jplag.java_cpg.transformation.GraphTransformation.ExecutionPhase.AST_TRANSFORM; +import static de.jplag.java_cpg.transformation.GraphTransformation.ExecutionPhase.CPG_TRANSFORM; +import static de.jplag.java_cpg.transformation.GraphTransformation.ExecutionPhase.OBLIGATORY; +import static de.jplag.java_cpg.transformation.Role.ARGUMENT; +import static de.jplag.java_cpg.transformation.Role.BODY; +import static de.jplag.java_cpg.transformation.Role.CLASS_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.CONDITION; +import static de.jplag.java_cpg.transformation.Role.CONSTRUCTOR_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.CONTAINING_FILE; +import static de.jplag.java_cpg.transformation.Role.CONTAINING_RECORD; +import static de.jplag.java_cpg.transformation.Role.CONTAINING_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.DECLARATION; +import static de.jplag.java_cpg.transformation.Role.DECLARATION_CONTAINER; +import static de.jplag.java_cpg.transformation.Role.DECLARATION_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.DEFINING_RECORD; +import static de.jplag.java_cpg.transformation.Role.DEFINING_RECORD_REFERENCE; +import static de.jplag.java_cpg.transformation.Role.DO_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.ELSE_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.EMPTY_RECORD; +import static de.jplag.java_cpg.transformation.Role.FIELD_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.FIELD_USAGE; +import static de.jplag.java_cpg.transformation.Role.FIELD_VALUE; +import static de.jplag.java_cpg.transformation.Role.FIRST_CONSTANT_USAGE; +import static de.jplag.java_cpg.transformation.Role.FOR_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.GETTER_METHOD_REFERENCE; +import static de.jplag.java_cpg.transformation.Role.IF_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.INITIALIZATION_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.INNER_CONDITION; +import static de.jplag.java_cpg.transformation.Role.ITERATION_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.MEMBER_CALL; +import static de.jplag.java_cpg.transformation.Role.METHOD_BLOCK; +import static de.jplag.java_cpg.transformation.Role.METHOD_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.METHOD_TYPE; +import static de.jplag.java_cpg.transformation.Role.OPTIONAL_CLASS; +import static de.jplag.java_cpg.transformation.Role.OPTIONAL_OBJECT; +import static de.jplag.java_cpg.transformation.Role.RECORD_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.RETURN_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.RETURN_TYPE; +import static de.jplag.java_cpg.transformation.Role.RETURN_VALUE; +import static de.jplag.java_cpg.transformation.Role.SCOPE_BLOCK; +import static de.jplag.java_cpg.transformation.Role.SURROUNDING_BLOCK; +import static de.jplag.java_cpg.transformation.Role.THEN_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.THROW_EXCEPTION; +import static de.jplag.java_cpg.transformation.Role.USING_RECORD; +import static de.jplag.java_cpg.transformation.Role.VARIABLE_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.VARIABLE_USAGE; +import static de.jplag.java_cpg.transformation.Role.VARIABLE_VALUE; +import static de.jplag.java_cpg.transformation.Role.WHILE_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.WHILE_STATEMENT_BODY; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.BLOCK__STATEMENTS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.CALL_EXPRESSION__ARGUMENTS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.CALL_EXPRESSION__CALLEE; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.CALL_EXPRESSION__INVOKES; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.DECLARATION_STATEMENT__DECLARATIONS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.DO_STATEMENT__STATEMENT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.FIELD_DECLARATION__MODIFIERS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.FOR_STATEMENT__CONDITION; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.FOR_STATEMENT__INITIALIZER_STATEMENT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.FOR_STATEMENT__ITERATION_STATEMENT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.FOR_STATEMENT__STATEMENT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.FUNCTION_TYPE__RETURN_TYPES; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.IF_STATEMENT__CONDITION; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.IF_STATEMENT__ELSE_STATEMENT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.IF_STATEMENT__THEN_STATEMENT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.MEMBER_EXPRESSION__BASE; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.MEMBER_EXPRESSION__RECORD_DECLARATION; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.METHOD_DECLARATION__BODY; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.METHOD_DECLARATION__LOCAL_NAME; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.METHOD_DECLARATION__RECORD_DECLARATION; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.NAMESPACE_DECLARATION__DECLARATIONS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.NODE__LOCATION; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.NODE__NAME; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.RECORD_DECLARATION__CONSTRUCTORS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.RECORD_DECLARATION__FIELDS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.RECORD_DECLARATION__METHODS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.RECORD_DECLARATION__NAME; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.REFERENCE__REFERS_TO; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.RETURN_STATEMENT__RETURN_VALUES; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.STATEMENT__LOCALS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.TRANSLATION_UNIT__DECLARATIONS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.UNARY_OPERATOR__INPUT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.UNARY_OPERATOR__OPERATOR_CODE; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.VALUE_DECLARATION__TYPE; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.VALUE_DECLARATION__USAGES; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.VARIABLE_DECLARATION__INITIALIZER; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.WHILE_STATEMENT__CONDITION; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.WHILE_STATEMENT__STATEMENT; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.attributeContains; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.attributeEquals; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.attributeToStringEquals; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.attributeToStringStartsWith; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.isConstant; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.isFieldReference; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.nElements; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.notEmpty; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.notInstanceOf; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.notNull; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.nthElement; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.or; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DoStatement; +import de.fraunhofer.aisec.cpg.graph.statements.EmptyStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement; +import de.fraunhofer.aisec.cpg.graph.statements.Statement; +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator; +import de.fraunhofer.aisec.cpg.graph.types.FunctionType; +import de.fraunhofer.aisec.cpg.graph.types.ObjectType; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.pattern.GraphPatternBuilder; +import de.jplag.java_cpg.transformation.matching.pattern.MultiGraphPattern; +import de.jplag.java_cpg.transformation.matching.pattern.PatternUtil; +import de.jplag.java_cpg.transformation.matching.pattern.SimpleGraphPattern; + +/** + * Contains factory methods to create different {@link GraphTransformation}s. + */ +public class TransformationRepository { + /* + * These constants are supposed to avoid uselessly building the same graph transformations multiple times. + * Alternatively, all factory methods could be public and use private fields to create a kind-of singleton pattern. + */ + /** + * Constant ifWithNegatedConditionResolution. + */ + public static final GraphTransformation ifWithNegatedConditionResolution = ifWithNegatedConditionResolution(); + /** + * Constant forStatementToWhileStatement. + */ + public static final GraphTransformation forStatementToWhileStatement = forStatementToWhileStatement(); + /** + * Constant removeGetterMethod. + */ + public static final GraphTransformation removeGetterMethod = removeGetterMethod(); + /** + * Constant removeUnusedVariableDeclaration. + */ + public static final GraphTransformation removeUnusedVariableDeclaration = removeUnusedVariableDeclaration(); + + /** + * Constant removeEmptyDeclarationStatement. + */ + public static final GraphTransformation removeEmptyDeclarationStatement = removeEmptyDeclarationStatement(); + /** + * Constant removeLibraryRecord. + */ + public static final GraphTransformation removeLibraryRecord = removeLibraryRecord(); + /** + * Constant removeLibraryField. + */ + public static final GraphTransformation removeLibraryField = removeLibraryField(); + /** + * Constant moveConstantToOnlyUsingClass. + */ + public static final GraphTransformation moveConstantToOnlyUsingClass = moveConstantToOnlyUsingClass(); + /** + * Constant inlineSingleUseVariable. + */ + public static final GraphTransformation inlineSingleUseVariable = inlineSingleUseVariable(); + + /** + * Constant inlineSingleUseConstant. + */ + public static final GraphTransformation inlineSingleUseConstant = inlineSingleUseConstant(); + /** + * Constant removeEmptyConstructor. + */ + public static final GraphTransformation removeEmptyConstructor = removeEmptyConstructor(); + + /** + * Constant removeEmptyRecord. + */ + public static final GraphTransformation removeEmptyRecord = removeEmptyRecord(); + /** + * Constant removeImplicitStandardConstructor. + */ + public static final GraphTransformation removeImplicitStandardConstructor = removeImplicitStandardConstructor(); + /** + * Constant removeOptionalOfCall. + */ + public static final GraphTransformation removeOptionalOfCall = removeOptionalOfCall(); + /** + * Constant removeOptionalGetCall. + */ + public static final GraphTransformation removeOptionalGetCall = removeOptionalGetCall(); + /** + * Constant removeUnsupportedConstructor. + */ + public static final GraphTransformation removeUnsupportedConstructor = removeUnsupportedConstructor(); + /** + * Constant removeUnsupportedMethod. + */ + public static final GraphTransformation removeUnsupportedMethod = removeUnsupportedMethod(); + /** + * Constant wrapElseStatement. + */ + public static final GraphTransformation wrapElseStatement = wrapElseStatement(); + /** + * Constant wrapForStatement. + */ + public static final GraphTransformation wrapForStatement = wrapForStatement(); + /** + * Constant wrapThenStatement. + */ + public static final GraphTransformation wrapThenStatement = wrapThenStatement(); + /** + * Constant wrapWhileStatement. + */ + public static final GraphTransformation wrapWhileStatement = wrapWhileStatement(); + /** + * Constant wrapDoStatement. + */ + public static final GraphTransformation wrapDoStatement = wrapDoStatement(); + + private TransformationRepository() { + } + + /** + * Creates a {@link GraphTransformation} that un-negates the condition of an if statement and swaps the then and else + * blocks. + * @return the graph transformation object + */ + private static GraphTransformation ifWithNegatedConditionResolution() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(IfStatement.class, IF_STATEMENT, + related(IF_STATEMENT__CONDITION, UnaryOperator.class, CONDITION, + property(attributeEquals(UNARY_OPERATOR__OPERATOR_CODE, "!")), + related(UNARY_OPERATOR__INPUT, Expression.class, INNER_CONDITION)), + related(IF_STATEMENT__THEN_STATEMENT, Statement.class, THEN_STATEMENT), + related(IF_STATEMENT__ELSE_STATEMENT, Statement.class, ELSE_STATEMENT)); + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + + @Override + public SimpleGraphPattern build() { + return create(IfStatement.class, IF_STATEMENT, related(IF_STATEMENT__CONDITION, Expression.class, INNER_CONDITION), + related(IF_STATEMENT__THEN_STATEMENT, Statement.class, ELSE_STATEMENT), + related(IF_STATEMENT__ELSE_STATEMENT, Statement.class, THEN_STATEMENT)); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "ifWithNegatedConditionResolution", AST_TRANSFORM) + .setExecutionOrder(ASCENDING_LOCATION).build(); + } + + /** + * Creates a {@link GraphTransformation} that replaces a {@link VariableDeclaration} of an unused variable by an + * {@link EmptyStatement}. + * @return the graph transformation object + */ + private static GraphTransformation removeUnusedVariableDeclaration() { + MultiGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot( + wildcardParent(VariableDeclaration.class, VARIABLE_DECLARATION, setRepresentingNode(), + property(nElements(VALUE_DECLARATION__USAGES, 0))), + create(Statement.class, CONTAINING_STATEMENT, + relatedExisting1ToN(STATEMENT__LOCALS, VariableDeclaration.class, VARIABLE_DECLARATION))); + } + }.build(); + MultiGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot( + // Remove VariableDeclaration + emptyWildcardParent(), + // Remove reference to local variable + create(Statement.class, CONTAINING_STATEMENT)); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeUnusedVariableDeclarations", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that removes a {@link DeclarationStatement} with no {@link Declaration}s. + * @return the graph transformation object + */ + private static GraphTransformation removeEmptyDeclarationStatement() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return wildcardParent(DeclarationStatement.class, DECLARATION_STATEMENT, setRepresentingNode(), + property(PatternUtil.nElements(DECLARATION_STATEMENT__DECLARATIONS, 0))); + } + }.build(); + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return emptyWildcardParent(); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeEmptyDeclarationStatement", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that replaces {@link ForStatement}s by equivalent {@link WhileStatement}s. + * @return the graph transformation + */ + private static GraphTransformation forStatementToWhileStatement() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return wildcardParent(ForStatement.class, FOR_STATEMENT, setRepresentingNode(), + related(FOR_STATEMENT__INITIALIZER_STATEMENT, Statement.class, INITIALIZATION_STATEMENT), + related(FOR_STATEMENT__CONDITION, Expression.class, CONDITION), + related(FOR_STATEMENT__ITERATION_STATEMENT, Statement.class, ITERATION_STATEMENT), + related(FOR_STATEMENT__STATEMENT, Statement.class, BODY)); + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return wildcardParent(Block.class, SURROUNDING_BLOCK, + relatedConsecutive(BLOCK__STATEMENTS, Statement.class, node(Statement.class, INITIALIZATION_STATEMENT), + node(WhileStatement.class, WHILE_STATEMENT, related(WHILE_STATEMENT__CONDITION, Expression.class, CONDITION), + related(WHILE_STATEMENT__STATEMENT, Block.class, WHILE_STATEMENT_BODY, relatedConsecutive(BLOCK__STATEMENTS, + Statement.class, node(Statement.class, BODY), node(Statement.class, ITERATION_STATEMENT)))))); + } + + }.build(); + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "forStmtToWhileStmt", AST_TRANSFORM) + .setExecutionOrder(ASCENDING_LOCATION).build(); + + } + + /** + * Creates a {@link GraphTransformation} that removes fields without a proper location from RecordDeclarations. These + * are fields of library classes that the CPG is unable to resolve correctly. + * @return the graph transformation + */ + private static GraphTransformation removeLibraryField() { + /* + * The SymbolResolver pass may interpret references to the standard library (e.g. java.util.List) as fields of the + * current record. This transformation removes these fields. + */ + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, RECORD_DECLARATION, related1ToN(RECORD_DECLARATION__FIELDS, FieldDeclaration.class, + FIELD_DECLARATION, setRepresentingNode(), property(attributeEquals(NODE__LOCATION, null)))); + } + }.build(); + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, RECORD_DECLARATION); + } + }.build(); + + // SymbolResolver comes after TransformationPass -> Phase 2 + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeLibraryFields", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that removes records without a proper location from + * TranslationUnitDeclarations. These are classes from libraries that the CPG is unable to resolve correctly. + * @return the graph transformation + */ + private static GraphTransformation removeLibraryRecord() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(TranslationUnitDeclaration.class, DECLARATION_CONTAINER, related1ToN(TRANSLATION_UNIT__DECLARATIONS, + RecordDeclaration.class, DECLARATION, setRepresentingNode(), property(attributeEquals(NODE__LOCATION, null)))); + } + }.build(); + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(TranslationUnitDeclaration.class, DECLARATION_CONTAINER); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeLibraryRecords", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that moves constant declarations that are only used in one class to that class. + * This serves to counter the formation of constant classes. + * @return the graph transformation + */ + private static GraphTransformation moveConstantToOnlyUsingClass() { + + MultiGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot( + create(RecordDeclaration.class, DEFINING_RECORD, related1ToN(RECORD_DECLARATION__FIELDS, FieldDeclaration.class, + FIELD_DECLARATION, setRepresentingNode(), stopRecursion(), // no transformations beyond this point + property(attributeContains(FIELD_DECLARATION__MODIFIERS, "final")), + property(attributeContains(FIELD_DECLARATION__MODIFIERS, "static")), property(notEmpty(VALUE_DECLARATION__USAGES)), + related(nthElement(VALUE_DECLARATION__USAGES, 0), MemberExpression.class, FIRST_CONSTANT_USAGE, + related(MEMBER_EXPRESSION__RECORD_DECLARATION, RecordDeclaration.class, USING_RECORD, + notEqualTo(DEFINING_RECORD))), + forAllRelated(VALUE_DECLARATION__USAGES, MemberExpression.class, FIELD_USAGE, + relatedExisting(MEMBER_EXPRESSION__RECORD_DECLARATION, RecordDeclaration.class, USING_RECORD)))), + create(RecordDeclaration.class, USING_RECORD + // field declaration should go here + ), create(MemberExpression.class, FIRST_CONSTANT_USAGE, related(MEMBER_EXPRESSION__BASE, Reference.class, DEFINING_RECORD_REFERENCE, + relatedExisting(REFERENCE__REFERS_TO, RecordDeclaration.class, DEFINING_RECORD))) + + ); + } + }.build(); + MultiGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot(create(RecordDeclaration.class, DEFINING_RECORD + // field declaration removed from here + ), create(RecordDeclaration.class, USING_RECORD, related1ToN(RECORD_DECLARATION__FIELDS, FieldDeclaration.class, FIELD_DECLARATION)), + create(MemberExpression.class, FIRST_CONSTANT_USAGE, + related(MEMBER_EXPRESSION__BASE, Reference.class, DEFINING_RECORD_REFERENCE, + // adjust reference + relatedExisting(REFERENCE__REFERS_TO, RecordDeclaration.class, USING_RECORD)))); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "moveConstantToOnlyUsingClass", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that, for a variable that is only referenced once, replaces that reference by + * the value of the variable, if the value is determined to be stable between the variable declaration and the + * reference. + * @return the graph transformation + */ + private static GraphTransformation inlineSingleUseVariable() { + MultiGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot( + // parent pointer + wildcardParent(VariableDeclaration.class, VARIABLE_DECLARATION, setRepresentingNode(), property(notNull(NODE__LOCATION)), + property(nElements(VALUE_DECLARATION__USAGES, 1)), + related(nthElement(VALUE_DECLARATION__USAGES, 0), Reference.class, VARIABLE_USAGE), + related(VARIABLE_DECLARATION__INITIALIZER, Expression.class, VARIABLE_VALUE, + assignedValueStableBetween(VARIABLE_DECLARATION, VARIABLE_USAGE))), + wildcardParent(Reference.class, VARIABLE_USAGE, + relatedExisting(REFERENCE__REFERS_TO, VariableDeclaration.class, VARIABLE_DECLARATION)), + wildcardParent(Block.class, SCOPE_BLOCK, + relatedExisting1ToN(STATEMENT__LOCALS, VariableDeclaration.class, VARIABLE_DECLARATION))); + } + }.build(); + + MultiGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot( + // remove variable declaration from AST parent + emptyWildcardParent(), + // replace variable reference by value + wildcardParent(Expression.class, VARIABLE_VALUE), + // delete "local" edge + wildcardParent(Block.class, SCOPE_BLOCK)); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "inlineSingleUseVariable", CPG_TRANSFORM) + .setExecutionOrder(ASCENDING_LOCATION).build(); + } + + /** + * Creates a {@link GraphTransformation} that, for a constant that is only referenced once, replaces that reference by + * the value of the variable. + * @return the graph transformation + */ + private static GraphTransformation inlineSingleUseConstant() { + MultiGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot( + // parent pointer + create(RecordDeclaration.class, CONTAINING_RECORD, + related1ToN(RECORD_DECLARATION__FIELDS, FieldDeclaration.class, FIELD_DECLARATION, setRepresentingNode(), + property(notNull(NODE__LOCATION)), property(attributeContains(FIELD_DECLARATION__MODIFIERS, "final")), + property(attributeContains(FIELD_DECLARATION__MODIFIERS, "static")), + property(nElements(VALUE_DECLARATION__USAGES, 1)), + related(nthElement(VALUE_DECLARATION__USAGES, 0), Reference.class, FIELD_USAGE), + related(VARIABLE_DECLARATION__INITIALIZER, Expression.class, FIELD_VALUE))), + wildcardParent(Reference.class, FIELD_USAGE, + relatedExisting(REFERENCE__REFERS_TO, FieldDeclaration.class, FIELD_DECLARATION))); + } + }.build(); + + MultiGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot( + // remove field declaration from class + create(RecordDeclaration.class, CONTAINING_RECORD), + // replace variable reference by value + wildcardParent(Expression.class, FIELD_VALUE)); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "inlineSingleUseConstant", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that removes ConstructorDeclarations without proper locations from records. + * These are representations of the implicit constructor without parameters inherited from the Object class. + * @return the graph transformation + */ + private static GraphTransformation removeImplicitStandardConstructor() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + + return create(RecordDeclaration.class, DEFINING_RECORD, related1ToN(RECORD_DECLARATION__CONSTRUCTORS, ConstructorDeclaration.class, + CONSTRUCTOR_DECLARATION, property(attributeEquals(NODE__LOCATION, null)))); + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + // remove constructor declaration from record + return create(RecordDeclaration.class, DEFINING_RECORD); + } + }.build(); + + // Must run in PHASE_TWO, otherwise any reference to the standard constructor reinserts a ConstructorDeclaration + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeImplicitStandardConstructor", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that removes ConstructorDeclarations with an empty body. + * @return the graph transformation + */ + private static GraphTransformation removeEmptyConstructor() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, CONTAINING_RECORD, + related1ToN(RECORD_DECLARATION__CONSTRUCTORS, ConstructorDeclaration.class, CONSTRUCTOR_DECLARATION, setRepresentingNode(), + stopRecursion(), + related(METHOD_DECLARATION__BODY, Block.class, METHOD_BLOCK, property(nElements(BLOCK__STATEMENTS, 1)), + related(nthElement(BLOCK__STATEMENTS, 0), ReturnStatement.class, RETURN_STATEMENT, + property(attributeEquals(NODE__LOCATION, null)))))); + + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, CONTAINING_RECORD); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeEmptyConstructor", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that removes ConstructorDeclarations where the body consists only of a + * ThrowStatement. + * @return the graph transformation + */ + private static GraphTransformation removeUnsupportedConstructor() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, CONTAINING_RECORD, + related1ToN(RECORD_DECLARATION__CONSTRUCTORS, ConstructorDeclaration.class, CONSTRUCTOR_DECLARATION, setRepresentingNode(), + stopRecursion(), + related(METHOD_DECLARATION__BODY, Block.class, METHOD_BLOCK, property(nElements(BLOCK__STATEMENTS, 2)), + related(nthElement(BLOCK__STATEMENTS, 0), UnaryOperator.class, THROW_EXCEPTION, + property(attributeEquals(UNARY_OPERATOR__OPERATOR_CODE, "throw"))), + related(nthElement(BLOCK__STATEMENTS, 1), ReturnStatement.class, RETURN_STATEMENT, + property(attributeEquals(NODE__LOCATION, null)))))); + + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, CONTAINING_RECORD); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeUnsupportedConstructor", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that removes MethodDeclarations where the body consists only of a + * ThrowStatement. + * @return the graph transformation + */ + private static GraphTransformation removeUnsupportedMethod() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, CONTAINING_RECORD, + related1ToN(RECORD_DECLARATION__METHODS, MethodDeclaration.class, METHOD_DECLARATION, setRepresentingNode(), stopRecursion(), + related(METHOD_DECLARATION__BODY, Block.class, METHOD_BLOCK, property(nElements(BLOCK__STATEMENTS, 2)), + related(nthElement(BLOCK__STATEMENTS, 0), UnaryOperator.class, THROW_EXCEPTION, + property(attributeEquals(UNARY_OPERATOR__OPERATOR_CODE, "throw"))), + related(nthElement(BLOCK__STATEMENTS, 1), ReturnStatement.class, RETURN_STATEMENT, + property(attributeEquals(NODE__LOCATION, null)))))); + + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, CONTAINING_RECORD); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeUnsupportedMethod", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that removes MethodDeclarations with an empty body. + * @return the graph transformation + */ + private static GraphTransformation removeEmptyRecord() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(NamespaceDeclaration.class, CONTAINING_FILE, + related1ToN(NAMESPACE_DECLARATION__DECLARATIONS, RecordDeclaration.class, EMPTY_RECORD, setRepresentingNode(), + property(nElements(RECORD_DECLARATION__FIELDS, 0)), property(nElements(RECORD_DECLARATION__METHODS, 0)))); + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + // remove RecordDeclaration from NamespaceDeclaration + return create(NamespaceDeclaration.class, CONTAINING_FILE); + } + }.build(); + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeEmptyRecord", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that wraps the "then" statement of an {@link IfStatement} in a {@link Block} if + * is not a {@link Block}. + * @return the graph transformation + */ + private static GraphTransformation wrapThenStatement() { + return wrapInBlock(IfStatement.class, IF_STATEMENT, IF_STATEMENT__THEN_STATEMENT, "wrapThenStatement"); + } + + /** + * Creates a {@link GraphTransformation} that wraps the "else" statement of an {@link IfStatement} in a {@link Block} if + * is not a {@link Block}. + * @return the graph transformation + */ + private static GraphTransformation wrapElseStatement() { + return wrapInBlock(IfStatement.class, IF_STATEMENT, IF_STATEMENT__ELSE_STATEMENT, "wrapElseStatement"); + } + + /** + * Creates a {@link GraphTransformation} that wraps the "do" statement of a {@link ForStatement} in a {@link Block} if + * is not a {@link Block}. + * @return the graph transformation + */ + private static GraphTransformation wrapForStatement() { + return wrapInBlock(ForStatement.class, FOR_STATEMENT, FOR_STATEMENT__STATEMENT, "wrapForStatement"); + } + + /** + * Creates a {@link GraphTransformation} that wraps the "do" statement of a {@link WhileStatement} in a {@link Block} if + * is not a {@link Block}. + * @return the graph transformation + */ + private static GraphTransformation wrapWhileStatement() { + return wrapInBlock(WhileStatement.class, WHILE_STATEMENT, WHILE_STATEMENT__STATEMENT, "wrapWhileStatement"); + } + + /** + * Creates a {@link GraphTransformation} that wraps the "do" statement of a {@link WhileStatement} in a {@link Block} if + * is not a {@link Block}. + * @return the graph transformation + */ + private static GraphTransformation wrapDoStatement() { + return wrapInBlock(DoStatement.class, DO_STATEMENT, DO_STATEMENT__STATEMENT, "wrapDoWhileStatement"); + } + + private static GraphTransformation wrapInBlock(Class tClass, Role role, final CpgEdge blockEdge, String name) { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(tClass, role, property(notInstanceOf(blockEdge, Block.class)), related(blockEdge, Statement.class, THEN_STATEMENT)); + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(tClass, role, related(blockEdge, Block.class, Role.WRAPPING_BLOCK, + related(nthElement(BLOCK__STATEMENTS, 0), Statement.class, THEN_STATEMENT))); + } + }.build(); + return GraphTransformation.Builder.from(sourcePattern, targetPattern, name, OBLIGATORY).setExecutionOrder(ASCENDING_LOCATION).build(); + } + + /** + * Creates a {@link GraphTransformation} that removes all {@link MethodDeclaration}s of a {@link RecordDeclaration} that + * contain only a {@link ReturnStatement} with a constant or field reference as the return value. + * @return the graph transformation + */ + private static GraphTransformation removeGetterMethod() { + SimpleGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, CLASS_DECLARATION, + related1ToN(RECORD_DECLARATION__METHODS, MethodDeclaration.class, METHOD_DECLARATION, setRepresentingNode(), stopRecursion(), + related(VALUE_DECLARATION__TYPE, FunctionType.class, METHOD_TYPE, property(nElements(FUNCTION_TYPE__RETURN_TYPES, 1)), + // ObjectType in contrast to void, which is an IncompleteType + related(nthElement(FUNCTION_TYPE__RETURN_TYPES, 0), ObjectType.class, RETURN_TYPE)), + related(METHOD_DECLARATION__BODY, Block.class, METHOD_BLOCK, property(nElements(BLOCK__STATEMENTS, 1)), + related(nthElement(BLOCK__STATEMENTS, 0), ReturnStatement.class, RETURN_STATEMENT, + property(nElements(RETURN_STATEMENT__RETURN_VALUES, 1)), + related(nthElement(RETURN_STATEMENT__RETURN_VALUES, 0), Expression.class, RETURN_VALUE, + property(or(isConstant(), isFieldReference()))))))); + } + }.build(); + + SimpleGraphPattern targetPattern = new GraphPatternBuilder() { + + @Override + public SimpleGraphPattern build() { + return create(RecordDeclaration.class, CLASS_DECLARATION + // remove MethodDeclaration + ); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeGetterMethod", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that replaces calls to Optional.of(x) with their argument x. + * @return the graph transformation + */ + private static GraphTransformation removeOptionalOfCall() { + MultiGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot(wildcardParent(MemberCallExpression.class, MEMBER_CALL, + related1ToN(CALL_EXPRESSION__INVOKES, MethodDeclaration.class, METHOD_DECLARATION, + property(attributeToStringEquals(NODE__NAME, "Optional.of"))), + property(nElements(CALL_EXPRESSION__ARGUMENTS, 1)), + related(nthElement(CALL_EXPRESSION__ARGUMENTS, 0), Expression.class, ARGUMENT))); + } + }.build(); + + MultiGraphPattern targetPattern = new GraphPatternBuilder() { + + @Override + public MultiGraphPattern build() { + return multiRoot( + // replace Optional.of(x) by x + wildcardParent(Expression.class, ARGUMENT)); + + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeOptionalOfCall", CPG_TRANSFORM).build(); + } + + /** + * Creates a {@link GraphTransformation} that replaces calls to Optional.get() with the variable on which the method was + * called. + * @return the graph transformation + */ + private static GraphTransformation removeOptionalGetCall() { + MultiGraphPattern sourcePattern = new GraphPatternBuilder() { + @Override + public MultiGraphPattern build() { + return multiRoot(wildcardParent(MemberCallExpression.class, MEMBER_CALL, setRepresentingNode(), stopRecursion(), + related(CALL_EXPRESSION__CALLEE, MemberExpression.class, GETTER_METHOD_REFERENCE, + related(MEMBER_EXPRESSION__BASE, Expression.class, OPTIONAL_OBJECT)), + related1ToN(CALL_EXPRESSION__INVOKES, MethodDeclaration.class, METHOD_DECLARATION, + property(attributeToStringEquals(METHOD_DECLARATION__LOCAL_NAME, "get")), + related(METHOD_DECLARATION__RECORD_DECLARATION, RecordDeclaration.class, OPTIONAL_CLASS, + property(attributeToStringStartsWith(RECORD_DECLARATION__NAME, "java.util.Optional")))), + property(nElements(CALL_EXPRESSION__ARGUMENTS, 0)))); + } + }.build(); + + MultiGraphPattern targetPattern = new GraphPatternBuilder() { + + @Override + public MultiGraphPattern build() { + return multiRoot( + // replace Optional.get() by the proper value + wildcardParent(Expression.class, OPTIONAL_OBJECT)); + } + }.build(); + + return GraphTransformation.Builder.from(sourcePattern, targetPattern, "removeOptionalGetCall", CPG_TRANSFORM).build(); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/CpgIsomorphismDetector.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/CpgIsomorphismDetector.java new file mode 100644 index 0000000000..67b91b8d49 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/CpgIsomorphismDetector.java @@ -0,0 +1,206 @@ +package de.jplag.java_cpg.transformation.matching; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker; +import de.jplag.java_cpg.transformation.matching.pattern.GraphPattern; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; + +/** + * This class finds {@link Match}es of {@link GraphPattern}s in concrete graphs. + */ +public class CpgIsomorphismDetector { + + private static final Logger logger = LoggerFactory.getLogger(CpgIsomorphismDetector.class); + private final ClassComparator classComparator; + private TreeMap, List> nodeMap; + + /** + * Creates a new {@link CpgIsomorphismDetector}. + */ + public CpgIsomorphismDetector() { + classComparator = new ClassComparator(); + } + + /** + * Sets the current graph to find pattern matches in. + * @param root The root {@link Node} of the graph. + */ + public void loadGraph(Node root) { + nodeMap = new TreeMap<>(classComparator); + + List allNodes = SubgraphWalker.INSTANCE.flattenAST(root); + allNodes.forEach(this::registerNode); + } + + /** + * Gets an {@link Iterator} over all {@link Match}es of the given {@link GraphPattern} in the currently loaded Graph. + * @param sourcePattern the {@link GraphPattern} to search matches for + * @return an {@link Iterator} over all found {@link Match}es + */ + public List getMatches(GraphPattern sourcePattern) { + Map, List> rootCandidateMap = sourcePattern.getRoots().stream().collect(Collectors.toMap(root -> root, + root -> root.getCandidateClasses().stream().map(this::getNodesOfType).flatMap(List::stream).distinct().toList())); + List matches = sourcePattern.match(rootCandidateMap); + logger.info("Got {} matches looking at {} nodes", matches.size(), NodePattern.NodePatternImpl.getCounter()); + NodePattern.NodePatternImpl.resetCounter(); + return matches; + } + + /** + * Registers a concrete {@link Node} of a CPG so that it can be considered as a root candidate of a + * {@link GraphPattern}. + * @param node the node + */ + private void registerNode(Node node) { + nodeMap.computeIfAbsent(node.getClass(), c -> new ArrayList<>()).add(node); + } + + /** + * Gets a {@link List} of all registered {@link Node}s that are assignable to the given type. + * @param superClass the type class to search + * @param the type + * @return a list of assignable nodes + */ + public List getNodesOfType(Class superClass) { + classComparator.setMode(ClassComparator.Mode.FIRST_COMPATIBLE); + Class firstCompatible = nodeMap.ceilingKey(superClass); + classComparator.setMode(ClassComparator.Mode.FIRST_INCOMPATIBLE); + Class firstIncompatible = nodeMap.ceilingKey(superClass); + classComparator.setMode(ClassComparator.Mode.FIRST_COMPATIBLE); + + if (Objects.isNull(firstCompatible)) { + // no nodes of this type + return List.of(); + } + + SortedMap, List> subMap = nodeMap.tailMap(firstCompatible); + if (firstIncompatible != null) { + subMap = subMap.headMap(firstIncompatible); + } + return subMap.values().stream().flatMap(List::stream).map(superClass::cast).toList(); + } + + /** + * Verifies that the given match of the source {@link GraphPattern} is still valid. After a transformation involving the + * match's {@link Node}s, a match may be invalidated. + * @param match the match + * @param sourcePattern the source pattern + * @return true iff the match is still valid + */ + public boolean validateMatch(Match match, GraphPattern sourcePattern) { + return sourcePattern.validate(match); + } + + /** + * This comparator imposes a total order on classes by using their class hierarchy and name, where: + *

    + *
  • different subclasses of a common superclass are ordered alphanumerically
  • . + *
  • the sublist of subclasses of a superclass comes directly after the superclass
  • . + *
+ */ + private static final class ClassComparator implements Comparator> { + + private Mode mode = Mode.FIRST_COMPATIBLE; + + public void setMode(Mode mode) { + this.mode = mode; + } + + @Override + public int compare(Class o1, Class o2) { + // classes are compatible with each other? -> Order of hierarchy + if (Objects.equals(o1, o2)) + return this.mode.equalityValue; + else if (o1.isAssignableFrom(o2)) + return this.mode.subClassValue; + else if (o2.isAssignableFrom(o1)) + return this.mode.superClassValue; + + // classes are incompatible; find most special incompatible superclass + Class super1 = o1.getSuperclass(); + Class super2 = o2.getSuperclass(); + + Class comp1; + Class comp2; + Mode previousMode = this.mode; + setMode(Mode.FIRST_COMPATIBLE); + int res = (int) Math.signum(compare(super1, super2)); + setMode(previousMode); + if (res == -1 && super1.isAssignableFrom(super2)) { + // super1 is common supertype + comp1 = o1; + while (!super2.getSuperclass().equals(super1)) { + super2 = super2.getSuperclass(); + } + // o1 and super2 are incompatible subclasses of the same class + comp2 = super2; + } else if (res == 0) { + // o1 and o2 are incompatible subclasses of the same class + comp1 = o1; + comp2 = o2; + } else if (res == 1 && super2.isAssignableFrom(super1)) { + // super2 is common supertype + comp2 = o2; + while (!super1.getSuperclass().equals(super2)) { + super1 = super1.getSuperclass(); + } + // o2 and super1 are incompatible subclasses of the same class + comp1 = super1; + } else { + // super types are incompatible, just use their comparison value + return res; + } + // found most special incompatible supertypes; use alphabetical order on name + return Comparator., String>comparing(Class::getName).compare(comp1, comp2); + + } + + enum Mode { + /** + * In this mode, a binary search of a class A in a list returns the position of the first class B in the list so that B + * is a subclass or equal to A. + */ + FIRST_COMPATIBLE(0, -1, 1), + + /** + * In this mode, a binary search of a class A in a list returns the position after the last class B in the list + * so that B is a subclass or equal to A. + */ + FIRST_INCOMPATIBLE(1, 1, 1); + + /** + * The value to return for classes (A, B) if B is a true superclass to A. + */ + public final int superClassValue; + /** + * The value to return for classes (A, A). + */ + private final int equalityValue; + /** + * The value to return for classes (A, B) if B is a true subclass to A. + */ + private final int subClassValue; + + Mode(int equalityValue, int subClassValue, int superClassValue) { + this.equalityValue = equalityValue; + this.subClassValue = subClassValue; + this.superClassValue = superClassValue; + } + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/PatternRepository.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/PatternRepository.java new file mode 100644 index 0000000000..9ecfd58f69 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/PatternRepository.java @@ -0,0 +1,125 @@ +package de.jplag.java_cpg.transformation.matching; + +import static de.jplag.java_cpg.transformation.Role.ASSIGN_EXPRESSION; +import static de.jplag.java_cpg.transformation.Role.CLASS_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.CONDITION; +import static de.jplag.java_cpg.transformation.Role.ELSE_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.FIELD_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.FIELD_REFERENCE; +import static de.jplag.java_cpg.transformation.Role.FIELD_TYPE; +import static de.jplag.java_cpg.transformation.Role.IF_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.INNER_CONDITION; +import static de.jplag.java_cpg.transformation.Role.METHOD_BODY; +import static de.jplag.java_cpg.transformation.Role.METHOD_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.METHOD_TYPE; +import static de.jplag.java_cpg.transformation.Role.PARAMETER_DECLARATION; +import static de.jplag.java_cpg.transformation.Role.PARAMETER_REFERENCE; +import static de.jplag.java_cpg.transformation.Role.RETURN_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.THEN_STATEMENT; +import static de.jplag.java_cpg.transformation.Role.VOID_TYPE; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.ASSIGN_EXPRESSION__LHS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.ASSIGN_EXPRESSION__RHS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.BLOCK__STATEMENTS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.FUNCTION_TYPE__PARAMETERS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.FUNCTION_TYPE__RETURN_TYPES; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.IF_STATEMENT__CONDITION; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.IF_STATEMENT__ELSE_STATEMENT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.IF_STATEMENT__THEN_STATEMENT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.METHOD_DECLARATION__BODY; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.METHOD_DECLARATION__PARAMETERS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.METHOD_DECLARATION__RECORD_DECLARATION; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.RECORD_DECLARATION__FIELDS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.RECORD_DECLARATION__METHODS; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.REFERENCE__REFERS_TO; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.RETURN_STATEMENT__RETURN_VALUES; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.TYPE__TYPE_NAME; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.UNARY_OPERATOR__INPUT; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.UNARY_OPERATOR__OPERATOR_CODE; +import static de.jplag.java_cpg.transformation.matching.edges.Edges.VALUE_DECLARATION__TYPE; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.nElements; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.notEmpty; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.nthElement; + +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement; +import de.fraunhofer.aisec.cpg.graph.statements.Statement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator; +import de.fraunhofer.aisec.cpg.graph.types.FunctionType; +import de.fraunhofer.aisec.cpg.graph.types.IncompleteType; +import de.fraunhofer.aisec.cpg.graph.types.ObjectType; +import de.jplag.java_cpg.transformation.matching.pattern.GraphPattern; +import de.jplag.java_cpg.transformation.matching.pattern.GraphPatternBuilder; +import de.jplag.java_cpg.transformation.matching.pattern.PatternUtil; + +/** + * This class is used to collect sub-patterns that may appear repetitively or used in tests. + */ +public final class PatternRepository { + + private PatternRepository() { + /* should not be instantiated */ + } + + /** + * Creates a {@link GraphPatternBuilder} for an {@link IfStatement} with an else statement. + * @return the graph pattern builder + */ + public static GraphPatternBuilder ifElseWithNegatedCondition() { + + return new GraphPatternBuilder() { + @Override + public GraphPattern build() { + return create(IfStatement.class, IF_STATEMENT, + related(IF_STATEMENT__CONDITION, UnaryOperator.class, CONDITION, + property(PatternUtil.attributeEquals(UNARY_OPERATOR__OPERATOR_CODE, "!")), + related(UNARY_OPERATOR__INPUT, Expression.class, INNER_CONDITION)), + related(IF_STATEMENT__THEN_STATEMENT, Statement.class, THEN_STATEMENT), + related(IF_STATEMENT__ELSE_STATEMENT, Statement.class, ELSE_STATEMENT)); + } + }; + } + + /** + * Creates a {@link GraphPatternBuilder} for a setter method. + * @return the {@link GraphPatternBuilder} + */ + public static GraphPatternBuilder setterMethod() { + return new GraphPatternBuilder() { + + @Override + public GraphPattern build() { + return create(MethodDeclaration.class, METHOD_DECLARATION, + related(METHOD_DECLARATION__RECORD_DECLARATION, RecordDeclaration.class, CLASS_DECLARATION, + related1ToN(RECORD_DECLARATION__FIELDS, FieldDeclaration.class, FIELD_DECLARATION, + related(VALUE_DECLARATION__TYPE, ObjectType.class, FIELD_TYPE)), + relatedExisting1ToN(RECORD_DECLARATION__METHODS, MethodDeclaration.class, METHOD_DECLARATION)), + related(VALUE_DECLARATION__TYPE, FunctionType.class, METHOD_TYPE, property(nElements(FUNCTION_TYPE__PARAMETERS, 1)), + relatedExisting(nthElement(FUNCTION_TYPE__PARAMETERS, 0), ObjectType.class, FIELD_TYPE), + property(nElements(FUNCTION_TYPE__RETURN_TYPES, 1)), + related(nthElement(FUNCTION_TYPE__RETURN_TYPES, 0), IncompleteType.class, VOID_TYPE, + property(PatternUtil.attributeEquals(TYPE__TYPE_NAME, "void")))), + property(notEmpty(METHOD_DECLARATION__PARAMETERS)), + related(nthElement(METHOD_DECLARATION__PARAMETERS, 0), ParameterDeclaration.class, PARAMETER_DECLARATION), + property(MethodDeclaration::hasBody), + related(METHOD_DECLARATION__BODY, Block.class, METHOD_BODY, property(nElements(BLOCK__STATEMENTS, 2)), + related(nthElement(BLOCK__STATEMENTS, 0), AssignExpression.class, ASSIGN_EXPRESSION, + related(ASSIGN_EXPRESSION__LHS, Reference.class, FIELD_REFERENCE), + + related(ASSIGN_EXPRESSION__RHS, Reference.class, PARAMETER_REFERENCE, + relatedExisting(REFERENCE__REFERS_TO, ParameterDeclaration.class, PARAMETER_DECLARATION))), + related(nthElement(BLOCK__STATEMENTS, 1), ReturnStatement.class, RETURN_STATEMENT, + property(nElements(RETURN_STATEMENT__RETURN_VALUES, 0))))); + } + }; + + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/AEdge.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/AEdge.java new file mode 100644 index 0000000000..f8f811cf87 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/AEdge.java @@ -0,0 +1,82 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import static de.jplag.java_cpg.transformation.matching.edges.IEdge.EdgeCategory.ANALYTIC; +import static de.jplag.java_cpg.transformation.matching.edges.IEdge.EdgeCategory.AST; +import static de.jplag.java_cpg.transformation.matching.edges.IEdge.EdgeCategory.REFERENCE; + +import de.fraunhofer.aisec.cpg.graph.Node; + +/** + * This abstract class contains the method implementations common to all {@link IEdge}s. + * @param the source node type + * @param the related node type + */ +public abstract class AEdge implements IEdge { + /** + * The {@link EdgeCategory} of the edge. + */ + protected final EdgeCategory category; + private Class sourceClass; + private Class relatedClass; + + /** + * Creates a new AEdge of the given category. + * @param category the category + */ + protected AEdge(EdgeCategory category) { + this.category = category; + } + + /** + * Gets the {@link EdgeCategory} of this edge. + * @return the edge category + */ + public EdgeCategory getCategory() { + return category; + } + + /** + * Gets the source node class of this edge. + * @return the source node class + */ + public Class getSourceClass() { + return this.sourceClass; + } + + /** + * Gets the related node class of this edge. + * @return the related node class + */ + public Class getRelatedClass() { + return relatedClass; + } + + /** + * @return true iff this edge is {@link EdgeCategory#ANALYTIC}. + */ + public boolean isAnalytic() { + return category == ANALYTIC; + } + + /** + * @return true iff this edge is {@link EdgeCategory#AST}. + */ + public boolean isAst() { + return category == AST; + } + + /** + * @return true iff this edge is {@link EdgeCategory#REFERENCE}. + */ + public boolean isReference() { + return category == REFERENCE; + } + + public void setSourceClass(Class sourceClass) { + this.sourceClass = sourceClass; + } + + public void setRelatedClass(Class relatedClass) { + this.relatedClass = relatedClass; + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/AnyOfNEdge.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/AnyOfNEdge.java new file mode 100644 index 0000000000..b48b7068da --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/AnyOfNEdge.java @@ -0,0 +1,60 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import de.fraunhofer.aisec.cpg.graph.Node; + +/** + * A {@link AnyOfNEdge} serves as a placeholder for a {@link CpgNthEdge} during transformation calculation as long as + * the index is not known. + * @param the source node type + * @param the target node type + */ +public class AnyOfNEdge extends CpgNthEdge { + + private final CpgMultiEdge cpgMultiEdge; + private final int minIndex; + + /** + * Creates a new {@link AnyOfNEdge} for the corresponding {@link CpgMultiEdge}. + * @param cpgMultiEdge the multi edge + * @param minIndex the minimal index this edge can take + */ + public AnyOfNEdge(CpgMultiEdge cpgMultiEdge, int minIndex) { + super(cpgMultiEdge, -1); + this.cpgMultiEdge = cpgMultiEdge; + this.minIndex = minIndex; + } + + public int getMinimalIndex() { + return minIndex; + } + + /** + * Gets the corresponding {@link CpgMultiEdge}. + * @return the multi edge + */ + @Override + public CpgMultiEdge getMultiEdge() { + return cpgMultiEdge; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + + AnyOfNEdge that = (AnyOfNEdge) o; + + return cpgMultiEdge.equals(that.cpgMultiEdge); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + cpgMultiEdge.hashCode(); + return result; + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgAttributeEdge.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgAttributeEdge.java new file mode 100644 index 0000000000..0cc8d7e87f --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgAttributeEdge.java @@ -0,0 +1,27 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +import de.fraunhofer.aisec.cpg.graph.Node; + +/** + * This represents the relation to a property, i.e. an object related to a node other than another node, e.g. a String + * name. To avoid confusion with a {@link de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge}, which is an edge that has + * properties itself, this is called {@link CpgAttributeEdge}. + * @param getter function to get the property + * @param setter function to set the property + * @param the node type of the source node + * @param

the type of the property + */ +public record CpgAttributeEdge(Function getter, BiConsumer setter) { + + /** + * Gets the property related to the given node. + * @param t the source node + * @return the property + */ + public P get(T t) { + return getter.apply(t); + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgEdge.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgEdge.java new file mode 100644 index 0000000000..ea55d7b37a --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgEdge.java @@ -0,0 +1,90 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import static de.jplag.java_cpg.transformation.matching.edges.IEdge.EdgeCategory.AST; + +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import de.fraunhofer.aisec.cpg.graph.Node; + +/** + * This is a wrapper for a graph edge (with a 1:1 relation). + * @param The type of the source node + * @param The type of the related node + */ +public class CpgEdge extends AEdge { + private final Function getter; + private final BiConsumer setter; + + /** + * Creates a new {@link CpgEdge} with a getter and setter for the related node. + * @param getter the getter + * @param setter the setter + * @param category the edge category + */ + public CpgEdge(Function getter, BiConsumer setter, EdgeCategory category) { + super(category); + this.getter = getter; + this.setter = setter; + } + + /** + * Creates a new {@link CpgEdge} with a getter and setter for the related node. + * @param getter the getter + * @param setter the setter + */ + public CpgEdge(Function getter, BiConsumer setter) { + this(getter, setter, AST); + } + + /** + * Creates a new list-valued {@link CpgEdge} with a getter and setter for the related node list. + * @param getter the getter + * @param setter the setter + * @param The type of the source node + * @param The type of the related node + * @return the new {@link CpgEdge} + */ + public static CpgEdge listValued(Function> getter, BiConsumer> setter) { + // used only for assignment left-hand sides and right-hand sides, where in Java only one value is allowed + return new CpgEdge<>(node -> getter.apply(node).getFirst(), (node, value) -> setter.accept(node, List.of(value))); + } + + /** + * Gets the related node of this edge starting from the given source node. + * @param from the source node + * @return the related node + */ + public R getRelated(T from) { + return getter.apply(from); + } + + /** + * Gets the getter function of this {@link CpgEdge}, used to get the relateds from a given source. + * @return the getter + */ + public Function getter() { + return getter; + } + + /** + * Gets the setter function of this {@link CpgEdge}, used to set the relateds for a given source. + * @return the setter + */ + public BiConsumer setter() { + return setter; + } + + @Override + public String toString() { + return "CpgEdge[" + "getter=" + getter + ", " + "setter=" + setter + ']'; + } + + @Override + public boolean isEquivalentTo(IEdge other) { + return Objects.equals(this, other); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgMultiEdge.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgMultiEdge.java new file mode 100644 index 0000000000..93dce6e31f --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgMultiEdge.java @@ -0,0 +1,178 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import static de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge.ValueType.NODE_VALUED; +import static de.jplag.java_cpg.transformation.matching.edges.IEdge.EdgeCategory.AST; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; +import de.fraunhofer.aisec.cpg.helpers.TriConsumer; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; + +/** + * This is a wrapper for a graph edge (1:n relation). + * @param The type of the target node + * @param The type of the related node + */ +public final class CpgMultiEdge extends AEdge { + private final Function> getter; + private final Function>> getEdges; + private final TriConsumer setter; + private final Map, AnyOfNEdge> any1ofNEdges; + private final ValueType valueType; + private final HashMap, Integer> sequenceNodes; + + /** + * Creates a new CpgMultiEdge. + * @param getter a function to get all the target nodes + * @param setter a function to set the nth target node + * @param valueType describes the type of representation of this edge in the CPG. + * @param getEdges if edgeValued, then this should be a function to get all the target edges, null otherwise. + * @param category category of the edge + */ + public CpgMultiEdge(Function> getter, TriConsumer setter, ValueType valueType, + Function>> getEdges, EdgeCategory category) { + super(category); + this.getter = getter; + this.setter = setter; + this.valueType = valueType; + this.getEdges = getEdges; + + this.any1ofNEdges = new HashMap<>(); + this.sequenceNodes = new HashMap<>(); + } + + /** + * A shorthand to create an edge-valued {@link CpgMultiEdge}. + * @param getter a function to get all the edges + * @param The type of the target node + * @param The type of the related node + * @return the new {@link CpgMultiEdge} + */ + public static CpgMultiEdge edgeValued(Function>> getter) { + return edgeValued(getter, AST); + } + + /** + * A shorthand to create an edge-valued {@link CpgMultiEdge}. + * @param getter a function to get all the edges + * @param category the category of the edge + * @param The type of the target node + * @param The type of the related node + * @return the new {@link CpgMultiEdge} + */ + public static CpgMultiEdge edgeValued(Function>> getter, EdgeCategory category) { + Function> getNodes = (T node) -> getter.apply(node).stream().map(PropertyEdge::getEnd).toList(); + TriConsumer setOne = (T node, Integer n, R value) -> getter.apply(node).get(n).setEnd(value); + return new CpgMultiEdge<>(getNodes, setOne, ValueType.EDGE_VALUED, getter, category); + } + + /** + * A shorthand to create a node-valued {@link CpgMultiEdge}. + * @param getter a function to get all the nodes + * @param category the edge category + * @param The type of the target node + * @param The type of the related node + * @return the new {@link CpgMultiEdge} + */ + public static CpgMultiEdge nodeValued(Function> getter, EdgeCategory category) { + TriConsumer setOne = (T node, Integer n, R value) -> getter.apply(node).set(n, value); + return new CpgMultiEdge<>(getter, setOne, NODE_VALUED, null, category); + } + + /** + * A shorthand to create a node-valued AST {@link CpgMultiEdge}. + * @param getter a function to get all the nodes + * @param The type of the target node + * @param The type of the related node + * @return the new {@link CpgMultiEdge} + */ + public static CpgMultiEdge nodeValued(Function> getter) { + return nodeValued(getter, AST); + } + + /** + * Gets all the targets of this edge, starting from the given concrete source node. + * @param t the source node + * @return the target nodes + */ + public List getAllTargets(T t) { + return getter.apply(t); + } + + /** + * Get the getter function of this multi edge. + * @return the getter + */ + public Function> getter() { + return getter; + } + + public void saveSequenceIndex(NodePattern pattern, int idx) { + this.sequenceNodes.put((NodePattern) pattern, idx); + } + + /** + * Gets the setter function of this multi edge. + * @return the setter + */ + public TriConsumer setter() { + return setter; + } + + /** + * Gets a {@link AnyOfNEdge} from this {@link CpgMultiEdge} directed at the given {@link NodePattern}. + * @param pattern the pattern + * @return the 'any of n' edge + */ + public AnyOfNEdge getAnyOfNEdgeTo(NodePattern pattern) { + int index = this.sequenceNodes.getOrDefault(pattern, 0); + return this.any1ofNEdges.computeIfAbsent(pattern, p -> new AnyOfNEdge<>(this, index)); + } + + /** + * If true, the getter and setter method handle a list of edges. Otherwise, they handle a list of nodes. + * @return true if this edge is edge-valued, false if this edge is node-valued. + */ + public boolean isEdgeValued() { + return this.valueType == ValueType.EDGE_VALUED; + } + + @Override + public boolean isEquivalentTo(IEdge other) { + // true if it is the same edge + return Objects.equals(this, other); + } + + /** + * Gets all the edges represented by this edge, starting from the given concrete source node. + * @param t the source node + * @return the target edges + */ + public List> getAllEdges(T t) { + return getEdges.apply(t); + } + + /** + * Describes the type of connection between nodes via an edge. + */ + public enum ValueType { + /** + * An edge where the targets can be accessed directly as nodes. + */ + NODE_VALUED, + /** + * An edge where the target can be accessed via {@link PropertyEdge}s. + */ + EDGE_VALUED, + /** + * An edge where the target is a List of nodes. + */ + LIST_VALUED + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgNthEdge.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgNthEdge.java new file mode 100644 index 0000000000..fbccdff70e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/CpgNthEdge.java @@ -0,0 +1,70 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; + +/** + * A {@link CpgNthEdge} represents an individual {@link PropertyEdge} out of a {@link CpgMultiEdge}. + * @param source node type + * @param target node type + */ +public class CpgNthEdge extends CpgEdge { + private final CpgMultiEdge multiEdge; + private final int index; + + /** + * Creates a new {@link CpgNthEdge}. + * @param edge The {@link CpgMultiEdge} that represents multiple edges + * @param index The index of this edge + */ + public CpgNthEdge(CpgMultiEdge edge, int index) { + super(t -> edge.getAllTargets(t).get(index), (t, r) -> edge.setter().accept(t, index, r), edge.getCategory()); + this.multiEdge = edge; + this.index = index; + this.setSourceClass(edge.getSourceClass()); + this.setRelatedClass(edge.getRelatedClass()); + } + + @Override + public boolean isEquivalentTo(IEdge other) { + if (!(other instanceof CpgNthEdge otherNthEdge)) { + return false; + } + return multiEdge.isEquivalentTo(otherNthEdge.multiEdge) && index == otherNthEdge.getIndex(); + } + + /** + * Returns the multi edge object of which this edge is one. + * @return the multi edge + */ + public CpgMultiEdge getMultiEdge() { + return multiEdge; + } + + /** + * Returns the index n of this nth edge. + * @return the index + */ + public int getIndex() { + return index; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + CpgNthEdge that = (CpgNthEdge) o; + + if (getIndex() != that.getIndex()) + return false; + return getMultiEdge().equals(that.getMultiEdge()); + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/EdgeUtil.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/EdgeUtil.java new file mode 100644 index 0000000000..de09d9fd22 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/EdgeUtil.java @@ -0,0 +1,41 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.scopes.Scope; + +/** + * This class provides auxiliary methods to build edges. + */ +public final class EdgeUtil { + + private EdgeUtil() { + } + + /** + * Gets the {@link RecordDeclaration} that a {@link Node} is located in. + * @param node a node + * @return the record declaration + */ + public static RecordDeclaration getRecord(Node node) { + + Scope scope = node.getScope(); + while (!Objects.isNull(scope)) { + if (scope.getAstNode() instanceof RecordDeclaration classDecl) { + return classDecl; + } + scope = scope.getParent(); + } + return null; + } + + @NotNull + static String getLocalName(Node n) { + return n.getName().getLocalName(); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/Edges.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/Edges.java new file mode 100644 index 0000000000..1c938c5b73 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/Edges.java @@ -0,0 +1,239 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import static de.jplag.java_cpg.transformation.matching.edges.IEdge.EdgeCategory.ANALYTIC; +import static de.jplag.java_cpg.transformation.matching.edges.IEdge.EdgeCategory.REFERENCE; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import de.fraunhofer.aisec.cpg.graph.Component; +import de.fraunhofer.aisec.cpg.graph.Name; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement; +import de.fraunhofer.aisec.cpg.graph.statements.DoStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement; +import de.fraunhofer.aisec.cpg.graph.statements.Statement; +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.SubscriptExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator; +import de.fraunhofer.aisec.cpg.graph.types.FunctionType; +import de.fraunhofer.aisec.cpg.graph.types.IncompleteType; +import de.fraunhofer.aisec.cpg.graph.types.ObjectType; +import de.fraunhofer.aisec.cpg.graph.types.Type; +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation; +import de.jplag.java_cpg.transformation.GraphTransformation.Builder; +import de.jplag.java_cpg.transformation.matching.pattern.WildcardGraphPattern; + +/** + * A constant class containing relevant {@link IEdge} objects. + */ +public class Edges { + public static final CpgEdge ASSIGN_EXPRESSION__LHS = CpgEdge.listValued(AssignExpression::getLhs, + AssignExpression::setLhs); + public static final CpgEdge ASSIGN_EXPRESSION__RHS = CpgEdge.listValued(AssignExpression::getRhs, + AssignExpression::setRhs); + public static final CpgEdge BINARY_OPERATOR__LHS = new CpgEdge<>(BinaryOperator::getLhs, BinaryOperator::setLhs); + public static final CpgAttributeEdge BINARY_OPERATOR__OPERATOR_CODE = new CpgAttributeEdge<>( + BinaryOperator::getOperatorCode, BinaryOperator::setOperatorCode); + public static final CpgEdge BINARY_OPERATOR__RHS = new CpgEdge<>(BinaryOperator::getRhs, BinaryOperator::setRhs); + public static final CpgMultiEdge BLOCK__DECLARATIONS = CpgMultiEdge.nodeValued(Block::getDeclarations, REFERENCE); + public static final CpgMultiEdge BLOCK__STATEMENTS = CpgMultiEdge.edgeValued(Block::getStatementEdges); + public static final CpgMultiEdge CALL_EXPRESSION__ARGUMENTS = CpgMultiEdge + .edgeValued(CallExpression::getArgumentEdges); + public static final CpgEdge CALL_EXPRESSION__CALLEE = new CpgEdge<>(CallExpression::getCallee, + CallExpression::setCallee); + public static final CpgMultiEdge CALL_EXPRESSION__INVOKES = CpgMultiEdge + .edgeValued(CallExpression::getInvokeEdges, REFERENCE); + public static final CpgMultiEdge COMPONENT__TRANSLATION_UNITS = CpgMultiEdge + .nodeValued(Component::getTranslationUnits); + public static final CpgMultiEdge DECLARATION_STATEMENT__DECLARATIONS = CpgMultiEdge + .edgeValued(DeclarationStatement::getDeclarationEdges); + public static final CpgAttributeEdge> FIELD_DECLARATION__MODIFIERS = new CpgAttributeEdge<>( + FieldDeclaration::getModifiers, FieldDeclaration::setModifiers); + public static final CpgEdge FOR_STATEMENT__CONDITION = new CpgEdge<>(ForStatement::getCondition, + ForStatement::setCondition); + public static final CpgEdge FOR_STATEMENT__INITIALIZER_STATEMENT = new CpgEdge<>(ForStatement::getInitializerStatement, + ForStatement::setInitializerStatement); + public static final CpgEdge FOR_STATEMENT__ITERATION_STATEMENT = new CpgEdge<>(ForStatement::getIterationStatement, + ForStatement::setIterationStatement); + public static final CpgEdge FOR_STATEMENT__STATEMENT = new CpgEdge<>(ForStatement::getStatement, + ForStatement::setStatement); + public static final CpgMultiEdge FUNCTION_DECLARATION__OVERRIDES = CpgMultiEdge + .nodeValued(FunctionDeclaration::getOverrides, REFERENCE); + public static final CpgMultiEdge FUNCTION_DECLARATION__OVERRIDDEN_BY = CpgMultiEdge + .nodeValued(FunctionDeclaration::getOverriddenBy, REFERENCE); + public static final CpgMultiEdge FUNCTION_TYPE__PARAMETERS = CpgMultiEdge.nodeValued(FunctionType::getParameters); + public static final CpgMultiEdge FUNCTION_TYPE__RETURN_TYPES = CpgMultiEdge.nodeValued(FunctionType::getReturnTypes); + public static final CpgEdge IF_STATEMENT__CONDITION = new CpgEdge<>(IfStatement::getCondition, + IfStatement::setCondition); + public static final CpgEdge IF_STATEMENT__ELSE_STATEMENT = new CpgEdge<>(IfStatement::getElseStatement, + IfStatement::setElseStatement); + public static final CpgEdge IF_STATEMENT__THEN_STATEMENT = new CpgEdge<>(IfStatement::getThenStatement, + IfStatement::setThenStatement); + public static final CpgEdge MEMBER_EXPRESSION__RECORD_DECLARATION = new CpgEdge<>(EdgeUtil::getRecord, null, + ANALYTIC); + public static final CpgEdge MEMBER_EXPRESSION__BASE = new CpgEdge<>(MemberExpression::getBase, + MemberExpression::setBase); + public static final CpgEdge METHOD_DECLARATION__BODY = new CpgEdge<>(MethodDeclaration::getBody, + MethodDeclaration::setBody); + public static final CpgMultiEdge METHOD_DECLARATION__PARAMETERS = CpgMultiEdge + .nodeValued(MethodDeclaration::getParameters); + public static final CpgEdge METHOD_DECLARATION__RECORD_DECLARATION = new CpgEdge<>( + MethodDeclaration::getRecordDeclaration, MethodDeclaration::setRecordDeclaration, REFERENCE); + public static final CpgMultiEdge NAMESPACE_DECLARATION__DECLARATIONS = CpgMultiEdge + .nodeValued(NamespaceDeclaration::getDeclarations); + public static final CpgAttributeEdge NODE__LOCATION = new CpgAttributeEdge<>(Node::getLocation, Node::setLocation); + public static final CpgAttributeEdge NODE__NAME = new CpgAttributeEdge<>(Node::getName, Node::setName); + public static final CpgAttributeEdge NODE__LOCAL_NAME = new CpgAttributeEdge<>(EdgeUtil::getLocalName, null); + public static final CpgAttributeEdge METHOD_DECLARATION__LOCAL_NAME = new CpgAttributeEdge<>(EdgeUtil::getLocalName, + null); + public static final CpgEdge OBJECT_TYPE__RECORD_DECLARATION = new CpgEdge<>(ObjectType::getRecordDeclaration, + ObjectType::setRecordDeclaration, REFERENCE); + public static final CpgMultiEdge RECORD_DECLARATION__FIELDS = CpgMultiEdge + .edgeValued(RecordDeclaration::getFieldEdges); + public static final CpgMultiEdge RECORD_DECLARATION__METHODS = CpgMultiEdge + .edgeValued(RecordDeclaration::getMethodEdges); + public static final CpgAttributeEdge RECORD_DECLARATION__NAME = new CpgAttributeEdge<>(Node::getName, Node::setName); + public static final CpgMultiEdge RECORD_DECLARATION__CONSTRUCTORS = CpgMultiEdge + .edgeValued(RecordDeclaration::getConstructorEdges); + public static final CpgEdge REFERENCE__REFERS_TO = new CpgEdge<>(Reference::getRefersTo, Reference::setRefersTo, + REFERENCE); + public static final CpgMultiEdge RETURN_STATEMENT__RETURN_VALUES = CpgMultiEdge + .nodeValued(ReturnStatement::getReturnValues); + public static final CpgMultiEdge STATEMENT__LOCALS = CpgMultiEdge.edgeValued(Statement::getLocalEdges, REFERENCE); + public static final CpgEdge SUBSCRIPT_EXPRESSION__SUBSCRIPT_EXPRESSION = new CpgEdge<>( + SubscriptExpression::getSubscriptExpression, SubscriptExpression::setSubscriptExpression); + public static final CpgEdge SUBSCRIPT_EXPRESSION__ARRAY_EXPRESSION = new CpgEdge<>( + SubscriptExpression::getArrayExpression, SubscriptExpression::setArrayExpression); + public static final CpgMultiEdge TRANSLATION_UNIT__DECLARATIONS = CpgMultiEdge + .edgeValued(TranslationUnitDeclaration::getDeclarationEdges); + public static final CpgAttributeEdge TYPE__TYPE_NAME = new CpgAttributeEdge<>(Type::getTypeName, null); + public static final CpgEdge UNARY_OPERATOR__INPUT = new CpgEdge<>(UnaryOperator::getInput, UnaryOperator::setInput); + public static final CpgAttributeEdge UNARY_OPERATOR__OPERATOR_CODE = new CpgAttributeEdge<>(UnaryOperator::getOperatorCode, + UnaryOperator::setOperatorCode); + public static final CpgMultiEdge VALUE_DECLARATION__USAGES = CpgMultiEdge.edgeValued(ValueDeclaration::getUsageEdges, + REFERENCE); + public static final CpgEdge VALUE_DECLARATION__TYPE = new CpgEdge<>(ValueDeclaration::getType, ValueDeclaration::setType); + public static final CpgEdge VARIABLE_DECLARATION__INITIALIZER = new CpgEdge<>( + VariableDeclaration::getInitializer, VariableDeclaration::setInitializer); + public static final CpgEdge WHILE_STATEMENT__STATEMENT = new CpgEdge<>(WhileStatement::getStatement, + WhileStatement::setStatement); + public static final CpgEdge WHILE_STATEMENT__CONDITION = new CpgEdge<>(WhileStatement::getCondition, + WhileStatement::setCondition); + public static final CpgEdge DO_STATEMENT__STATEMENT = new CpgEdge<>(DoStatement::getStatement, DoStatement::setStatement); + public static final CpgEdge DO_STATEMENT__CONDITION = new CpgEdge<>(DoStatement::getCondition, + DoStatement::setCondition); + /** + * A {@link Map} to retrieve all {@link IEdge}s with a specific source type. + */ + private static final Map, List>> edgesBySourceType; + /** + * A {@link Map} to retrieve all {@link IEdge}s with a specific target type. + */ + private static final Map, List>> edgesByTargetType; + + static { + edgesBySourceType = new HashMap<>(); + edgesByTargetType = new HashMap<>(); + + register(ASSIGN_EXPRESSION__LHS, AssignExpression.class, Expression.class); + register(ASSIGN_EXPRESSION__RHS, AssignExpression.class, Expression.class); + register(BINARY_OPERATOR__LHS, BinaryOperator.class, Expression.class); + register(BINARY_OPERATOR__RHS, BinaryOperator.class, Expression.class); + register(BLOCK__STATEMENTS, Block.class, Statement.class); + register(CALL_EXPRESSION__ARGUMENTS, CallExpression.class, Expression.class); + register(CALL_EXPRESSION__CALLEE, CallExpression.class, Expression.class); + register(DECLARATION_STATEMENT__DECLARATIONS, DeclarationStatement.class, Declaration.class); + register(DO_STATEMENT__CONDITION, DoStatement.class, Expression.class); + register(DO_STATEMENT__STATEMENT, DoStatement.class, Statement.class); + register(FOR_STATEMENT__CONDITION, ForStatement.class, Expression.class); + register(FOR_STATEMENT__INITIALIZER_STATEMENT, ForStatement.class, Statement.class); + register(FOR_STATEMENT__ITERATION_STATEMENT, ForStatement.class, Statement.class); + register(FOR_STATEMENT__STATEMENT, ForStatement.class, Statement.class); + register(FUNCTION_TYPE__PARAMETERS, FunctionType.class, Type.class); + register(FUNCTION_TYPE__RETURN_TYPES, FunctionType.class, Type.class); + register(IF_STATEMENT__CONDITION, IfStatement.class, Expression.class); + register(IF_STATEMENT__ELSE_STATEMENT, IfStatement.class, Statement.class); + register(IF_STATEMENT__THEN_STATEMENT, IfStatement.class, Statement.class); + register(MEMBER_EXPRESSION__BASE, MemberExpression.class, Expression.class); + register(METHOD_DECLARATION__BODY, MethodDeclaration.class, Statement.class); + register(METHOD_DECLARATION__PARAMETERS, MethodDeclaration.class, ParameterDeclaration.class); + register(METHOD_DECLARATION__RECORD_DECLARATION, MethodDeclaration.class, RecordDeclaration.class); + register(NAMESPACE_DECLARATION__DECLARATIONS, NamespaceDeclaration.class, Declaration.class); + register(RECORD_DECLARATION__FIELDS, RecordDeclaration.class, FieldDeclaration.class); + register(RECORD_DECLARATION__METHODS, RecordDeclaration.class, MethodDeclaration.class); + register(RETURN_STATEMENT__RETURN_VALUES, ReturnStatement.class, Expression.class); + register(SUBSCRIPT_EXPRESSION__ARRAY_EXPRESSION, SubscriptExpression.class, Expression.class); + register(SUBSCRIPT_EXPRESSION__SUBSCRIPT_EXPRESSION, SubscriptExpression.class, Expression.class); + register(TRANSLATION_UNIT__DECLARATIONS, TranslationUnitDeclaration.class, Declaration.class); + register(UNARY_OPERATOR__INPUT, UnaryOperator.class, Expression.class); + register(VALUE_DECLARATION__TYPE, ValueDeclaration.class, Type.class); + register(VARIABLE_DECLARATION__INITIALIZER, VariableDeclaration.class, Expression.class); + register(WHILE_STATEMENT__CONDITION, WhileStatement.class, Expression.class); + register(WHILE_STATEMENT__STATEMENT, WhileStatement.class, Statement.class); + } + + private Edges() { + /* should not be instantiated */ + } + + /** + * Registers an AST edge type to be found by {@link WildcardGraphPattern}s and by the general {@link Builder}. + * @param edge the edge + * @param sClass node class of the edge source + * @param tClass node class of the edge target + * @param type of the edge source + * @param type of the related node + */ + private static void register(IEdge edge, Class sClass, Class tClass) { + edge.setSourceClass(sClass); + edge.setRelatedClass(tClass); + edgesBySourceType.computeIfAbsent(sClass, c -> new ArrayList<>()).add(edge); + edgesByTargetType.computeIfAbsent(tClass, c -> new ArrayList<>()).add(edge); + } + + /** + * Gets the list of edges with the given node class as target. + * @param tClass the target node class + * @param the related node type + * @param consumer a consumer to process the edges + */ + public static void getEdgesToType(Class tClass, Consumer> consumer) { + Class type = tClass; + while (Node.class.isAssignableFrom(type)) { + edgesByTargetType.getOrDefault(type, List.of()).stream().map(e -> (IEdge) e).forEach(consumer); + type = getSuperclass(type); + } + } + + private static Class getSuperclass(Class type) { + if (!type.getSuperclass().equals(type)) { + return type.getSuperclass(); + } + return (Class) type.getGenericSuperclass(); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/IEdge.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/IEdge.java new file mode 100644 index 0000000000..8213d9318e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/edges/IEdge.java @@ -0,0 +1,80 @@ +package de.jplag.java_cpg.transformation.matching.edges; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; +import de.jplag.java_cpg.transformation.matching.pattern.GraphPattern; + +/** + * This serves as an interface to wrap any kind of {@link PropertyEdge}. + * @param the source node type + * @param the related node type + */ +public interface IEdge { + + /** + * Sets the class object representing the source {@link Node} type. + * @param sourceClass the source {@link Node} class + */ + void setSourceClass(Class sourceClass); + + /** + * Sets the class object representing the related {@link Node} type. + * @param relatedClass the related {@link Node} class + */ + void setRelatedClass(Class relatedClass); + + /** + * Gets the class object representing the source {@link Node} type. + * @return the source {@link Node} class + */ + Class getSourceClass(); + + /** + * Gets the class object representing the target {@link Node} type. + * @return the target {@link Node} class + */ + Class getRelatedClass(); + + /** + * If true, this edge should be treated as equivalent to this one in the context of stepping through the source and + * target {@link GraphPattern}s. + * @param other the edge to check for equivalence + * @return true if the other edge is equivalent + */ + boolean isEquivalentTo(IEdge other); + + /** + * If true, this is an {@link EdgeCategory#AST} edge. + * @return true iff this is an AST edge + */ + boolean isAst(); + + /** + * If true, this is an {@link EdgeCategory#ANALYTIC} edge. + * @return true iff this is an analytic edge + */ + boolean isAnalytic(); + + /** + * If true, this is an {@link EdgeCategory#REFERENCE} edge. + * @return true iff this is a reference edge + */ + boolean isReference(); + + enum EdgeCategory { + /** + * An edge that represents the inherent structure of the code. + */ + AST, + + /** + * An edge that represents that nodes are connected by reference (e.g. method call -> method definition). + */ + REFERENCE, + + /** + * An edge that represents a connection that is the result of a calculation. These edges can never be set. + */ + ANALYTIC + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPattern.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPattern.java new file mode 100644 index 0000000000..75bf288010 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPattern.java @@ -0,0 +1,84 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.GraphTransformation; +import de.jplag.java_cpg.transformation.Role; + +/** + * A {@link GraphPattern} represents a collection of CPG nodes related to each other in specific ways. + */ +public interface GraphPattern { + + NodePattern getPattern(Role role); + + /** + * Returns a collection of the identifiers of all {@link NodePattern}s of this {@link GraphPattern}. + * @return the identifiers + */ + Collection getAllRoles(); + + /** + * Compares this {@link GraphPattern} to another {@link GraphPattern} in order to generate a {@link GraphTransformation} + * between the two. The type of {@link GraphPattern} determines how the comparison works. + * @param targetPattern the target pattern + * @param compareFunction a comparison function for {@link NodePattern}s + */ + void compareTo(GraphPattern targetPattern, BiConsumer, NodePattern> compareFunction); + + /** + * Adds a newly created {@link NodePattern} to this pattern. This occurs when a {@link GraphTransformation} includes the + * generation of new {@link Node}s. + * @param role the role of the node pattern + * @param newNode the new node pattern (gets copied) + * @param the node type + * @return the new node pattern + */ + NodePattern addNode(Role role, NodePattern newNode); + + /** + * Gets the {@link NodePattern} associated to the given role. + * @param role the role + * @return the node pattern + */ + NodePattern getPattern(Role role, Class nodeClass); + + /** + * Gets the identifier that the given {@link NodePattern} is associated to in this {@link GraphPattern}. + * @param pattern the node pattern + * @return the identifier + */ + Role getRole(NodePattern pattern); + + /** + * Gets the representing node of this {@link GraphPattern}. + * @return the representing node + */ + NodePattern getRepresentingNode(); + + /** + * Returns the list of root {@link NodePattern}s. + * @return the root(s). + */ + List> getRoots(); + + /** + * Matches the given candidate {@link Node}s against the respective root {@link NodePattern}s of this + * {@link GraphPattern}. + * @param rootCandidates the root candidate {@link Node}s for each root {@link NodePattern} + * @return the matches + */ + List match(Map, List> rootCandidates); + + /** + * Verifies that the given match of this {@link GraphPattern} is still valid. After a transformation involving the + * match's {@link Node}s, a match may be invalidated. + * @param match the match + * @return true iff the match is still valid + */ + boolean validate(Match match); +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPatternBuilder.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPatternBuilder.java new file mode 100644 index 0000000000..20cee6f9b6 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPatternBuilder.java @@ -0,0 +1,672 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.IntStream; + +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.GraphTransformation; +import de.jplag.java_cpg.transformation.Role; +import de.jplag.java_cpg.transformation.matching.edges.CpgAttributeEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.pattern.relation.ForAllRelatedNode; +import de.jplag.java_cpg.transformation.matching.pattern.relation.RelatedNode; +import de.jplag.java_cpg.transformation.matching.pattern.relation.RelatedOneToNNode; + +/** + * Abstract builder class for {@link GraphPattern}s, offering convenience methods. + */ +public abstract class GraphPatternBuilder { + /** + * A registry for {@link NodePattern}s, which may be re-referenced after their creation. + */ + private final PatternRegistry patterns; + + /** + * Creates a new {@link GraphPatternBuilder}. + */ + protected GraphPatternBuilder() { + this.patterns = new PatternRegistry(); + } + + /** + * Creates a {@link NodePattern} of the given {@link Node} {@link Class}. + * @param tClass the class of the represented {@link Node} + * @param role a role for this {@link NodePattern} + * @param patterns the pattern registry as in-out parameter + * @param modifications the modifications to be applied to the new {@link NodePattern} + * @param the {@link Node} type of the represented {@link Node} + * @return the node pattern + */ + @NotNull + private static NodePattern createNodePattern(Class tClass, Role role, PatternRegistry patterns, + List> modifications) { + NodePattern related = NodePattern.forNodeType(tClass); + patterns.put(role, related); + modifications.forEach(m -> m.apply(related, patterns)); + return related; + } + + /** + * Creates a {@link PatternModification} that adds the {@link MatchProperty} to a {@link NodePattern} that a specific + * attribute of a matching {@link Node} must be equal to the same attribute of another {@link Node}. + * @param propertyEdge the property edge + * @param otherRole the role of the other {@link NodePattern} + * @param the {@link Node} type + * @param

the attribute type + * @return the pattern modification + */ + public static PatternModification equalAttributes(CpgAttributeEdge propertyEdge, Class sClass, Role otherRole) { + return new AddEqualAttributes<>(propertyEdge, sClass, otherRole); + } + + /** + * Creates a {@link PatternModification} that adds the {@link MatchProperty} to a {@link NodePattern} that the assigned + * value is unchanged between the evaluation of the two given {@link Node}s. + * @param startRole the role of the starting node role + * @param endRole the role of the end node role + * @param the type of + * @return the pattern modification + */ + public static PatternModification assignedValueStableBetween(Role startRole, Role endRole) { + return new AddAssignedValueStableBetween<>(startRole, endRole); + } + + /** + * Creates a {@link PatternModification} that adds a {@link Predicate} property to a {@link NodePattern} that specifies + * that matching {@link Node}s not be equal to the {@link Node} given by the role. + * @param otherRole the role of the other {@link Node} + * @param the {@link Node} type of the target node + * @return the pattern modification + */ + public static PatternModification notEqualTo(Role otherRole) { + return new AddNotEqualTo<>(otherRole); + } + + @SafeVarargs + public static PatternModification relatedConsecutive(CpgMultiEdge edge, Class cClass, + PatternListModification... listModifications) { + return new AddConsecutive<>(edge, cClass, Arrays.asList(listModifications)); + } + + /** + * Builds a {@link SimpleGraphPattern}. The specifics of the structure of the SimpleGraphPattern are defined in the + * concrete subclasses of {@link GraphPatternBuilder}. + * @return the graph patterns + */ + public abstract GraphPattern build(); + + /** + * Convenience method to create a {@link NodePattern}. + * @param tClass the {@link Node} class of the pattern + * @param role the role for the pattern + * @param modifications a list of modifications to the {@link NodePattern} + * @param the node type + * @return the node pattern + */ + @SafeVarargs + public final SimpleGraphPattern create(Class tClass, Role role, PatternModification... modifications) { + NodePattern pattern; + if (patterns.containsPattern(role)) { + pattern = patterns.getPattern(role, tClass); + } else { + pattern = NodePattern.forNodeType(tClass); + patterns.put(role, pattern); + } + Arrays.asList(modifications).forEach(m -> m.apply(pattern, patterns)); + return new SimpleGraphPattern<>(pattern, patterns); + } + + /** + * Creates an empty {@link WildcardGraphPattern}. It can be used as a target pattern for a {@link GraphTransformation} + * where a {@link Node} shall be removed. + * @return the wildcard graph patterns + */ + protected SimpleGraphPattern emptyWildcardParent() { + return new WildcardGraphPattern<>(Node.class, null, patterns); + } + + /** + * Creates a {@link PatternModification} that adds a {@link ForAllRelatedNode} to a {@link NodePattern}. + * @param multiEdge The multi edge + * @param cClass the concrete class object of the for-all related nodes + * @param role the role for the related nodes + * @param modifications the modifications for the created node patterns + * @param the type of source node + * @param the type of related node, defined by the edge + * @param the concrete type of related node + * @return the pattern modification + */ + @SafeVarargs + public final PatternModification forAllRelated(CpgMultiEdge multiEdge, Class cClass, + Role role, PatternModification... modifications) { + return new AddForAllRelated<>(multiEdge, cClass, role, Arrays.asList(modifications)); + } + + /** + * Creates a {@link MultiGraphPattern} from the given {@link SimpleGraphPattern}s. + * @param subgraphs the subgraph patterns + * @return the multi graph patterns + */ + public final MultiGraphPattern multiRoot(SimpleGraphPattern... subgraphs) { + return new MultiGraphPattern(List.of(subgraphs), patterns); + } + + /** + * Creates a {@link PatternListModification} that adds a related {@link NodePattern} to another + * {@link NodePattern}ePattern in sequence. + * @param cClass the concrete class of the added NodePattern + * @param role the role for the node patterns + * @param modifications the modifications to be applied to the node + * @param the concrete type of the added node + * @return the patterns list modification + */ + @SafeVarargs + public final PatternListModification node(Class cClass, Role role, PatternModification... modifications) { + return new AddNode<>(cClass, role, Arrays.asList(modifications)); + } + + /** + * Creates a {@link PatternModification} to add a property to a {@link NodePattern}. + * @param property the predicate establishing the property + * @param the target {@link Node} type + * @return the pattern modification + */ + public final PatternModification property(Predicate property) { + return new AddProperty<>(property); + } + + /** + * Creates a {@link PatternModification} that adds a new node patterns that is related to the reference node patterns + * via a 1:n relation. + * @param edge the edge type connecting the node patterns + * @param rClass the class object indicating the specified target's class type + * @param role the name of the new related node patterns + * @param modifications a list of modifications targeting the new node patterns + * @param the type of the source node patterns + * @param the type of the relation target, defined by the edge + * @param the concrete type of the related node patterns + * @return the pattern modification object + */ + @SafeVarargs + public final PatternModification related(CpgEdge edge, Class rClass, Role role, + PatternModification... modifications) { + return new AddRelatedNode<>(edge, rClass, role, Arrays.asList(modifications)); + } + + /** + * Creates a {@link PatternModification} that adds a new node patterns that is related to the reference node patterns + * via a 1:n relation. + * @param edge the edge type connecting the node patterns + * @param rClass the class object indicating the specified target's class type + * @param role the name of the new related node patterns + * @param modifications a list of modifications targeting the new node patterns + * @param the type of the source node patterns + * @param the type of the relation target, defined by the edge + * @param the concrete type of the related node patterns + * @return the pattern modification object + */ + @SafeVarargs + public final PatternModification related1ToN(CpgMultiEdge edge, Class rClass, Role role, + PatternModification... modifications) { + return new AddRelated1ToNNode<>(edge, rClass, role, Arrays.asList(modifications)); + } + + /** + * Creates a {@link PatternModification} to add a 1:1 relation to an existing {@link NodePattern}. + * @param edge the edge establishing the relation + * @param role the {@link Role} of the existing target {@link NodePattern} + * @param cClass the class of the related node + * @param modifications modifications to the related node + * @param the target node type, as specified by the edge + * @param the related node type, as specified by the edge + * @param the concrete node type of the related node + * @return the pattern modification + */ + @SafeVarargs + public final PatternModification relatedExisting(CpgEdge edge, Class cClass, Role role, + PatternModification... modifications) { + return new AddRelatedExistingNode<>(edge, cClass, role, List.of(modifications)); + } + + /** + * Creates a {@link PatternModification} to add a 1:n relation to an existing {@link NodePattern}. + * @param edge the multi-edge establishing the relation + * @param role the {@link Role} of the existing target {@link NodePattern} + * @param modifications modifications to the related node + * @param the target node type, as specified by the edge + * @param the related node type, as specified by the edge + * @param the concrete node type of the related node + * @return the pattern modification + */ + @SafeVarargs + public final PatternModification relatedExisting1ToN(CpgMultiEdge edge, Class cClass, + Role role, PatternModification... modifications) { + return new AddRelatedExisting1ToNNode<>(edge, cClass, role, List.of(modifications)); + } + + /** + * Creates a {@link PatternModification} that sets the target {@link NodePattern} as the representative of the + * NodePattern. {@link Node}s matching this {@link NodePattern} will be the representative of the {@link Match}. + * @param the {@link Node} type + * @return the pattern modification + */ + public final PatternModification setRepresentingNode() { + return new SetRepresentingNode<>(); + } + + /** + * Creates a {@link PatternModification} that sets a flag to indicate that the child patterns contained in this patterns + * are not relevant for the transformation calculation, but only for the patterns matching. + * @param the target node patterns type + * @return the pattern modification + */ + public final PatternModification stopRecursion() { + return new StopRecursion<>(); + } + + /** + * Convenience method to create a {@link WildcardGraphPattern} with the specified child {@link NodePattern}. + * @param tClass the child {@link Node} class + * @param childRole the {@link Role} for the child patterns + * @param modifications a list of modifications targeting the child node patterns + * @param the child {@link Node} type + * @return the {@link WildcardGraphPattern} + */ + @SafeVarargs + public final WildcardGraphPattern wildcardParent(Class tClass, Role childRole, + PatternModification... modifications) { + NodePattern child; + if (!patterns.containsPattern(childRole)) { + child = createNodePattern(tClass, childRole, patterns, Arrays.asList(modifications)); + } else { + child = patterns.getPattern(childRole, tClass); + Arrays.stream(modifications).forEach(m -> m.apply(child, patterns)); + } + return new WildcardGraphPattern<>(tClass, child, patterns); + } + + /** + * {@link PatternModification}s serve to modify a {@link NodePattern}, e.g. add relations and properties to it. + * @param the target {@link Node} type + */ + public sealed interface PatternModification + permits AddAssignedValueStableBetween, AddConsecutive, AddEqualAttributes, AddForAllRelated, AddNotEqualTo, AddProperty, + AddRelated1ToNNode, AddRelatedExisting1ToNNode, AddRelatedExistingNode, AddRelatedNode, SetRepresentingNode, StopRecursion { + /** + * Applies this {@link PatternModification} to the given target {@link NodePattern}. + * @param target the target {@link NodePattern} + * @param patterns the current {@link SimpleGraphPattern}'s patterns + */ + void apply(NodePattern target, PatternRegistry patterns); + + } + + /** + * {@link PatternModification}s serve to modify a {@link NodePattern}, e.g. add relations and properties to it. + * @param the target {@link Node} type + */ + public sealed interface PatternListModification permits AddNode { + /** + * Applies this {@link PatternListModification} to the given target {@link NodePattern}. + * @param target the target {@link NodePattern} + * @param patterns the current {@link SimpleGraphPattern}'s patterns + */ + void apply(NodePattern target, CpgMultiEdge edge, PatternRegistry patterns, + Consumer> register); + } + + /** + * A PatternModification to add a {@link NodePattern} related via a {@link CpgEdge}. + * @param The source {@link Node} type + * @param The target {@link Node} type, specified by the edge + * @param The concrete target {@link Node} type + */ + static final class AddRelatedNode implements PatternModification { + + private final CpgEdge getter; + private final Class cClass; + private final Role role; + + private final List> modifications; + + /** + * Creates a new {@link AddRelatedNode} object. + * @param edge the edge connecting the source node with the target node + * @param cClass the concrete class of the target node + * @param role the role for the related node + * @param modifications list of modifications to the target node + */ + public AddRelatedNode(CpgEdge edge, Class cClass, Role role, List> modifications) { + this.getter = edge; + this.cClass = cClass; + this.role = role; + this.modifications = modifications; + } + + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + // C extends R -> safe + @SuppressWarnings("unchecked") + NodePattern related = (NodePattern) createNodePattern(cClass, role, patterns, modifications); + target.addRelation(new RelatedNode<>(related, getter)); + } + + } + + static final class AddNode implements PatternListModification { + private final Class cClass; + private final Role role; + private final List> modifications; + + public AddNode(Class cClass, Role role, List> modifications) { + this.cClass = cClass; + this.role = role; + this.modifications = modifications; + } + + @Override + public void apply(NodePattern target, CpgMultiEdge edge, PatternRegistry patterns, + Consumer> register) { + NodePattern related = createNodePattern(cClass, role, patterns, modifications); + register.accept(related); + target.addRelation(new RelatedOneToNNode<>(related, edge)); + } + } + + /** + * A {@link PatternModification} to add a {@link NodePattern} related via a CpgEdge that has already been created. + * @param The source {@link Node} type + * @param The target {@link Node} type, specified by the edge + */ + static final class AddRelatedExistingNode implements PatternModification { + + private final CpgEdge getter; + private final Class cClass; + private final Role role; + private final List> modifications; + + /** + * Creates a new {@link AddRelatedExistingNode} object. + * @param edge the edge connecting the source node with the target node + * @param cClass the node class of the related node + * @param role the role for the related node + */ + public AddRelatedExistingNode(CpgEdge edge, Class cClass, Role role, List> modifications) { + this.getter = edge; + this.cClass = cClass; + this.role = role; + this.modifications = modifications; + } + + public void apply(NodePattern target, PatternRegistry patterns) { + NodePattern related = patterns.getPattern(role, cClass); + target.addRelation(new RelatedNode<>(related, getter)); + modifications.forEach(m -> m.apply(related, patterns)); + } + } + + /** + * A {@link PatternModification} to add a required {@link Predicate} property to a NodePattern. + * @param The target {@link Node} type + */ + + static final class AddProperty implements PatternModification { + private final Predicate property; + + public AddProperty(Predicate property) { + this.property = property; + } + + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + target.addProperty(property); + } + } + + /** + * A {@link PatternModification} to add a {@link NodePattern} related via a CpgMultiEdge that has already been created. + * @param The source {@link Node} type + * @param The target {@link Node} type, specified by the edge + */ + static final class AddRelatedExisting1ToNNode implements PatternModification { + + private final CpgMultiEdge edge; + private final Class cClass; + private final Role role; + private final List> modifications; + + /** + * Creates a new {@link AddRelatedExisting1ToNNode} object. + * @param edge the edge connecting the source node with the target node + * @param role the role for the related node + */ + public AddRelatedExisting1ToNNode(CpgMultiEdge edge, Class cClass, Role role, List> modifications) { + this.edge = edge; + this.cClass = cClass; + this.role = role; + this.modifications = modifications; + + } + + public void apply(NodePattern target, PatternRegistry patterns) { + NodePattern related = patterns.getPattern(role, cClass); + target.addRelation(new RelatedOneToNNode<>(related, edge)); + modifications.forEach(m -> m.apply(related, patterns)); + } + } + + /** + * A {@link PatternModification} to add a {@link NodePattern} related via a CpgMultiEdge. + * @param The target {@link Node} type + * @param The related {@link Node} type, specified by the edge + * @param The concrete target {@link Node} type + */ + static final class AddRelated1ToNNode implements PatternModification { + private final CpgMultiEdge edge; + private final Class cClass; + private final Role role; + private final List> modifications; + + /** + * Creates a new {@link AddRelated1ToNNode} object. + * @param edge the edge connecting the source node with the target node + * @param cClass the concrete class of the target node + * @param role the role for the related node + * @param modifications list of modifications to the target node + */ + public AddRelated1ToNNode(CpgMultiEdge edge, Class cClass, Role role, List> modifications) { + this.edge = edge; + this.cClass = cClass; + this.role = role; + this.modifications = modifications; + } + + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + NodePattern related = NodePattern.forNodeType(cClass); + patterns.put(role, related); + modifications.forEach(m -> m.apply(related, patterns)); + target.addRelation(new RelatedOneToNNode<>(related, edge)); + } + } + + static final class AddForAllRelated implements PatternModification { + private final CpgMultiEdge edge; + private final Class cClass; + private final Role role; + private final List> modifications; + + /** + * Creates a new {@link AddForAllRelated} object. + * @param edge the edge connecting the source node with the target node + * @param cClass the concrete class of the target node + * @param modifications list of modifications to the target node + */ + public AddForAllRelated(CpgMultiEdge edge, Class cClass, Role role, List> modifications) { + this.edge = edge; + this.cClass = cClass; + this.role = role; + this.modifications = modifications; + } + + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + NodePattern related = NodePattern.forNodeType(cClass); + patterns.put(role, related); + modifications.forEach(m -> m.apply(related, patterns)); + target.addRelation(new ForAllRelatedNode<>(related, edge)); + } + } + + private record AddEqualAttributes(CpgAttributeEdge propertyEdge, Class sClass, Role otherRole) + implements PatternModification { + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + NodePattern otherPattern = patterns.getPattern(otherRole, sClass); + MatchProperty property = (t, match) -> { + P value = propertyEdge.get(t); + P otherValue = propertyEdge.get(match.get(otherPattern)); + return Objects.equals(value, otherValue); + }; + target.addMatchProperty(property); + } + } + + private record AddNotEqualTo(Role otherRole) implements PatternModification { + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + NodePattern otherPattern = patterns.getPattern(otherRole, Node.class); + MatchProperty property = (s, match) -> !Objects.equals(s, match.get(otherPattern)); + target.addMatchProperty(property); + } + } + + public static final class AddConsecutive implements PatternModification { + private final CpgMultiEdge edge; + private final Class cClass; + private final List> modifications; + private final List> elements; + + public AddConsecutive(CpgMultiEdge edge, Class cClass, List> modifications) { + this.edge = edge; + this.cClass = cClass; + this.modifications = modifications; + this.elements = new ArrayList<>(); + } + + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + modifications.forEach(m -> m.apply(target, edge, patterns, elements::addLast)); + + IntStream.range(0, elements.size()).forEach(idx -> + // set min index + edge.saveSequenceIndex(elements.get(idx), idx)); + + MatchProperty property = (parent, match) -> { + List allTargets = edge.getAllTargets(parent); + int firstChildIndex = allTargets.indexOf(match.get(elements.getFirst())); + for (int idx = 1; idx < modifications.size(); idx++) { + int childIdx = allTargets.indexOf(match.get(elements.get(idx))); + if (childIdx != firstChildIndex + idx) { + return false; + } + } + return true; + }; + target.addMatchProperty(property); + } + + public CpgMultiEdge edge() { + return edge; + } + + @Override + public int hashCode() { + return Objects.hash(edge, cClass, modifications); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != this.getClass()) + return false; + var that = (AddConsecutive) obj; + return Objects.equals(this.edge, that.edge) && Objects.equals(this.cClass, that.cClass) + && Objects.equals(this.modifications, that.modifications); + } + + @Override + public String toString() { + return "AddConsecutive[" + "edge=" + edge + ", " + "cClass=" + cClass + ", " + "modifications=" + modifications + ']'; + } + + } + + private static final class StopRecursion implements PatternModification { + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + target.markStopRecursion(); + } + } + + private static final class SetRepresentingNode implements PatternModification { + + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + patterns.setRepresentingNode(target); + } + } + + private record AddAssignedValueStableBetween(Role startRole, Role endRole) implements PatternModification { + + @Override + public void apply(NodePattern target, PatternRegistry patterns) { + NodePattern startNP = patterns.getPattern(startRole, Node.class); + NodePattern endNP = patterns.getPattern(endRole, Node.class); + MatchProperty matchProperty = (t, match) -> { + Set assignNodes = PatternUtil.dfgReferences(t); + // s is a constant term + if (assignNodes.isEmpty()) { + return true; + } + + Set seenList = new HashSet<>(); + Node start = match.get(startNP); + Node end = match.get(endNP); + LinkedList workList = new LinkedList<>(start.getNextEOG()); + seenList.add(start); + while (!workList.isEmpty()) { + Node node = workList.removeFirst(); + if (!seenList.containsAll(node.getPrevEOG())) { + // Process all predecessors first + continue; + } + List eogSuccessors = new LinkedList<>(node.getNextEOG()); + eogSuccessors.removeAll(seenList); + eogSuccessors.removeAll(assignNodes); + + if (eogSuccessors.contains(end)) { + return true; + } + + workList.addAll(eogSuccessors); // add to the end + seenList.add(node); + } + return false; + }; + target.addMatchProperty(matchProperty); + } + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPatternImpl.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPatternImpl.java new file mode 100644 index 0000000000..0e2efa5282 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/GraphPatternImpl.java @@ -0,0 +1,70 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.Role; + +/** + * This abstract class contains the method implementations common to all types of concrete {@link GraphPattern}s. + */ +public abstract class GraphPatternImpl implements GraphPattern { + protected final PatternRegistry patternRegistry; + protected NodePattern representingNode; + + /** + * Constructs a new {@link GraphPatternImpl} from a {@link PatternRegistry}. + * @param patterns the {@link PatternRegistry} for this graph pattern + */ + protected GraphPatternImpl(PatternRegistry patterns) { + representingNode = patterns.getRepresentingNode(); + this.patternRegistry = patterns; + } + + static List copy(List matches) { + return matches.stream().map(Match::copy).collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public NodePattern getPattern(Role role) { + return patternRegistry.getPattern(role, Node.class); + } + + @Override + public Collection getAllRoles() { + return patternRegistry.allRoles(); + } + + @Override + public NodePattern addNode(Role roleName, NodePattern pattern) { + NodePattern patternCopy = pattern.deepCopy(); + this.patternRegistry.put(roleName, patternCopy); + return patternCopy; + } + + @Override + public NodePattern getPattern(Role role, Class tClass) { + return patternRegistry.getPattern(role, tClass); + } + + /** + * Gets the {@link Role} of the given {@link NodePattern} + * @param pattern the node pattern + * @return the role + */ + public Role getRole(NodePattern pattern) { + return patternRegistry.getRole(pattern); + } + + /** + * Gets the representingNode of this {@link GraphPatternImpl}. + * @return the representative {@link NodePattern} + */ + public NodePattern getRepresentingNode() { + return (NodePattern) representingNode; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/Match.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/Match.java new file mode 100644 index 0000000000..66cf1dcda9 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/Match.java @@ -0,0 +1,321 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.desc; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.matching.edges.AnyOfNEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgNthEdge; +import de.jplag.java_cpg.transformation.matching.pattern.WildcardGraphPattern.ParentNodePattern; +import de.jplag.java_cpg.transformation.matching.pattern.relation.OneToNRelation; +import de.jplag.java_cpg.transformation.matching.pattern.relation.RelatedNode; +import de.jplag.java_cpg.transformation.operations.GraphOperation; +import de.jplag.java_cpg.transformation.operations.GraphOperationImpl; + +/** + * A {@link Match} stores the mapping between a {@link GraphPattern} and {@link Node}s matching the pattern. Especially, + * a {@link ParentNodePattern}'s match in the sourceGraph can be saved. + */ +public class Match implements Comparable { + + private final Map, Node> patternToNode; + private final GraphPattern pattern; + private final Match parent; + private final Map, WildcardMatch> wildcardMatches; + private final Map, CpgEdge> edgeMap; + + private final int childId; + private int childCount; + + /** + * Creates a new {@link Match}. + * @param pattern the {@link SimpleGraphPattern} of which this is a {@link Match}. + */ + public Match(GraphPattern pattern) { + this.pattern = pattern; + patternToNode = new HashMap<>(); + edgeMap = new HashMap<>(); + this.childId = 0; + this.childCount = 0; + parent = this; + this.wildcardMatches = new HashMap<>(); + } + + /** + * Creates a new {@link Match}. + * @param pattern the {@link GraphPattern} that this is a match of + * @param parent the parent {@link Match} + */ + public Match(GraphPattern pattern, Match parent) { + this.pattern = pattern; + patternToNode = new HashMap<>(parent.patternToNode); + edgeMap = new HashMap<>(parent.edgeMap); + this.childCount = 0; + this.childId = parent.childCount++; + this.parent = parent; + wildcardMatches = new HashMap<>(parent.wildcardMatches); + } + + @Override + public int compareTo(Match o) { + Iterator thisId = this.getFullID().iterator(); + Iterator otherId = o.getFullID().iterator(); + + while (true) { + if (!thisId.hasNext() || !otherId.hasNext()) { + return (thisId.hasNext() ? 1 : 0) + (otherId.hasNext() ? -1 : 0); + } + + Integer thisNext = thisId.next(); + Integer otherNext = otherId.next(); + if (!thisNext.equals(otherNext)) { + return thisNext - otherNext; + } + } + } + + /** + * Checks if the given {@link NodePattern} is contained in this {@link Match}. + * @param pattern the patterns + * @return true if the pattern is contained in this {@link Match} + */ + public boolean contains(NodePattern pattern) { + return patternToNode.containsKey(pattern); + } + + /** + * Creates a copy of this {@link Match} in its current state. + * @return the copy + */ + public Match copy() { + return new Match(pattern, this); + } + + @Override + public int hashCode() { + int result = pattern.hashCode(); + result = 31 * result + parent.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Match match = (Match) o; + + if (this.pattern != match.pattern) { + return false; + } + if (this.pattern.getAllRoles().stream().anyMatch(role -> { + Node patternNode1 = this.get(this.pattern.getPattern(role)); + Node patternNode2 = match.get(match.pattern.getPattern(role)); + return !Objects.equals(patternNode1, patternNode2); + })) { + return false; + } + + boolean wildcardMismatch = !this.wildcardMatches.keySet().stream() + .allMatch(key -> Objects.equals(this.wildcardMatches.get(key), match.wildcardMatches.get(key))); + if (wildcardMismatch) { + return false; + } + + return this.edgeMap.keySet().stream().allMatch(key -> Objects.equals(this.edgeMap.get(key), match.edgeMap.get(key))); + } + + @Override + public String toString() { + return getFullID().stream().map(Object::toString).collect(Collectors.joining(".")) + + (getRepresentingNode() == null ? "" : "[%s]".formatted(desc(getRepresentingNode()))); + } + + /** + * Gets the concrete {@link Node} corresponding to the given {@link NodePattern}. + * @param pattern the pattern + * @param the node type + * @return the concrete {@link Node} + */ + public T get(NodePattern pattern) { + return (T) patternToNode.get(pattern); + } + + /** + * Gets the concrete {@link CpgNthEdge} for a {@link AnyOfNEdge} in this {@link Match}. + * @param sourcePattern the source pattern + * @param edge the any-of-n edge + * @param the source node type + * @param the related node type + * @return the nth edge + */ + public CpgNthEdge getEdge(NodePattern sourcePattern, AnyOfNEdge edge) { + return (CpgNthEdge) this.edgeMap.get(new EdgeMapKey<>(sourcePattern, edge)); + } + + private LinkedList getFullID() { + LinkedList id = Objects.equals(this, this.parent) ? new LinkedList<>() : parent.getFullID(); + id.addLast(this.childId); + return id; + } + + /** + * Gets the representing {@link Node} of this {@link Match}. + * @return the representing node. + */ + public Node getRepresentingNode() { + NodePattern representingNodePattern = this.pattern.getRepresentingNode(); + if (Objects.isNull(representingNodePattern)) { + return null; + } + return this.get(representingNodePattern); + } + + /** + * Gets the count of {@link Node}s that are part of this {@link Match}. + * @return the number of nodes + */ + public int getSize() { + return patternToNode.size(); + } + + public GraphOperation instantiateGraphOperation(ParentNodePattern wcParent, GraphOperationImpl wildcardedOperation) { + WildcardMatch wildcardMatch = (WildcardMatch) this.wildcardMatches.get(wcParent); + return wildcardMatch.instantiateGraphOperation(wildcardedOperation::fromWildcardMatch); + } + + /** + * Adds a matching concrete {@link Node} to this {@link Match}. + * @param pattern the {@link NodePattern} matching the node + * @param node the node + * @param the concrete {@link Node} type + */ + public void register(NodePattern pattern, N node) { + patternToNode.put(pattern, node); + } + + /** + * Removes a node from this {@link Match}. + * @param pattern the pattern + */ + public void remove(NodePattern pattern) { + this.patternToNode.remove(pattern); + } + + /** + * Resolves an {@link AnyOfNEdge} to a concrete {@link CpgNthEdge}. + * @param parent the parent node pattern + * @param relation the relation object + * @param index the child index + * @param the parent node type + * @param the child node type + * @return the nth edge + */ + public Match resolveAnyOfNEdge(NodePattern parent, OneToNRelation relation, int index) { + AnyOfNEdge anyOfNEdge = relation.getEdge().getAnyOfNEdgeTo(relation.pattern); + CpgNthEdge concreteEdgePattern = new CpgNthEdge<>(anyOfNEdge.getMultiEdge(), index); + EdgeMapKey key = new EdgeMapKey<>(parent, anyOfNEdge); + this.edgeMap.put(key, concreteEdgePattern); + return this; + } + + /** + * Saves the concrete parent {@link Node} and edge corresponding to a {@link WildcardGraphPattern}. + * @param parentPattern the parent node pattern + * @param parent the concrete parent node + * @param edge the edge + * @param the node type of the parent as specified by the edge + * @param the node type of the child as specified by the edge + */ + public void resolveWildcard(ParentNodePattern parentPattern, T parent, CpgEdge edge) { + NodePattern concreteRoot = NodePattern.forNodeType(edge.getSourceClass()); + concreteRoot.addRelation(new RelatedNode<>(parentPattern.getChildPattern(), edge)); + + this.wildcardMatches.put(parentPattern, new WildcardMatch<>(concreteRoot, edge)); + this.patternToNode.put(concreteRoot, parent); + } + + /** + * Saves the data related to a concrete occurrence of a {@link WildcardGraphPattern}. + * @param the concrete type of the child, specified by the edge + */ + public static final class WildcardMatch { + private final NodePattern parentPattern; + private final CpgEdge edge; + + /** + * @param parentPattern A concrete {@link NodePattern} for the parent + * @param edge the edge + */ + public WildcardMatch(NodePattern parentPattern, CpgEdge edge) { + this.parentPattern = parentPattern; + this.edge = edge; + } + + @Override + public int hashCode() { + return Objects.hash(parentPattern, edge); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != this.getClass()) + return false; + var that = (WildcardMatch) obj; + return Objects.equals(this.parentPattern, that.parentPattern) && Objects.equals(this.edge, that.edge); + } + + @Override + public String toString() { + return "WildcardMatch[" + "parentPattern=" + parentPattern + ", " + "edge=" + edge + ']'; + } + + public GraphOperation instantiateGraphOperation(BiFunction, CpgEdge, GraphOperation> factoryMethod) { + return factoryMethod.apply(this.parentPattern, this.edge); + } + + } + + private static final class EdgeMapKey { + private final NodePattern parent; + private final AnyOfNEdge edge; + + private EdgeMapKey(NodePattern parent, AnyOfNEdge edge) { + this.parent = parent; + this.edge = edge; + } + + @Override + public int hashCode() { + return Objects.hash(parent, edge); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != this.getClass()) + return false; + var that = (EdgeMapKey) obj; + return Objects.equals(this.parent, that.parent) && Objects.equals(this.edge, that.edge); + } + + @Override + public String toString() { + return "EdgeMapKey[" + "parent=" + parent + ", " + "edge=" + edge + ']'; + } + + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/MatchProperty.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/MatchProperty.java new file mode 100644 index 0000000000..2af460a1f8 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/MatchProperty.java @@ -0,0 +1,20 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import de.fraunhofer.aisec.cpg.graph.Node; + +/** + * A {@link MatchProperty} can be used to represent a property of a {@link Match} involving multiple {@link Node}s and + * their relations. + * @param The node type of the node that the property is assigned to + */ +@FunctionalInterface +public interface MatchProperty { + + /** + * Tests whether the {@link Node} and the {@link Match} satisfy this {@link MatchProperty}. + * @param n the node + * @param match the match + * @return true iff the match satisfies the match property + */ + boolean test(N n, Match match); +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/MultiGraphPattern.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/MultiGraphPattern.java new file mode 100644 index 0000000000..6d495d5211 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/MultiGraphPattern.java @@ -0,0 +1,80 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.GraphTransformation; + +/** + * A {@link MultiGraphPattern} is a {@link GraphPattern} that involves multiple subtrees that may or may not be linked + * to each other directly in the AST. It can be used to facilitate the formulation of complex + * {@link GraphTransformation}s. + */ +public class MultiGraphPattern extends GraphPatternImpl { + private final List> subgraphs; + + /** + * Creates a new {@link MultiGraphPattern}. + * @param subgraphs the child graphs + * @param patterns the pattern registry + */ + public MultiGraphPattern(List> subgraphs, PatternRegistry patterns) { + super(patterns); + this.subgraphs = subgraphs; + if (Objects.isNull(representingNode)) { + representingNode = subgraphs.getFirst().getRoot(); + } + } + + @Override + public List> getRoots() { + return subgraphs.stream().map(SimpleGraphPattern::getRoot).map(root -> (NodePattern) root) + .collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public List match(Map, List> rootCandidateMap) { + List matches = new ArrayList<>(); + matches.add(new Match(this)); + getRoots().forEach(root -> { + if (matches.isEmpty()) { + return; + } + List rootCandidates = rootCandidateMap.get(root); + List rootMatches = rootCandidates.stream().flatMap(rootCandidate -> { + List matchesCopy = copy(matches); + root.recursiveMatch(rootCandidate, matchesCopy); + return matchesCopy.stream(); + }).toList(); + matches.clear(); + matches.addAll(rootMatches); + // root matches from the nth root get passed on to the (n+1)th root + }); + return matches.stream().sorted().toList(); + } + + @Override + public boolean validate(Match match) { + Map, List> rootCandidates = getRoots().stream() + .collect(Collectors.toMap(root -> root, root -> List.of(match.get(root)))); + List matches = match(rootCandidates); + return matches.stream().anyMatch(match::equals); + } + + @Override + public void compareTo(GraphPattern targetPattern, BiConsumer, NodePattern> compareFunction) { + MultiGraphPattern multiTarget = (MultiGraphPattern) targetPattern; + assert this.subgraphs.size() == multiTarget.subgraphs.size(); + for (int i = 0; i < this.subgraphs.size(); i++) { + SimpleGraphPattern srcSubgraph = subgraphs.get(i); + SimpleGraphPattern tgtSubgraph = multiTarget.subgraphs.get(i); + compareFunction.accept(srcSubgraph.getRoot(), tgtSubgraph.getRoot()); + } + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/NodePattern.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/NodePattern.java new file mode 100644 index 0000000000..808ef1047f --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/NodePattern.java @@ -0,0 +1,331 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.GraphTransformation.Builder.RelationComparisonFunction; +import de.jplag.java_cpg.transformation.Role; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.pattern.relation.ForAllRelatedNode; +import de.jplag.java_cpg.transformation.matching.pattern.relation.Relation; + +/** + * This class represents a Graph pattern on node level. It can be used to match against a concrete node and its related + * nodes. + * @param the type of node represented. + */ +public interface NodePattern { + + /** + * Creates a new {@link NodePattern} for the given {@link Node} type. + * @param tClass the node class + * @param the node type + * @return the node pattern + */ + @NotNull + static NodePattern forNodeType(Class tClass) { + return new NodePatternImpl<>(tClass); + } + + /** + * Adds a for-all relation to this pattern. + * @param related the pattern to match against all related nodes + * @param edge the edge that points to the related nodes + * @param the related node type + */ + void addForAllRelated(NodePattern related, CpgMultiEdge edge); + + /** + * Adds the given {@link MatchProperty} to this {@link NodePattern}. + * @param property the match property + */ + void addMatchProperty(MatchProperty property); + + /** + * Adds a property to the pattern that has to hold for this pattern to match. + * @param property the property + */ + void addProperty(Predicate property); + + void addRelation(Relation trRelatedOneToNNode); + + /** + * Creates a copy of this {@link NodePattern} and all related nodes and properties. + * @return the copied {@link NodePattern} + */ + NodePattern deepCopy(); + + /** + * Gets the list of node classes of potential candidates for this node pattern. + * @return the candidate classes + */ + List> getCandidateClasses(); + + Role getRole(); + + void setRole(Role role); + + /** + * Gets a {@link Class} object for the indicated {@link Node} class. + * @return the class object + */ + Class getRootClass(); + + void handleRelationships(NodePattern target, RelationComparisonFunction comparator); + + /** + * Set a flag that indicates that no graph operations should be created beyond this node pattern. + */ + void markStopRecursion(); + + /** + * Checks whether the given concrete node matches this pattern. + * @param node a candidate node + * @param matches matches of pattern nodes to sourceGraph nodes + */ + void recursiveMatch(Node node, List matches); + + /** + * Determines whether the STOP_RECURSION flag has been set for this node pattern. + * @return true if STOP_RECURSION is set + */ + boolean shouldStopRecursion(); + + /** + * Standard implementation of the {@link NodePattern}. + * @param The {@link Node} type of a target node. + */ + class NodePatternImpl implements NodePattern { + + private static long candidateCounter = 0; + /** + * The class object of the concrete type of the represented node. + */ + protected final Class clazz; + /** + * List of properties that a matching node must fulfil. + */ + private final List> properties; + /** + * List of properties that a match must fulfil. + */ + private final List> matchProperties; + + private final List> relations; + + /** + * List of annotations. + */ + private final EnumSet annotations; + private Role role; + + public NodePatternImpl(Class clazz) { + this.clazz = clazz; + this.properties = new ArrayList<>(); + this.matchProperties = new ArrayList<>(); + this.relations = new ArrayList<>(); + this.annotations = EnumSet.noneOf(NodeAnnotation.class); + } + + private static void incrementCandidateCounter() { + candidateCounter++; + } + + public static long getCounter() { + return candidateCounter; + } + + public static void resetCounter() { + candidateCounter = 0; + } + + @Override + public void addForAllRelated(NodePattern related, CpgMultiEdge edge) { + // Could be refactored to a map by the edge type instead of a list + relations.add(new ForAllRelatedNode<>(related, edge)); + } + + @Override + public void addMatchProperty(MatchProperty property) { + matchProperties.add(property); + } + + @Override + public void addProperty(Predicate property) { + properties.add(property); + } + + @Override + public void addRelation(Relation relation) { + relations.add(relation); + } + + @Override + public NodePattern deepCopy() { + NodePatternImpl copy = new NodePatternImpl<>(this.clazz); + copy.properties.addAll(this.properties); + copy.relations.addAll(this.relations); + copy.matchProperties.addAll(this.matchProperties); + return copy; + } + + @Override + public List> getCandidateClasses() { + return List.of(getRootClass()); + } + + public Role getRole() { + return role; + } + + @Override + public void setRole(Role role) { + this.role = role; + } + + @Override + public Class getRootClass() { + return clazz; + } + + @Override + public void handleRelationships(NodePattern target, RelationComparisonFunction comparator) { + List> unprocessedTargetRelated = ((NodePatternImpl) target).relations; + boolean multipleCandidates = unprocessedTargetRelated.size() > 1; + + for (Relation sourceRelated : relations) { + if (sourceRelated.getEdge().isAnalytic()) { + continue; + } + + Optional> matchingTargetRelated = unprocessedTargetRelated.stream() + .filter(targetRelated -> sourceRelated.isEquivalentTo(targetRelated, multipleCandidates)).findFirst(); + + matchingTargetRelated.ifPresentOrElse(targetRelated -> { + unprocessedTargetRelated.remove(targetRelated); + comparator.castAndCompare(sourceRelated, targetRelated, this); + }, () -> + // no target candidate -> remove + comparator.castAndCompare(sourceRelated, sourceRelated, this)); + } + + for (Relation targetRelated : unprocessedTargetRelated) { + // no source candidate -> insert + comparator.castAndCompare(targetRelated, targetRelated, this); + } + } + + @Override + public void markStopRecursion() { + annotations.add(NodeAnnotation.STOP_RECURSION); + } + + @Override + public void recursiveMatch(Node node, List matches) { + // We may have encountered this pattern before. If we have not also arrived at the same node, it's a mismatch. + matches.removeIf(match -> match.contains(this) && !match.get(this).equals(node)); + var splitList = matches.stream().collect(Collectors.groupingBy(match -> match.contains(this))); + var finishedMatches = splitList.getOrDefault(true, new ArrayList<>()); + + // unencountered only + var openMatches = splitList.getOrDefault(false, new ArrayList<>()); + if (openMatches.isEmpty()) + return; + + incrementCandidateCounter(); + + // check node properties + boolean localPropertiesMismatch = !localMatch(node); + if (localPropertiesMismatch) { + matches.clear(); + return; + } + + // if !localPropertiesMismatch, then this cast is valid + T tNode = (T) node; + openMatches.forEach(match -> match.register(this, tNode)); + + // all relations must match in a specific way according to their relation type + relations.forEach(relation -> relation.recursiveMatch(this, tNode, openMatches)); + + openMatches.removeIf(match -> !matchProperties.isEmpty() && matchProperties.stream().anyMatch(mp -> !mp.test(tNode, match))); + + matches.clear(); + matches.addAll(finishedMatches); + matches.addAll(openMatches); + } + + @Override + public boolean shouldStopRecursion() { + return annotations.contains(NodeAnnotation.STOP_RECURSION); + } + + @Override + public int hashCode() { + // hashCode must not use list fields, as their hashCode changes with their content + // this would lead to HashMaps failing to recognize the same NodePattern. + int result = clazz != null ? clazz.hashCode() : 0; + result = 31 * result + super.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + NodePatternImpl that = (NodePatternImpl) o; + + if (!clazz.equals(that.clazz)) + return false; + if (!properties.equals(that.properties)) + return false; + if (!Objects.equals(matchProperties, that.matchProperties)) + return false; + if (!Objects.equals(relations, that.relations)) + return false; + return annotations.equals(that.annotations); + } + + @Override + public String toString() { + return "NodePattern{%s \"%s\"}".formatted(clazz.getSimpleName(), role.name()); + } + + /** + * Checks the local properties of the {@link NodePattern} against the concrete {@link Node}. + * @param node the node + * @return true if the node has the corresponding type and properties + */ + protected boolean localMatch(Node node) { + boolean typeMismatch = !clazz.isAssignableFrom(node.getClass()); + if (typeMismatch) { + return false; + } + + T tNode = clazz.cast(node); + + boolean propertyMismatch = properties.stream().anyMatch(p -> !p.test(tNode)); + return !propertyMismatch; + } + + private enum NodeAnnotation { + DISCONNECT_AST, + STOP_RECURSION, + REPRESENTING_NODE, + DISCONNECT_EOG + } + + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/PatternRegistry.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/PatternRegistry.java new file mode 100644 index 0000000000..9234669153 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/PatternRegistry.java @@ -0,0 +1,118 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.GraphTransformation; +import de.jplag.java_cpg.transformation.Role; + +/** + * The {@link PatternRegistry} saves the {@link NodePattern}s involved in a {@link GraphTransformation} and their + * identifiers. + */ +public class PatternRegistry { + private static final String WILDCARD_PARENT_ID = "wildcardParent#"; + private final Map> patternByRole; + private final Map, Role> roleByPattern; + + /** + * A NodePattern that represents the {@link GraphPattern}. If not set, it is the (first) root of the + * {@link GraphPattern}. + */ + private NodePattern representingNode; + + private static final Logger logger = LoggerFactory.getLogger(PatternRegistry.class); + private int wildcardCounter; + + /** + * Creates a new {@link PatternRegistry}. + */ + public PatternRegistry() { + this.patternByRole = new HashMap<>(); + this.roleByPattern = new HashMap<>(); + } + + /** + * Gets the {@link NodePattern} with the given {@link Role}, cast to the given {@link Node} class. + * @param role the role + * @return the node pattern associated to the role + */ + public NodePattern getPattern(Role role, Class targetClass) { + NodePattern nodePattern = patternByRole.get(role); + if (!targetClass.isAssignableFrom(nodePattern.getRootClass())) { + throw new ClassCastException("Pattern %s is incompatible with target class %s".formatted(role, targetClass)); + } + @SuppressWarnings("unchecked") + NodePattern castNodePattern = (NodePattern) nodePattern; + return castNodePattern; + } + + /** + * Gets the {@link Role} of a {@link NodePattern}. + * @param nodePattern the node pattern + * @return the role + */ + public Role getRole(NodePattern nodePattern) { + return roleByPattern.get(nodePattern); + } + + /** + * Gets a list of all registered roles. + * @return list of roles + */ + public Collection allRoles() { + return patternByRole.keySet(); + } + + /** + * Registers the given {@link NodePattern} with its {@link Role}. + * @param role the role + * @param pattern the node pattern + */ + public void put(Role role, NodePattern pattern) { + if (patternByRole.containsKey(role)) { + logger.warn("A NodePattern with the role '{}' is already present in the PatternRegistry", role.name()); + } + pattern.setRole(role); + this.patternByRole.put(role, pattern); + this.roleByPattern.put(pattern, role); + } + + /** + * Sets the representing node of the associated graph pattern. + * @param representingNode the representing node + */ + public void setRepresentingNode(NodePattern representingNode) { + this.representingNode = (NodePattern) representingNode; + } + + /** + * Gets the {@link NodePattern} of the associated {@link GraphPattern} that is marked representative. + * @return the representative node pattern + */ + public NodePattern getRepresentingNode() { + return this.representingNode; + } + + /** + * Creates a new Role for a wildcard parent pattern. + * @return the role + */ + public Role createWildcardRole() { + return new Role(WILDCARD_PARENT_ID + wildcardCounter++); + } + + /** + * Determines whether a pattern with the given role is present in this {@link PatternRegistry}. + * @param role the role + * @return true iff the pattern is in the registry + */ + public boolean containsPattern(Role role) { + return patternByRole.containsKey(role); + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/PatternUtil.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/PatternUtil.java new file mode 100644 index 0000000000..01c8a0453b --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/PatternUtil.java @@ -0,0 +1,272 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference; +import de.jplag.java_cpg.transformation.TransformationException; +import de.jplag.java_cpg.transformation.matching.edges.CpgAttributeEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgNthEdge; + +/** + * Contains convenience methods to create elements of {@link GraphPattern}s and {@link NodePattern}s. + */ +public class PatternUtil { + + private PatternUtil() { + // should not be instantiated + } + + /** + * Creates a Predicate that checks if a node has a non-null related Node via the given edge. + * @param edge the edge + * @param the source node type + * @param the target node type + * @return the predicate + */ + public static Predicate notNull(CpgEdge edge) { + return s -> !Objects.isNull(edge.getRelated(s)); + } + + /** + * Creates a {@link Predicate} property for an edge that specifies that its target shall not be null. + * @param edge the edge + * @param the source node type + * @param

the target property type + * @return the predicate + */ + public static Predicate notNull(CpgAttributeEdge edge) { + return s -> !Objects.isNull(edge.getter().apply(s)); + } + + /** + * Creates a {@link Predicate} property for an edge that specifies that it target shall not be an instant of the given + * type. + * @param edge the edge + * @param clazz the concrete node class + * @param the source node type + * @param the target node type as specified by the edge + * @param the concrete target node type + * @return the predicate + */ + public static Predicate notInstanceOf(CpgEdge edge, Class clazz) { + return s -> !clazz.isInstance(edge.getter().apply(s)); + } + + /** + * Creates a proxy for the nth element of a 1:n relation. + * @param edge the 1:n relation edge + * @param n the index of the edge + * @param the source node type + * @param the target node type + * @return the nth edge + */ + public static CpgEdge nthElement(CpgMultiEdge edge, int n) { + return new CpgNthEdge<>(edge, n); + } + + /** + * Creates a {@link Predicate} that checks if the related attribute is equal to the given value. + * @param attributeEdge a function to get the related attribute + * @param value the value to check against + * @param the source node type + * @param

the predicate type + * @return the predicate + */ + public static Predicate attributeEquals(CpgAttributeEdge attributeEdge, P value) { + return t -> Objects.equals(attributeEdge.get(t), value); + } + + /** + * Creates a predicate property for an edge that specifies that its target attribute shall be equal to the given String. + * @param attributeEdge the attribute edge + * @param value the required value + * @param the source node type + * @param

the attribute type + * @return the predicate + */ + public static Predicate attributeToStringEquals(CpgAttributeEdge attributeEdge, String value) { + return t -> Objects.equals(attributeEdge.get(t).toString(), value); + } + + /** + * Creates a {@link Predicate} property for an edge that specifies that the target attribute as a {@link String} starts + * with the given {@link String}. + * @param attributeEdge the attribute edge + * @param value the required starting substring + * @param the source node type + * @param

the target attribute type + * @return the predicate + */ + public static Predicate attributeToStringStartsWith(CpgAttributeEdge attributeEdge, String value) { + return t -> attributeEdge.get(t).toString().startsWith(value); + } + + /** + * Creates a {@link Predicate} property for an edge that specifies that the target attribute list contains the given + * value. + * @param attributeEdge the attribute edge + * @param value the required contained value + * @param the source node type + * @param

The target attributes type + * @return the predicate + */ + public static Predicate attributeContains(CpgAttributeEdge> attributeEdge, P value) { + return t -> attributeEdge.get(t).contains(value); + } + + /** + * Creates a predicate property for an edge that specifies that the target node list is not empty. + * @param edge the edge + * @param the source node type + * @param the target node type + * @return the predicate + */ + public static Predicate notEmpty(CpgMultiEdge edge) { + return t -> !edge.getAllTargets(t).isEmpty(); + } + + /** + * Creates a {@link Predicate} property for an edge that specifies that the target node list is empty. + * @param edge the edge + * @param the source node type + * @param the target node type + * @return the predicate + */ + public static Predicate isEmpty(CpgMultiEdge edge) { + return t -> edge.getAllTargets(t).isEmpty(); + } + + /** + * Creates a {@link Predicate} that checks if the 1:n relation targets exactly the number of nodes specified. + * @param edge the 1:n edge + * @param n the number to check against + * @param the source node type + * @param the target node type + * @return the predicate + */ + public static Predicate nElements(CpgMultiEdge edge, int n) { + return t -> edge.getAllTargets(t).size() == n; + } + + /** + * Creates a {@link Predicate} that checks if the property value is contained in the list of values given. + * @param getter a function to get the property value + * @param acceptedValues a list of accepted values + * @param the source node type + * @param

the property value type + * @return the predicate + */ + public static Predicate oneOf(CpgAttributeEdge getter, List

acceptedValues) { + return t -> acceptedValues.contains(getter.get(t)); + } + + /** + * Creates a new {@link Node} of the type specified by the given {@link NodePattern}. + * @param pattern the pattern + * @param the node pattern type + * @return the new {@link Node} + * @throws TransformationException if instantiation fails + */ + public static R instantiate(NodePattern pattern) { + try { + // every Node type has a constructor without parameters + return pattern.getRootClass().getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new TransformationException(e); + } + } + + /** + * Computes the list of {@link Node}s that may influence the given {@link Node}'s value. + * @param node the node + * @return the nodes with data flow dependencies towards the given node + */ + public static Set dfgReferences(Node node) { + LinkedList workList = new LinkedList<>(List.of(node)); + Set references = new HashSet<>(); + while (!workList.isEmpty()) { + Node candidate = workList.pop(); + + switch (candidate) { + case Literal ignored -> { + // Literal is constant + } + case null -> { + // do nothing + } + case CallExpression call -> { + workList.addAll(call.getArguments()); + if (call instanceof MemberCallExpression memberCall) { + workList.add(memberCall.getBase()); + } + } + case Reference ref -> references.add(ref.getRefersTo()); + default -> workList.addAll(candidate.getPrevDFG()); + } + } + return references.stream().flatMap(decl -> decl.getPrevDFG().stream()).collect(Collectors.toSet()); + } + + /** + * Creates a {@link Predicate} property that specifies that the given expression is constant. + * @return the predicate + */ + public static Predicate isConstant() { + return expression -> expression instanceof Literal; + } + + /** + * Creates a {@link Predicate} property that specifies that the given {@link Expression} is a field reference. + * @return the predicate + */ + public static Predicate isFieldReference() { + return expression -> { + if (!(expression instanceof MemberExpression fieldAccess)) { + return false; + } + if (!(fieldAccess.getBase() instanceof Reference thisOrClassReference)) + return false; + return thisOrClassReference.getName().toString().equals("this") || thisOrClassReference.getRefersTo() instanceof RecordDeclaration; + }; + } + + /** + * Creates a {@link Predicate} property that specifies that either of the given predicates must hold. + * @param predicate1 the first predicate + * @param predicate2 the second predicate + * @param the target node type + * @return the predicate + */ + public static Predicate or(Predicate predicate1, Predicate predicate2) { + return t -> predicate1.test(t) || predicate2.test(t); + } + + /** + * Computes a brief description for a {@link Node}. + * @param node the node + * @return the description + */ + public static String desc(Node node) { + node.getName(); + return "%s(%s%s)".formatted(node.getClass().getSimpleName(), + node.getName().getLocalName().isEmpty() ? "" : "\"" + node.getName().getLocalName() + "\", ", + Objects.requireNonNullElse(node.getLocation(), "")); + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/SimpleGraphPattern.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/SimpleGraphPattern.java new file mode 100644 index 0000000000..cbc05185d8 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/SimpleGraphPattern.java @@ -0,0 +1,86 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.TransformationException; + +/** + * A {@link SimpleGraphPattern} describes the occurrence and relation of {@link Node}s in a Graph and their properties. + * A SimpleGraphPattern has exactly one root {@link NodePattern}. + * @param the root {@link Node} type of the graph pattern + */ +public class SimpleGraphPattern extends GraphPatternImpl { + + private final NodePattern root; + + /** + * Creates a new {@link SimpleGraphPattern} with the given root {@link NodePattern}. + * @param root the root {@link NodePattern} + * @param patterns the {@link PatternRegistry} for this graph pattern + */ + public SimpleGraphPattern(NodePattern root, PatternRegistry patterns) { + super(patterns); + this.root = root; + if (Objects.isNull(representingNode)) { + representingNode = root; + } + } + + /** + * Gets the root {@link NodePattern} of the {@link SimpleGraphPattern}. + * @return the root + */ + public NodePattern getRoot() { + return root; + } + + /** + * Checks this {@link SimpleGraphPattern} against the given concrete {@link Node} for {@link Match}es. + * @param rootCandidate the possible root {@link Node} of {@link Match}es + * @param a C class + * @return the list of {@link Match}es found + */ + public List recursiveMatch(C rootCandidate) { + List matches = new ArrayList<>(); + matches.add(new Match(this)); + root.recursiveMatch(rootCandidate, matches); + return matches; + } + + @Override + public void compareTo(GraphPattern targetPattern, BiConsumer, NodePattern> compareFunction) { + if (!(targetPattern instanceof SimpleGraphPattern tTarget && Objects.equals(root.getClass(), tTarget.root.getClass()))) { + throw new TransformationException( + "Invalid Transformation: SimpleGraphPattern %s is incompatible with %s".formatted(this.toString(), targetPattern.toString())); + } + compareFunction.accept(this.root, tTarget.getRoot()); + } + + @Override + public List> getRoots() { + return List.of((NodePattern) root); + } + + @Override + public List match(Map, List> rootCandidates) { + List resultMatches = new ArrayList<>(); + for (Node rootCandidate : rootCandidates.get(root)) { + List matches = recursiveMatch(rootCandidate); + resultMatches.addAll(matches); + } + return resultMatches; + } + + @Override + public boolean validate(Match match) { + Node rootCandidate = match.get(this.root); + List matches = recursiveMatch(rootCandidate); + return matches.stream().anyMatch(match::equals); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/WildcardGraphPattern.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/WildcardGraphPattern.java new file mode 100644 index 0000000000..e9de523bd9 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/WildcardGraphPattern.java @@ -0,0 +1,205 @@ +package de.jplag.java_cpg.transformation.matching.pattern; + +import static de.jplag.java_cpg.transformation.matching.edges.IEdge.EdgeCategory.AST; +import static de.jplag.java_cpg.transformation.matching.pattern.PatternUtil.nthElement; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.Role; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.edges.Edges; +import de.jplag.java_cpg.transformation.matching.edges.IEdge; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern.NodePatternImpl; +import de.jplag.java_cpg.transformation.matching.pattern.relation.RelatedNode; + +/** + * This class represents a pattern where the root node's parent is unknown, but involved in a transformation (e.g. the + * root node is moved/deleted). + * @param The node type of the child node of the wildcard parent + */ +public class WildcardGraphPattern extends SimpleGraphPattern { + private final ParentNodePattern wildcardParent; + + /** + * Creates a new {@link WildcardGraphPattern}. + * @param tClass The node type of the child node + * @param child The node pattern representing the child node + * @param patterns A mapping of {@link Role}s to {@link NodePattern}s. + */ + WildcardGraphPattern(Class tClass, NodePattern child, PatternRegistry patterns) { + super(new ParentNodePattern<>(tClass, child), patterns); + this.wildcardParent = (ParentNodePattern) getRoot(); + patterns.put(patterns.createWildcardRole(), wildcardParent); + } + + @Override + public List recursiveMatch(Node rootCandidate) { + // rootCandidate is actually candidate for wildcard parent patterns! + List matches = new ArrayList<>(); + matches.add(new Match(this)); + + this.wildcardParent.recursiveMatch(rootCandidate, matches); + + return matches; + } + + @Override + public boolean validate(Match match) { + Node rootCandidate = match.get(this.wildcardParent); + List matches = recursiveMatch(rootCandidate); + return matches.stream().anyMatch(match::equals); + } + + /** + * Pattern to describe the unknown AST context that a node may appear in. + * @param the child node type + */ + public static class ParentNodePattern extends NodePatternImpl { + private final NodePattern childPattern; + private final List> edgesToType; + + /** + * Creates a new {@link ParentNodePattern} for the given child {@link NodePattern}. + * @param tClass The {@link Node} type class of the child + * @param child the child node pattern + */ + public ParentNodePattern(Class tClass, NodePattern child) { + super(Node.class); + this.childPattern = child; + + Edge edge; + if (Objects.isNull(child)) { + edgesToType = null; + return; + } + + edge = new Edge<>(tClass); + this.addRelation(new RelatedNode<>(child, edge)); + edgesToType = new ArrayList<>(); + Edges.getEdgesToType(tClass, e -> edgesToType.addLast(e)); + } + + @Override + public void recursiveMatch(Node node, List matches) { + + // This node should match if it has a fitting edge and child + List resultMatches = edgesToType.stream().filter(e -> e.getSourceClass().isAssignableFrom(node.getClass())).map(e -> { + List matchesCopy = new ArrayList<>(matches.stream().map(Match::copy).toList()); + matchesCopy.forEach(match -> match.register(this, node)); + wildCardMatch(e, node, matchesCopy); + return matchesCopy; + }).flatMap(List::stream).toList(); + matches.clear(); + matches.addAll(resultMatches); + } + + /** + * Checks for a match of the wild card pattern starting with the parent node. + * @param The parent {@link Node} type + * @param e The edge from the parent to the child + * @param parent the parent {@link Node} + * @param matches the current set of open matches + */ + private void wildCardMatch(IEdge e, Node parent, List matches) { + T from = (T) parent; + if (e instanceof CpgEdge singleEdge) { + Node target = singleEdge.getRelated(from); + if (Objects.isNull(target)) { + // target is not part of the graph or empty + matches.clear(); + } else { + childPattern.recursiveMatch(target, matches); + matches.forEach(match -> match.resolveWildcard(this, from, singleEdge)); + } + } else if (e instanceof CpgMultiEdge multiEdge) { + List targets = multiEdge.getAllTargets(from); + var resultMatches = new ArrayList(); + for (int i = 0; i < targets.size(); i++) { + var matchesCopy = new ArrayList<>(matches.stream().map(Match::copy).toList()); + Node target = targets.get(i); + CpgEdge edge = nthElement(multiEdge, i); + childPattern.recursiveMatch(target, matchesCopy); + matchesCopy.forEach(match -> match.resolveWildcard(this, from, edge)); + resultMatches.addAll(matchesCopy); + } + matches.clear(); + matches.addAll(resultMatches); + } + } + + @Override + public List> getCandidateClasses() { + return edgesToType.stream().map(IEdge::getSourceClass).collect(Collectors.toCollection(ArrayList::new)); + } + + public NodePattern getChildPattern() { + return childPattern; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + + ParentNodePattern that = (ParentNodePattern) o; + + if (getChildPattern() != null ? !getChildPattern().equals(that.getChildPattern()) : that.getChildPattern() != null) + return false; + return Objects.equals(edgesToType, that.edgesToType); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (getChildPattern() != null ? getChildPattern().hashCode() : 0); + result = 31 * result + (edgesToType != null ? edgesToType.hashCode() : 0); + return result; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + WildcardGraphPattern that = (WildcardGraphPattern) o; + + return Objects.equals(wildcardParent, that.wildcardParent); + } + + @Override + public int hashCode() { + return wildcardParent != null ? wildcardParent.hashCode() : 0; + } + + /** + * This models a wildcard edge unknown at creation time, of which the target is an R node. + * @param the related node type + */ + public static class Edge extends CpgEdge { + + private Edge(Class tClass) { + super(null, null, AST); + this.setRelatedClass(tClass); + } + + @Override + public boolean isEquivalentTo(IEdge other) { + // Wildcard edges should always be equivalent. + return other.getClass().equals(this.getClass()); + } + + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/ForAllRelatedNode.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/ForAllRelatedNode.java new file mode 100644 index 0000000000..223efa71e9 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/ForAllRelatedNode.java @@ -0,0 +1,42 @@ +package de.jplag.java_cpg.transformation.matching.pattern.relation; + +import java.util.List; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; + +/** + * A {@link ForAllRelatedNode} describes a one-to-n relation where in a match, all candidate nodes must match the + * given pattern. + * @param the parent node type + * @param the related node type + */ +public final class ForAllRelatedNode extends OneToNRelation { + + public ForAllRelatedNode(NodePattern pattern, CpgMultiEdge edge) { + super(pattern, edge); + } + + @Override + public List getTarget(T from) { + return getEdge().getAllTargets(from); + } + + @Override + public void recursiveMatch(NodePattern pattern, T parent, List openMatches) { + List candidates = getTarget(parent); + + for (Node candidate : candidates) { + this.pattern.recursiveMatch(candidate, openMatches); + openMatches.forEach(match -> match.remove(this.pattern)); + } + } + + @Override + public String toString() { + return "ForAllRelatedNode[" + "pattern=" + pattern + ", " + "edge=" + edge + ']'; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/OneToNRelation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/OneToNRelation.java new file mode 100644 index 0000000000..1f916fb3e0 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/OneToNRelation.java @@ -0,0 +1,76 @@ +package de.jplag.java_cpg.transformation.matching.pattern.relation; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; + +/** + * This is a superclass for the multiple kinds of one-to-n relations. + * @param the target node type + * @param the related node type + */ +public sealed class OneToNRelation extends Relation> permits ForAllRelatedNode, RelatedOneToNNode { + + private final CpgMultiEdge multiEdge; + + protected OneToNRelation(NodePattern pattern, CpgMultiEdge edge) { + super(pattern, edge); + this.multiEdge = edge; + } + + @Override + public CpgMultiEdge getEdge() { + return this.multiEdge; + } + + public List getTarget(T from) { + return multiEdge.getAllTargets(from); + } + + @Override + public boolean isEquivalentTo(Relation targetRelated, boolean multipleCandidates) { + return this.edge.isEquivalentTo(targetRelated.edge) + && (!multipleCandidates || this.pattern.getRole().equals(targetRelated.pattern.getRole())); + } + + @Override + public void recursiveMatch(NodePattern pattern, T parent, List openMatches) { + List candidates = getTarget(parent); + List resultMatches = IntStream.range(0, candidates.size()).mapToObj(i -> { + R candidate = candidates.get(i); + ArrayList openMatchesCopy = openMatches.stream().map(Match::copy).map(match -> match.resolveAnyOfNEdge(pattern, this, i)) + .collect(Collectors.toCollection(ArrayList::new)); + this.pattern.recursiveMatch(candidate, openMatchesCopy); + return openMatchesCopy; + }).flatMap(List::stream).toList(); + openMatches.clear(); + openMatches.addAll(resultMatches); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + + OneToNRelation that = (OneToNRelation) o; + + return multiEdge.equals(that.multiEdge); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + multiEdge.hashCode(); + return result; + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/RelatedNode.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/RelatedNode.java new file mode 100644 index 0000000000..6e301cff62 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/RelatedNode.java @@ -0,0 +1,78 @@ +package de.jplag.java_cpg.transformation.matching.pattern.relation; + +import java.util.List; +import java.util.Objects; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; + +/** + * Pair of a node patterns of a related node and a function to get from a reference node to a candidate related node. + * @param type of the target node + * @param type of the related node + */ +public final class RelatedNode extends Relation { + + private final CpgEdge cpgEdge; + + /** + * @param pattern the patterns describing the related node + * @param edge edge to get a related node given a reference node + */ + public RelatedNode(NodePattern pattern, CpgEdge edge) { + super(pattern, edge); + this.cpgEdge = edge; + } + + @Override + public CpgEdge getEdge() { + return cpgEdge; + } + + public R getTarget(T from) { + return cpgEdge.getRelated(from); + } + + public void recursiveMatch(NodePattern pattern, T parent, List openMatches) { + R candidateNode = getTarget(parent); + + if (Objects.isNull(candidateNode)) { + openMatches.clear(); + } else { + this.pattern.recursiveMatch(candidateNode, openMatches); + } + } + + @Override + public boolean isEquivalentTo(Relation targetRelated, boolean multipleCandidates) { + return this.cpgEdge.isEquivalentTo(targetRelated.edge); + } + + @Override + public String toString() { + return "RelatedNode{%s}".formatted(pattern.toString()); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + + RelatedNode that = (RelatedNode) o; + + return cpgEdge.equals(that.cpgEdge); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + cpgEdge.hashCode(); + return result; + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/RelatedOneToNNode.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/RelatedOneToNNode.java new file mode 100644 index 0000000000..16ea6cefc7 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/RelatedOneToNNode.java @@ -0,0 +1,29 @@ +package de.jplag.java_cpg.transformation.matching.pattern.relation; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; + +/** + * Pair of a node patterns of a related node and a multi edge from a reference node to a list of candidate related + * nodes. + * @param type of the target node + * @param type of the related node + */ +public final class RelatedOneToNNode extends OneToNRelation { + + /** + * @param pattern the patterns describing the related node + * @param edge edge from a reference node to the related nodes + */ + public RelatedOneToNNode(NodePattern pattern, CpgMultiEdge edge) { + super(pattern, edge); + + } + + @Override + public String toString() { + return "RelatedOneToNNode[" + "pattern=" + pattern + ", " + "edge=" + edge + ']'; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/Relation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/Relation.java new file mode 100644 index 0000000000..af035656ff --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/matching/pattern/relation/Relation.java @@ -0,0 +1,45 @@ +package de.jplag.java_cpg.transformation.matching.pattern.relation; + +import java.util.List; +import java.util.Objects; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.matching.edges.IEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; + +public abstract sealed class Relation permits OneToNRelation, RelatedNode { + public final NodePattern pattern; + public final IEdge edge; + + protected Relation(NodePattern pattern, IEdge edge) { + this.pattern = pattern; + this.edge = edge; + } + + public IEdge getEdge() { + return edge; + } + + public abstract V getTarget(T from); + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != this.getClass()) + return false; + var that = this.getClass().cast(obj); + return Objects.equals(this.pattern, that.pattern) && Objects.equals(this.edge, that.edge); + } + + @Override + public int hashCode() { + return Objects.hash(pattern, edge); + } + + public abstract boolean isEquivalentTo(Relation targetRelated, boolean multipleCandidates); + + public abstract void recursiveMatch(NodePattern pattern, T parent, List openMatches); + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/CreateNodeOperation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/CreateNodeOperation.java new file mode 100644 index 0000000000..c0ac51b9aa --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/CreateNodeOperation.java @@ -0,0 +1,72 @@ +package de.jplag.java_cpg.transformation.operations; + +import java.util.List; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement; +import de.fraunhofer.aisec.cpg.graph.statements.CatchClause; +import de.fraunhofer.aisec.cpg.graph.statements.DoStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement; +import de.fraunhofer.aisec.cpg.graph.statements.TryStatement; +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.jplag.java_cpg.transformation.Role; +import de.jplag.java_cpg.transformation.TransformationException; +import de.jplag.java_cpg.transformation.matching.pattern.GraphPattern; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; +import de.jplag.java_cpg.transformation.matching.pattern.PatternUtil; + +/** + * Creates a new {@link Node} in the graph. Note: The new {@link Node} needs to be inserted into the graph via other + * {@link GraphOperation}s. + * @param sourceGraph the graph + * @param role the role of the new {@link Node} + * @param pattern the {@link NodePattern} representing the new {@link Node} + * @param the new {@link Node}'s type + */ +public record CreateNodeOperation(GraphPattern sourceGraph, Role role, NodePattern pattern) implements GraphOperation { + + private static final List> scopedNodeClasses = List.of(Block.class, WhileStatement.class, DoStatement.class, + AssertStatement.class, ForStatement.class, ForEachStatement.class, SwitchStatement.class, FunctionDeclaration.class, IfStatement.class, + CatchClause.class, RecordDeclaration.class, TemplateDeclaration.class, TryStatement.class, NamespaceDeclaration.class); + + @Override + public void resolveAndApply(Match match, TranslationContext ctx) { + N newNode = PatternUtil.instantiate(pattern); + match.register(pattern, newNode); + + if (scopedNodeClasses.contains(newNode.getClass())) { + ctx.getScopeManager().enterScope(newNode); + ctx.getScopeManager().leaveScope(newNode); + } + } + + @Override + public GraphOperation instantiateWildcard(Match match) { + throw new TransformationException("Cannot instantiate CreateNodeOperation"); + } + + @Override + public GraphOperation instantiateAnyOfNEdge(Match match) { + throw new TransformationException("Cannot instantiate CreateNodeOperation"); + } + + @Override + public boolean isWildcarded() { + return false; + } + + @Override + public boolean isMultiEdged() { + return false; + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/DummyNeighbor.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/DummyNeighbor.java new file mode 100644 index 0000000000..0e6eef3bdc --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/DummyNeighbor.java @@ -0,0 +1,112 @@ +package de.jplag.java_cpg.transformation.operations; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; + +/** + * A special singleton {@link Node} to serve as an intermediary neighbor for {@link Node}s while they are moved around. + */ +public class DummyNeighbor extends Node { + + private static final DummyNeighbor INSTANCE = new DummyNeighbor(); + + /** + * Maps detached nodes to the edge that they were previously a source of. + */ + private final Map>> sourceMap; + + /** + * Maps detached nodes to the edge that they were previously a target of. + */ + private final Map>> targetMap; + + private DummyNeighbor() { + sourceMap = new HashMap<>(); + targetMap = new HashMap<>(); + } + + /** + * Gets the singleton instance. + * @return the {@link DummyNeighbor} node + */ + public static DummyNeighbor getInstance() { + return INSTANCE; + } + + /** + * Adds the given {@code edge} as a former incoming edge of {@code edge.end}. + * @param edge the edge + */ + public void saveOriginalTarget(PropertyEdge edge) { + if (edge.getEnd().equals(this)) { + return; + } + targetMap.computeIfAbsent(edge.getEnd(), n -> new ArrayList<>()).add(edge); + } + + /** + * Adds the given {@code edge} as a former outgoing edge of {@code edge.start}. + * @param edge the edge + */ + public void saveOriginalSource(PropertyEdge edge) { + if (edge.getStart().equals(this)) { + return; + } + sourceMap.computeIfAbsent(edge.getStart(), n -> new ArrayList<>()).add(edge); + } + + /** + * Gets the registered former outgoing edges of the given node. + * @param origSource the node + * @return the former outgoing edges + */ + public List> getOriginalEdgeOfSource(Node origSource) { + return List.copyOf(sourceMap.getOrDefault(origSource, Collections.emptyList())); + } + + /** + * Gets the registered former incoming edges of the given node. + * @param origTarget the node + * @return the former incoming edges + */ + public List> getOriginalEdgeOfTarget(Node origTarget) { + return List.copyOf(targetMap.getOrDefault(origTarget, Collections.emptyList())); + } + + /** + * Clears the registered former incoming edges of the given node. + * @param target the node + */ + public void clearOriginalEdgesOfTarget(Node target) { + List> edges = targetMap.getOrDefault(target, List.of()); + if (!edges.isEmpty()) { + edges.clear(); + } + } + + /** + * Clears the saved edges. + */ + public void clear() { + targetMap.clear(); + sourceMap.clear(); + } + + @Override + public boolean equals(@Nullable Object other) { + return other == INSTANCE; + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/GraphOperation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/GraphOperation.java new file mode 100644 index 0000000000..81b2789e8b --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/GraphOperation.java @@ -0,0 +1,57 @@ +package de.jplag.java_cpg.transformation.operations; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; +import de.jplag.java_cpg.transformation.GraphTransformation; +import de.jplag.java_cpg.transformation.TransformationException; +import de.jplag.java_cpg.transformation.matching.edges.AnyOfNEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgMultiEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgNthEdge; +import de.jplag.java_cpg.transformation.matching.edges.IEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.Match.WildcardMatch; +import de.jplag.java_cpg.transformation.matching.pattern.WildcardGraphPattern.Edge; +import de.jplag.java_cpg.transformation.matching.pattern.WildcardGraphPattern.ParentNodePattern; + +/** + * A {@link GraphOperation} is an arbitrary modification on a Graph and the basic unit of a {@link GraphTransformation}. + */ +public interface GraphOperation { + /** + * Applies the {@link GraphOperation} on the graph represented by a {@link Match} indicating which nodes are involved in + * the operation. + * @param match the pattern match + * @param ctx the translation context + * @throws TransformationException if the graph is malformed. + */ + void resolveAndApply(Match match, TranslationContext ctx) throws TransformationException; + + /** + * If the target nodes of this {@link GraphOperation} is a {@link ParentNodePattern}, then this method creates a + * concrete {@link GraphOperation} with the given {@link Node} and {@link IEdge} from the {@link WildcardMatch}. + * @param match The {@link WildcardMatch} containing the concrete {@link Node} and {@link PropertyEdge} for the wildcard + * @return The instantiated {@link GraphOperation} + */ + GraphOperation instantiateWildcard(Match match); + + /** + * Returns a copy of this {@link GraphOperation} where the relevant {@link AnyOfNEdge} is replaced with a + * {@link CpgNthEdge}. + * @param match the {@link Match} of a {@link GraphTransformation} source pattern + * @return the instantiated {@link GraphOperation} + */ + GraphOperation instantiateAnyOfNEdge(Match match); + + /** + * Determines whether this {@link GraphOperation} is wildcarded. + * @return true iff this {@link GraphOperation} involves a {@link Edge} that needs to be instantiated. + */ + boolean isWildcarded(); + + /** + * Determines whether this {@link GraphOperation} is multi-edged. + * @return true iff this {@link GraphOperation} involves a {@link CpgMultiEdge}. + */ + boolean isMultiEdged(); +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/GraphOperationImpl.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/GraphOperationImpl.java new file mode 100644 index 0000000000..72fa4e15e6 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/GraphOperationImpl.java @@ -0,0 +1,40 @@ +package de.jplag.java_cpg.transformation.operations; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.jplag.java_cpg.transformation.matching.edges.AnyOfNEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; +import de.jplag.java_cpg.transformation.matching.pattern.WildcardGraphPattern; + +/** + * This class stores method implementations common to all types of {@link GraphOperation}s. + * @param The type of the parent node where this GraphOperation happens + * @param The type of node related to the parent node + */ +public abstract class GraphOperationImpl implements GraphOperation { + + protected final NodePattern parentPattern; + protected final CpgEdge edge; + + /** + * Creates a new GraphOperationImpl. + * @param parentPattern the {@link NodePattern} where the {@link GraphOperation} sets in + * @param edge the {@link CpgEdge} that this {@link GraphOperation} manipulates + */ + protected GraphOperationImpl(NodePattern parentPattern, CpgEdge edge) { + this.parentPattern = parentPattern; + this.edge = edge; + } + + @Override + public boolean isMultiEdged() { + return this.edge instanceof AnyOfNEdge; + } + + @Override + public boolean isWildcarded() { + return this.parentPattern instanceof WildcardGraphPattern.ParentNodePattern && this.edge instanceof WildcardGraphPattern.Edge; + } + + public abstract GraphOperationImpl fromWildcardMatch(NodePattern pattern, CpgEdge edge); +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/InsertOperation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/InsertOperation.java new file mode 100644 index 0000000000..950dcf7af4 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/InsertOperation.java @@ -0,0 +1,134 @@ +package de.jplag.java_cpg.transformation.operations; + +import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.edge.Properties; +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; +import de.fraunhofer.aisec.cpg.graph.scopes.Scope; +import de.jplag.java_cpg.transformation.TransformationException; +import de.jplag.java_cpg.transformation.matching.edges.AnyOfNEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgNthEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; + +/** + * Inserts the target {@link Node} into a collection of other child nodes of the parent {@link Node}. + * @param type of the parentPattern node, defined by the edge + * @param type of the target node, defined by the edge + */ +public final class InsertOperation extends GraphOperationImpl { + + private static final Logger logger; + private static final String WILDCARD_ERROR_MESSAGE = "Cannot apply InsertOperation with WildcardGraphPattern.ParentPattern as parentPattern. Use a surrounding Block instead."; + + static { + logger = LoggerFactory.getLogger(InsertOperation.class); + } + + private final CpgNthEdge nthEdge; + private final NodePattern newChildPattern; + private final boolean connectEog; + + /** + * Creates a new {@link InsertOperation}. + * @param parentPattern source node of the edge + * @param nthEdge edge where an element shall be inserted + * @param newChildPattern node to be inserted + * @param connectEog if true, the new element will be connected to its neighbor elements in the EOG graph + */ + public InsertOperation(NodePattern parentPattern, CpgNthEdge nthEdge, NodePattern newChildPattern, + boolean connectEog) { + super(parentPattern, nthEdge); + this.nthEdge = nthEdge; + this.newChildPattern = newChildPattern; + this.connectEog = connectEog; + } + + @Override + public GraphOperationImpl fromWildcardMatch(NodePattern pattern, CpgEdge edge) { + throw new TransformationException(WILDCARD_ERROR_MESSAGE); + } + + @Override + public void resolveAndApply(Match match, TranslationContext ctx) { + T parent = match.get(parentPattern); + // match should contain newChildPattern node because of Builder.createNewNodes() + R newTarget = match.get(newChildPattern); + int index = nthEdge.getIndex(); + logger.debug("Insert {} into {} at position #{}}", newTarget, parent, index); + + apply(ctx, parent, newTarget, index); + + } + + /** + * Applies the InsertOperation on the given {@link Node}s. + * @param ctx the translation context + * @param parent the parent node + * @param newTarget the new child node + * @param index the insertion index + */ + public void apply(TranslationContext ctx, T parent, R newTarget, int index) { + PropertyEdge newEdge = new PropertyEdge<>(parent, newTarget); + newEdge.addProperty(Properties.INDEX, index); + + // Set AST edge + List> edges = nthEdge.getMultiEdge().getAllEdges(parent); + edges.add(index, newEdge); + IntStream.range(index, edges.size()).forEach(i -> edges.get(i).addProperty(Properties.INDEX, i + 1)); + + Scope parentScope = Objects.requireNonNullElse(ctx.getScopeManager().lookupScope(parent), parent.getScope()); + newTarget.setScope(parentScope); + Scope childScope = ctx.getScopeManager().lookupScope(newTarget); + if (!Objects.isNull(childScope)) { + childScope.setParent(parentScope); + } + + if (!connectEog) { + return; + } + + if (0 == index && edges.size() > 1) { + // successor exists + R previouslyFirst = edges.get(index + 1).getEnd(); + TransformationUtil.transferEogPredecessor(previouslyFirst, newTarget); + TransformationUtil.insertBefore(newTarget, previouslyFirst); + } else if (0 < index && index < edges.size() - 1) { + R successor = edges.get(index + 1).getEnd(); + TransformationUtil.transferEogPredecessor(successor, newTarget); + R predecessor = edges.get(index - 1).getEnd(); + TransformationUtil.transferEogSuccessor(predecessor, newTarget); + TransformationUtil.insertBefore(newTarget, successor); + } else if (index == edges.size() - 1 && edges.size() > 1) { + // predecessor exists + R previouslyLast = edges.get(index - 1).getEnd(); + TransformationUtil.transferEogSuccessor(previouslyLast, newTarget); + TransformationUtil.insertAfter(newTarget, previouslyLast); + + } + } + + @Override + public GraphOperation instantiateWildcard(Match match) { + throw new TransformationException(WILDCARD_ERROR_MESSAGE); + } + + @Override + public GraphOperation instantiateAnyOfNEdge(Match match) { + AnyOfNEdge anyOfNEdge = (AnyOfNEdge) nthEdge; + CpgNthEdge edge1 = match.getEdge(this.parentPattern, anyOfNEdge); + if (Objects.isNull(edge1)) { + edge1 = new CpgNthEdge<>(anyOfNEdge.getMultiEdge(), anyOfNEdge.getMinimalIndex()); + } + return new InsertOperation<>(parentPattern, edge1, newChildPattern, this.connectEog); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/RemoveOperation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/RemoveOperation.java new file mode 100644 index 0000000000..5225442bbd --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/RemoveOperation.java @@ -0,0 +1,122 @@ +package de.jplag.java_cpg.transformation.operations; + +import java.util.List; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.edge.Properties; +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; +import de.jplag.java_cpg.transformation.TransformationException; +import de.jplag.java_cpg.transformation.matching.edges.AnyOfNEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgNthEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; +import de.jplag.java_cpg.transformation.matching.pattern.WildcardGraphPattern; + +/** + * This operation removes a {@link Node} from its AST context. + * @param the parent {@link Node} type + * @param the target {@link Node} type + */ +public final class RemoveOperation extends GraphOperationImpl { + + private final boolean disconnectEog; + + /** + * Creates a new {@link RemoveOperation}. + * @param sourcePattern The source pattern of which a related node shall be removed + * @param edge the edge + * @param disconnectEog if true, the target node is disconnected in the EOG graph + */ + public RemoveOperation(NodePattern sourcePattern, CpgEdge edge, boolean disconnectEog) { + super(sourcePattern, edge); + this.disconnectEog = disconnectEog; + if (Objects.isNull(sourcePattern) || Objects.isNull(edge)) { + throw new TransformationException("Invalid RemoveOperation: the pattern root needs to be wrapped into a WildcardParentPattern."); + } + } + + private static final Logger logger; + + static { + logger = LoggerFactory.getLogger(RemoveOperation.class); + + } + + @Override + public void resolveAndApply(Match match, TranslationContext ctx) { + T parent = match.get(parentPattern); + R element = edge.getter().apply(parent); + apply(parent, element, edge, disconnectEog); + } + + /** + * Applies a {@link RemoveOperation} to the given nodes. + * @param the target node type + * @param the target node type + * @param target the target node + * @param child the related node + * @param edge the edge + * @param disconnectEog if true, the target node will be disconnected from the EOG graph + */ + public static void apply(T target, R child, CpgEdge edge, boolean disconnectEog) { + + if (!(edge instanceof CpgNthEdge nthEdge)) { + logger.debug("Remove {}", child); + edge.setter().accept(target, null); + } else if (nthEdge.getMultiEdge().isEdgeValued()) { + logger.debug("Remove {}} (Element no. {} of {})", child, nthEdge.getIndex(), target); + // set edge indices of successors + List> siblingEdges = nthEdge.getMultiEdge().getAllEdges(target); + int index = nthEdge.getIndex(); + + // remove edge + siblingEdges.remove(siblingEdges.get(index)); + + for (int i = index; i <= siblingEdges.size() - 1; i++) { + PropertyEdge sibling = siblingEdges.get(i); + sibling.addProperty(Properties.INDEX, i); + siblingEdges.set(i, sibling); + } + + } else { + // nthEdge is node-valued + List siblings = nthEdge.getMultiEdge().getAllTargets(target); + siblings.remove(child); + } + + if (!disconnectEog) { + return; + } + + List predExits = TransformationUtil.disconnectFromPredecessor(child); + Node succEntry = TransformationUtil.disconnectFromSuccessor(child); + + if (!Objects.isNull(succEntry) && succEntry != DummyNeighbor.getInstance()) { + predExits.forEach(exit -> TransformationUtil.connectNewSuccessor(exit, succEntry, false)); + } + } + + @Override + public GraphOperation instantiateWildcard(Match match) { + WildcardGraphPattern.ParentNodePattern wcParent = (WildcardGraphPattern.ParentNodePattern) this.parentPattern; + return match.instantiateGraphOperation(wcParent, this); + + } + + public RemoveOperation fromWildcardMatch(NodePattern pattern, CpgEdge edge) { + return new RemoveOperation<>(pattern, edge, this.disconnectEog); + } + + @Override + public GraphOperation instantiateAnyOfNEdge(Match match) { + AnyOfNEdge anyOfNEdge = (AnyOfNEdge) edge; + return new RemoveOperation<>(parentPattern, match.getEdge(this.parentPattern, anyOfNEdge), this.disconnectEog); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/ReplaceOperation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/ReplaceOperation.java new file mode 100644 index 0000000000..ad8cdcb7fa --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/ReplaceOperation.java @@ -0,0 +1,93 @@ +package de.jplag.java_cpg.transformation.operations; + +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.scopes.Scope; +import de.jplag.java_cpg.transformation.matching.edges.AnyOfNEdge; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; +import de.jplag.java_cpg.transformation.matching.pattern.WildcardGraphPattern; + +/** + * Replaces the target {@link Node} of an edge by another {@link Node}. + * @param type of the target node, defined by the edge + * @param type of the related node, defined by the edge + */ +public final class ReplaceOperation extends GraphOperationImpl { + + private static final Logger logger; + + static { + logger = LoggerFactory.getLogger(ReplaceOperation.class); + } + + private final NodePattern newChildPattern; + private final boolean disconnectEog; + + /** + * Constructs a new ReplaceOperation. + * @param parentPattern source node of the edge + * @param edge edge of which the target shall be replaced + * @param newChildPattern replacement node + * @param disconnectEog if true, the replaced element is inserted into the EOG graph at the target + */ + public ReplaceOperation(NodePattern parentPattern, CpgEdge edge, NodePattern newChildPattern, + boolean disconnectEog) { + super(parentPattern, edge); + this.newChildPattern = newChildPattern; + this.disconnectEog = disconnectEog; + } + + /** {@inheritDoc} */ + @Override + public void resolveAndApply(Match match, TranslationContext ctx) { + T parent = match.get(parentPattern); + // match should contain newChildPattern node because of Builder.createNewNodes() + R newTarget = match.get(newChildPattern); + + // Replace AST edge + R oldTarget = edge.getter().apply(parent); + logger.debug("Replace {} by {}", oldTarget, newTarget); + if (Objects.isNull(newTarget.getLocation())) { + newTarget.setLocation(oldTarget.getLocation()); + } + edge.setter().accept(parent, newTarget); + + Scope parentScope = Objects.requireNonNullElse(ctx.getScopeManager().lookupScope(parent), parent.getScope()); + newTarget.setScope(parentScope); + Scope childScope = ctx.getScopeManager().lookupScope(newTarget); + if (!Objects.isNull(childScope)) { + childScope.setParent(parentScope); + } + + if (!disconnectEog) { + return; + } + + TransformationUtil.transferEogPredecessor(oldTarget, newTarget); + TransformationUtil.transferEogSuccessor(oldTarget, newTarget); + } + + @Override + public GraphOperation instantiateWildcard(Match match) { + WildcardGraphPattern.ParentNodePattern wcParent = (WildcardGraphPattern.ParentNodePattern) this.parentPattern; + return match.instantiateGraphOperation(wcParent, this); + } + + public ReplaceOperation fromWildcardMatch(NodePattern pattern, CpgEdge edge) { + return new ReplaceOperation<>(pattern, edge, this.newChildPattern, this.disconnectEog); + } + + @Override + public GraphOperation instantiateAnyOfNEdge(Match match) { + AnyOfNEdge any1ofNEdge = (AnyOfNEdge) edge; + return new ReplaceOperation<>(parentPattern, match.getEdge(this.parentPattern, any1ofNEdge), newChildPattern, this.disconnectEog); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/SetOperation.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/SetOperation.java new file mode 100644 index 0000000000..a2bf637809 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/SetOperation.java @@ -0,0 +1,84 @@ +package de.jplag.java_cpg.transformation.operations; + +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.scopes.Scope; +import de.jplag.java_cpg.transformation.TransformationException; +import de.jplag.java_cpg.transformation.matching.edges.CpgEdge; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.NodePattern; +import de.jplag.java_cpg.transformation.matching.pattern.WildcardGraphPattern; + +/** + * Sets the target {@link Node} of a previously newly created edge to a {@link Node}. + * @param type of the parent node, defined by the edge + * @param type of the related node, defined by the edge + */ +public final class SetOperation extends GraphOperationImpl { + private static final Logger logger; + public static final String WILDCARD_ERROR_MESSAGE = "Cannot apply SetOperation with WildcardGraphPattern.ParentPattern as parentPattern."; + public static final String MULTI_EDGE_ERROR_MESSAGE = "Cannot apply SetOperation with Any1ofNEdge."; + private final NodePattern newChildPattern; + + /** + * Creates a new {@link SetOperation}. + * @param parentPattern the parent pattern of which a child shall be set + * @param edge the edge relating the parent and child + * @param newChildPattern the new child node pattern + */ + public SetOperation(NodePattern parentPattern, CpgEdge edge, NodePattern newChildPattern) { + super(parentPattern, edge); + this.newChildPattern = newChildPattern; + } + + static { + logger = LoggerFactory.getLogger(SetOperation.class); + } + + @Override + public GraphOperationImpl fromWildcardMatch(NodePattern pattern, CpgEdge edge) { + throw new TransformationException(WILDCARD_ERROR_MESSAGE); + } + + @Override + public void resolveAndApply(Match match, TranslationContext ctx) { + T parent = match.get(parentPattern); + // match should contain newChildPattern node because of Builder.createNewNodes() + R newChild = match.get(newChildPattern); + logger.debug("Set {} as AST child of {}", newChild, parent); + + assert Objects.isNull(edge.getter().apply(parent)); + edge.setter().accept(parent, newChild); + + Scope parentScope = Objects.requireNonNullElse(ctx.getScopeManager().lookupScope(parent), parent.getScope()); + newChild.setScope(parentScope); + Scope childScope = ctx.getScopeManager().lookupScope(newChild); + if (!Objects.isNull(childScope)) { + childScope.setParent(parentScope); + } + + // Here, the EOG would be connected. Yet, this would be very hard because we do not have any EOG neighbors to + // get hold onto. So, better leave SetOperations in the AstTransformationPass and let the EvaluationOrderGraphPass + // create the EOG for us. + } + + @Override + public GraphOperation instantiateWildcard(Match match) { + if (!(this.parentPattern instanceof WildcardGraphPattern.ParentNodePattern)) { + return this; + } + + throw new TransformationException(WILDCARD_ERROR_MESSAGE); + } + + @Override + public GraphOperation instantiateAnyOfNEdge(Match match) { + throw new TransformationException(MULTI_EDGE_ERROR_MESSAGE); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/TransformationUtil.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/TransformationUtil.java new file mode 100644 index 0000000000..0e72df2a38 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/transformation/operations/TransformationUtil.java @@ -0,0 +1,362 @@ +package de.jplag.java_cpg.transformation.operations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator; +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker; +import de.jplag.java_cpg.transformation.GraphTransformation; + +/** + * This class is a collection of auxiliary methods related to {@link GraphTransformation}s. + */ +public final class TransformationUtil { + + private static final Logger logger = LoggerFactory.getLogger(TransformationUtil.class); + private static final DummyNeighbor DUMMY = DummyNeighbor.getInstance(); + + private TransformationUtil() { + /* should not be instantiated */ + } + + /** + * Gets the {@link SubgraphWalker.Border} of the given node's sub-AST that links to outer nodes via EOG edges. + * @param astRoot the root of the sub-AST + * @return the EOG {@link SubgraphWalker.Border} of the AST + */ + @NotNull + public static SubgraphWalker.Border getEogBorders(@NotNull Node astRoot) { + SubgraphWalker.Border result; + if (astRoot instanceof Block block && !block.getStatements().isEmpty() && block.getNextEOG().isEmpty() && block.getPrevEOG().isEmpty()) { + result = new SubgraphWalker.Border(); + SubgraphWalker.Border firstStatementBorder = getEogBorders(block.get(0)); + result.setEntries(firstStatementBorder.getEntries()); + SubgraphWalker.Border lastStatementBorder = getEogBorders(block.get(block.getStatements().size() - 1)); + result.setExits(lastStatementBorder.getExits()); + } else { + result = SubgraphWalker.INSTANCE.getEOGPathEdges(astRoot); + if (result.getEntries().isEmpty()) { + Node entry = astRoot; + while (!entry.getPrevEOG().isEmpty()) + entry = entry.getPrevEOG().getFirst(); + result.setEntries(List.of(entry)); + } + if (result.getExits().isEmpty()) { + Node exit = astRoot; + while (!exit.getNextEOG().isEmpty()) + exit = exit.getNextEOG().getFirst(); + result.setExits(List.of(exit)); + } + } + checkBorder(astRoot, result); + return result; + } + + private static void checkBorder(Node astRoot, SubgraphWalker.Border result) { + if (result.getEntries().isEmpty()) { + logger.debug("AST subtree of {} has no EOG entry", astRoot); + } else if (result.getEntries().size() > 1) { + logger.debug("AST subtree of {} has multiple EOG entries", astRoot); + } + } + + /** + * Checks if the given {@link Node} {@code maybeChild} is contained in the sub-AST with root {@code astRoot}. + * @param astRoot the root of the sub-AST + * @param maybeChild the node to check + * @return true if {@code maybeChild} is contained in the sub-AST rooted at {@code astRoot} + */ + static boolean isAstChild(Node astRoot, Node maybeChild) { + return SubgraphWalker.INSTANCE.flattenAST(astRoot).contains(maybeChild); + } + + static void transferEogSuccessor(Node otherPredecessor, Node predecessor) { + disconnectFromSuccessor(predecessor); + Node otherEntry = disconnectFromSuccessor(otherPredecessor); + Node exit = getExit(predecessor); + connectNewSuccessor(exit, otherEntry, true); + } + + static void transferEogPredecessor(Node oldSuccessor, Node newSuccessor) { + List exits = disconnectFromPredecessor(oldSuccessor); + disconnectFromPredecessor(newSuccessor); + exits.forEach(exit -> connectNewSuccessor(exit, newSuccessor, true)); + } + + /** + * Disconnects the given {@link Node} from its EOG successor. + * @param node the node + * @return the EOG successor + */ + public static Node disconnectFromSuccessor(Node node) { + Node exit = getExit(node); + List> exitEdges = new ArrayList<>(getExitEdges(node, List.of(exit), true)); + + exitEdges.removeIf(e -> Objects.equals(e.getEnd(), DUMMY)); + exitEdges.removeIf(e -> Objects.equals(e.getStart(), DUMMY)); + + if (exitEdges.isEmpty()) + return null; + + Node entry = exitEdges.getFirst().getEnd(); + exitEdges.forEach(e -> { + PropertyEdge dummyEdge = new PropertyEdge<>(e); + DUMMY.saveOriginalTarget(e); + + int index = entry.getPrevEOGEdges().indexOf(e); + e.setEnd(DUMMY); + DUMMY.addPrevEOG(e); + + DUMMY.saveOriginalSource(dummyEdge); + dummyEdge.setStart(DUMMY); + DUMMY.addNextEOG(dummyEdge); + entry.getPrevEOGEdges().set(index, dummyEdge); + }); + return entry; + } + + /** + * Connects the EOG exit edges of the given target {@link Node} to the entries of the newSuccessor {@link Node}. + * @param target the node to be connected + * @param newSuccessor the node to be connected to + * @param enforceEogConnection if true, the two nodes will be connected even if target has no exit edges. + */ + static void connectNewSuccessor(Node target, Node newSuccessor, boolean enforceEogConnection) { + List exits = List.of(target); + List> exitEdges = getExitEdges(target, exits, false); + + if (Objects.isNull(newSuccessor) || target == DUMMY) { + return; + } + if (target instanceof UnaryOperator unaryOperator && Objects.equals(unaryOperator.getOperatorCode(), "throw")) { + target.clearNextEOG(); + return; + } + + if (exitEdges.isEmpty()) { + if (enforceEogConnection) { + PropertyEdge exitEdge = new PropertyEdge<>(target, DUMMY); + target.addNextEOG(exitEdge); + exitEdges = List.of(exitEdge); + } else { + return; + } + } + exitEdges = exitEdges.stream().filter(e -> e.getEnd().equals(DUMMY)).toList(); + assert !exitEdges.isEmpty(); + Node entry = getEntry(newSuccessor); + + getEntryEdges(newSuccessor, entry, false).forEach(e -> { + e.getStart().getNextEOGEdges().remove(e); + entry.getPrevEOGEdges().remove(e); + }); + + exitEdges.forEach(e -> { + DUMMY.getPrevEOGEdges().remove(e); + e.setEnd(entry); + entry.addPrevEOG(e); + }); + + } + + /** + * Disconnects the given {@link Node} from its EOG predecessor. + * @param node the node + * @return the EOG predecessor + */ + public static List disconnectFromPredecessor(Node node) { + Node entry = getEntry(node); + + List> entryEdges = getEntryEdges(node, entry, true); + if (entryEdges.isEmpty()) + return List.of(); + + List predExits = entryEdges.stream().map(PropertyEdge::getStart).toList(); + + entryEdges.stream().filter(e -> !Objects.equals(e.getStart(), DUMMY)).filter(e -> !Objects.equals(e.getEnd(), DUMMY)).forEach(e -> { + PropertyEdge dummyEdge = new PropertyEdge<>(e); + DUMMY.saveOriginalSource(e); + + e.getStart().getNextEOGEdges().remove(e); + e.setStart(DUMMY); + DUMMY.addNextEOG(e); + + DUMMY.saveOriginalTarget(dummyEdge); + dummyEdge.setEnd(DUMMY); + DUMMY.addPrevEOG(dummyEdge); + dummyEdge.getStart().addNextEOG(dummyEdge); + }); + return predExits; + } + + /** + * Gets the first EOG entry {@link Node} of the given {@link Node}. + * @param node the node + * @return the EOG entry node + */ + public static Node getEntry(Node node) { + return getEogBorders(node).getEntries().getFirst(); + } + + /** + * Gets the first EOG exit {@link Node} of the given {@link Node}. + * @param node the node + * @return the EOG entry node + */ + static Node getExit(Node node) { + return getEogBorders(node).getExits().getFirst(); + } + + static Node connectNewPredecessor(Node target, Node newPredecessor) { + Node entry = getEntry(target); + List exits = getEogBorders(newPredecessor).getExits(); + List> exitEdges = getExitEdges(target, exits, true); + + assert exitEdges.stream().allMatch(e -> e.getEnd().equals(DUMMY)); + if (exitEdges.isEmpty()) + return target; + + getEntryEdges(target, entry, true).forEach(e -> { + e.getStart().getNextEOGEdges().remove(e); + entry.getPrevEOGEdges().remove(e); + }); + + exitEdges.forEach(e -> { + DUMMY.getPrevEOGEdges().remove(e); + e.setEnd(entry); + entry.addPrevEOG(e); + }); + + return entry; + } + + /** + * Gets the EOG entry edges to the AST subtree represented by astParent, that connect to the given entry node. + * @param astParent the root of the AST subtree + * @param entry the EOG entry to the astParent + * @param useDummies if true, the returned edges may not be the current entries to the astParent, but instead earlier + * entry edges to the astParent that have been disconnected and saved for use later. + * @return the entry edges + */ + @NotNull + public static List> getEntryEdges(Node astParent, Node entry, boolean useDummies) { + List> currentEntryEdges = entry.getPrevEOGEdges().stream().filter(e -> !isAstChild(astParent, e.getStart())).toList(); + List> disconnectedEdges = currentEntryEdges.stream().filter(e -> e.getStart().equals(DUMMY)).toList(); + + List> originalEntryEdges = DUMMY.getOriginalEdgeOfTarget(entry); + + if (originalEntryEdges.isEmpty() || !useDummies) { + // Node is still in its proper place + return currentEntryEdges; + } else if (!disconnectedEdges.isEmpty()) { + // Node is disconnected + return disconnectedEdges; + } else { + // Node has been reattached; return original exit edges (and clear them) + DUMMY.clearOriginalEdgesOfTarget(entry); + return originalEntryEdges; + } + } + + /** + * Gets the EOG exit edges to the AST subtree represented by astParent, that connect to the given entry node. + * @param astParent the root of the AST subtree + * @param exits the EOG exit nodes of the astParent + * @param useDummies if true, the returned edges may not be the current exits of the astParent, but instead earlier exit + * edges of the astParent that have been disconnected and saved for use later. + * @return the exit edges + */ + @NotNull + public static List> getExitEdges(Node astParent, List exits, boolean useDummies) { + List> currentExitEdges = exits.stream().flatMap(n -> n.getNextEOGEdges().stream()) + .filter(e -> !isAstChild(astParent, e.getEnd()) || (astParent instanceof Block && e.getEnd() == astParent)).toList(); + List> disconnectedEdges = currentExitEdges.stream().filter(e -> e.getEnd().equals(DUMMY)).toList(); + + List> originalEdges = exits.stream().map(DUMMY::getOriginalEdgeOfSource).flatMap(List::stream).toList(); + if (originalEdges.isEmpty() || !useDummies) { + // Node is still in its proper place + return currentExitEdges; + } else if (!disconnectedEdges.isEmpty()) { + // Node is disconnected + return disconnectedEdges; + } else { + // Node has been reattached; return original exit edges (and clear them) + exits.forEach(DUMMY::clearOriginalEdgesOfTarget); + return originalEdges; + } + + } + + /** + * Inserts the given {@link Node} before the given newSuccessor {@link Node} in the EOG graph. + * @param target the node + * @param newSuccessor the new successor node + */ + public static void insertBefore(Node target, Node newSuccessor) { + Node entry = getEntry(target); + List exits = getEogBorders(target).getExits(); + disconnectFromPredecessor(target); + disconnectFromSuccessor(target); + + Node succEntry = getEntry(newSuccessor); + List newPreds = disconnectFromPredecessor(newSuccessor); + newPreds.forEach(pred -> connectNewSuccessor(pred, entry, false)); + exits.forEach(exit -> connectNewSuccessor(exit, succEntry, false)); + } + + /** + * Inserts the given {@link Node} after the given newSuccessor {@link Node} in the EOG graph. + * @param target the node + * @param newPredecessor the new predecessor node + */ + public static void insertAfter(Node target, Node newPredecessor) { + Node entry = getEntry(target); + Node exit = getExit(target); + disconnectFromPredecessor(target); + disconnectFromSuccessor(target); + + Node predExit = getExit(newPredecessor); + Node newSuccEntry = disconnectFromSuccessor(newPredecessor); + connectNewSuccessor(exit, newSuccEntry, false); + connectNewSuccessor(predExit, entry, false); + } + + /** + * Returns true if the {@code maybeSuccessor} {@link Node} is the AST neighbor following the {@code element} node. + * @param element a {@link Node} object + * @param maybeSuccessor the potential successor {@link Node} + * @return true if {@code maybeSuccessor} is an AST successor of {@code element} + */ + public static boolean isAstSuccessor(Node element, Node maybeSuccessor) { + // If maybeSuccessor is the AST successor of element, then an exit edge of element points to maybeSuccessor + // The exit is likely to be a child node, not element itself + List exits = getEogBorders(element).getExits(); + Node entry = getEntry(maybeSuccessor); + List> entryEdges = getEntryEdges(maybeSuccessor, entry, false); + + return entryEdges.stream().anyMatch(e -> exits.contains(e.getStart())); + } + + /** + * Returns true if the {@code maybeSuccessor} {@link Node} is the EOG successor of the {@code element} node. + * @param exit a {@link Node} object + * @param maybeSuccessor the potential successor {@link Node} + * @return true if {@code maybeSuccessor} is the EOG successor of {@code element} + */ + public static boolean isEogSuccessor(Node exit, Node maybeSuccessor) { + // Unlike isAstSuccessor, we check for edges from exit directly + Node entry = getEntry(maybeSuccessor); + List> entryEdges = getEntryEdges(maybeSuccessor, entry, false); + + return entryEdges.stream().anyMatch(e -> exit == e.getStart()); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/visitor/MethodOrderStrategy.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/visitor/MethodOrderStrategy.java new file mode 100644 index 0000000000..ada51d7cc8 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/visitor/MethodOrderStrategy.java @@ -0,0 +1,256 @@ +package de.jplag.java_cpg.visitor; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.TranslationResult; +import de.fraunhofer.aisec.cpg.graph.Name; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; +import de.fraunhofer.aisec.cpg.graph.types.ObjectType; +import de.fraunhofer.aisec.cpg.graph.types.ParameterizedType; +import de.fraunhofer.aisec.cpg.graph.types.Type; +import de.fraunhofer.aisec.cpg.graph.types.UnknownType; +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker; + +import kotlin.Unit; + +/** + * This class contains methods to put a call graph oriented order on methods. + */ +public class MethodOrderStrategy { + private static final Logger logger = LoggerFactory.getLogger(MethodOrderStrategy.class); + private final NodeOrderStrategy nodeOrderStrategy; + private int index; + private List allMethods; + + /** + * Creates a new {@link MethodOrderStrategy}. + */ + public MethodOrderStrategy() { + this.index = 0; + this.nodeOrderStrategy = new NodeOrderStrategy(); + } + + private static List getFunctionCandidates(MethodDeclaration methodDeclaration, CallExpression call) { + + RecordDeclaration recordDeclaration = methodDeclaration.getRecordDeclaration(); + if (Objects.isNull(recordDeclaration)) { + return List.of(); + } + List candidates; + if (methodDeclaration instanceof ConstructorDeclaration constructor) { + // CPG cannot handle super constructor calls at the moment + // we have to find a candidate ourselves + if (call.getCode() != null && call.getCode().startsWith("super")) { + // already filtered + return getSuperConstructorCandidates(constructor, call); + } else { + candidates = new ArrayList<>(recordDeclaration.getConstructors()); + } + } else { + candidates = recordDeclaration.getMethods().stream().filter(m -> m.getName().equals(methodDeclaration.getName())) + .collect(Collectors.toCollection(ArrayList::new)); + // add overriding methods + List.copyOf(candidates).stream().map(MethodDeclaration::getOverriddenBy).flatMap(List::stream).map(MethodDeclaration.class::cast) + .forEach(candidates::add); + } + + return candidates.stream().filter(m -> m.getLocation() != null).filter(m -> m.getBody() != null).filter(m -> hasSignature(m, call)).toList(); + } + + private static boolean hasSignature(MethodDeclaration m, CallExpression call) { + List arguments = call.getArguments(); + List parameters = m.getParameters(); + + if (arguments.size() < parameters.size()) + return false; + for (int i = 0; i < parameters.size(); i++) { + Expression argument = arguments.get(i); + ParameterDeclaration parameter = parameters.get(i); + + Type argumentType = argument.getType(); + Type parameterType = parameter.getType(); + if (!(argumentType instanceof UnknownType) && !(argumentType instanceof ParameterizedType) && !isGeneric(parameterType) + && !isTypeCompatible(argumentType, parameterType)) + return false; + + } + return true; + + } + + private static boolean isTypeCompatible(Type type, Type target) { + if (type.isSimilar(target)) + return true; + if (!(type instanceof ObjectType oType && target instanceof ObjectType oTarget && oType.getRecordDeclaration() != null + && oTarget.getRecordDeclaration() != null)) + return false; + + Name typeName = oType.getName(); + if (typeName.toString().equals("java.lang.Object") || typeName.getParent() == null) + return true; + Name targetName = oTarget.getName(); + if (targetName.toString().equals("java.lang.Object") || targetName.getParent() == null) + return true; + + List superTypes = oType.getRecordDeclaration().getSuperTypes(); + List superTargets = oTarget.getRecordDeclaration().getSuperTypes(); + + return superTypes.stream().anyMatch(t -> isTypeCompatible(t, target)) || superTargets.stream().anyMatch(t -> isTypeCompatible(type, t)); + } + + private static boolean isGeneric(Type type) { + if (type instanceof ParameterizedType) + return true; + + if (!(type instanceof ObjectType objectType)) + return false; + + // primitive types are subclasses of ObjectType + if (type.getClass() != ObjectType.class) + return false; + + // sufficient approximation: type arguments are unqualified -> no parent package + boolean isTypeArgument = Objects.isNull(objectType.getName().getParent()); + if (isTypeArgument) + return true; + + RecordDeclaration recordDeclaration = objectType.getRecordDeclaration(); + if (Objects.isNull(recordDeclaration)) + return false; + String code = recordDeclaration.getCode(); + if (Objects.isNull(code)) + return false; + OptionalInt cutOffIndex = Stream.of("{", "implements", "extends").mapToInt(code::indexOf).filter(i -> i > 0).min(); + if (cutOffIndex.isEmpty()) { + return false; + } + String firstLine = code.substring(0, cutOffIndex.getAsInt()); + // does it contain an angle bracket as a start for type parameters? + return firstLine.contains("<"); + } + + private static List getSuperConstructorCandidates(MethodDeclaration methodDeclaration, CallExpression call) { + RecordDeclaration recordDeclaration = methodDeclaration.getRecordDeclaration(); + if (recordDeclaration == null) + return List.of(); + + List superClassDeclarations = new ArrayList<>(); + superClassDeclarations.add(recordDeclaration); + do { + RecordDeclaration superClassDeclaration = superClassDeclarations.removeFirst(); + List candidates = superClassDeclaration.getConstructors().stream() + .filter(constructor -> isImplicitStandardConstructor(constructor) || constructor.getLocation() != null) + .filter(constructor -> hasSignature(constructor, call)).map(MethodDeclaration.class::cast).toList(); + if (!candidates.isEmpty()) { + return candidates; + } + Set superTypes = superClassDeclaration.getSuperTypeDeclarations(); + superClassDeclarations.addAll(superTypes); + } while (!superClassDeclarations.isEmpty()); + return List.of(); + } + + private static boolean isImplicitStandardConstructor(ConstructorDeclaration constructor) { + return constructor.getParameters().isEmpty() && constructor.getLocation() == null; + } + + private void handleNode(Node node, List callGraphIndex, List newFunctions) { + if (!(node instanceof CallExpression call)) { + return; + } + + /* + * Due to the DeclarationHandler/ExpressionHandler and JavaParser not delivering all necessary information, we may need + * to reconstruct which methods are being referred to here. + */ + List invokes = getFunctionCandidatesByName(call); + + invokes.forEach(methodDeclaration -> { + var candidates = getFunctionCandidates(methodDeclaration, call); + if (candidates.isEmpty()) { + logger.warn("No candidate for {}", call); + } + candidates.forEach(candidate -> { + if ((!callGraphIndex.contains(methodDeclaration) || callGraphIndex.indexOf(methodDeclaration) >= index) + && !newFunctions.contains(methodDeclaration)) { + newFunctions.add(methodDeclaration); + } + }); + }); + + } + + private List getFunctionCandidatesByName(CallExpression call) { + if (Objects.isNull(call.getCallee())) { + return List.of(); + } + String methodName = call.getCallee().getName().getLocalName(); + + List candidatesWithSameName = allMethods.stream().filter(method -> method.getName().getLocalName().equals(methodName)) + .toList(); + return candidatesWithSameName.stream().filter(method -> hasSignature(method, call)).toList(); + } + + List setupMethodCallGraphOrder(TranslationResult result) { + List astChildren = SubgraphWalker.INSTANCE.flattenAST(result); + List methods = astChildren.stream().filter(MethodDeclaration.class::isInstance).map(MethodDeclaration.class::cast) + .toList(); + this.allMethods = methods; + + List mainMethods = methods.stream().filter(NodeOrderStrategy::isMainMethod).toList(); + List> indices = new ArrayList<>(); + mainMethods.forEach(mainMethod -> { + List functionIndex = traverseCallGraph(mainMethod); + indices.add(functionIndex); + }); + Optional> maybeLargestIndex = indices.stream().max(Comparator.comparing(List::size)); + if (maybeLargestIndex.isPresent()) { + List largestIndex = maybeLargestIndex.get(); + allMethods.stream().filter(m -> !largestIndex.contains(m)).filter(m -> m.getLocation() != null) + .sorted(Comparator.comparing(m -> m.getLocation().getRegion())).forEach(largestIndex::add); + return largestIndex; + } + return allMethods; + } + + private List traverseCallGraph(MethodDeclaration mainMethod) { + List callGraphIndex = new ArrayList<>(); + callGraphIndex.add(mainMethod); + + SubgraphWalker.IterativeGraphWalker walker = new SubgraphWalker.IterativeGraphWalker(); + walker.setStrategy(nodeOrderStrategy::getIterator); + + index = 0; + List newFunctions = new ArrayList<>(); + walker.registerOnNodeVisit((node, parent) -> { + handleNode(node, callGraphIndex, newFunctions); + return Unit.INSTANCE; + }); + while (index < callGraphIndex.size()) { + MethodDeclaration next = callGraphIndex.get(index++); + walker.iterate(next); + callGraphIndex.removeAll(newFunctions); + callGraphIndex.addAll(index, newFunctions); + newFunctions.clear(); + } + return callGraphIndex; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/visitor/NodeOrderStrategy.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/visitor/NodeOrderStrategy.java new file mode 100644 index 0000000000..22184373a7 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/visitor/NodeOrderStrategy.java @@ -0,0 +1,216 @@ +package de.jplag.java_cpg.visitor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.TranslationResult; +import de.fraunhofer.aisec.cpg.graph.Component; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.graph.scopes.BlockScope; +import de.fraunhofer.aisec.cpg.graph.scopes.LoopScope; +import de.fraunhofer.aisec.cpg.graph.scopes.TryScope; +import de.fraunhofer.aisec.cpg.graph.scopes.ValueDeclarationScope; +import de.fraunhofer.aisec.cpg.graph.statements.DoStatement; +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.fraunhofer.aisec.cpg.graph.statements.Statement; +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement; +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block; +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker; +import de.fraunhofer.aisec.cpg.processing.IStrategy; +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy; +import de.fraunhofer.aisec.cpg.sarif.Region; +import de.jplag.java_cpg.transformation.operations.TransformationUtil; + +import com.google.common.collect.Iterators; +import kotlin.Unit; + +/** + * This class defines the order of visitation of the CPG {@link Node}s. + */ +public class NodeOrderStrategy implements IStrategy { + + private static final boolean USE_CALL_GRAPH_ORDER = true; + private List methodOrder; + + /** + * Creates a new {@link NodeOrderStrategy}. + */ + public NodeOrderStrategy() { + // nothing to do yet + } + + private static boolean isMainClass(RecordDeclaration recordDeclaration) { + return recordDeclaration.getMethods().stream().anyMatch(NodeOrderStrategy::isMainMethod); + } + + private static Iterator walkMethod(MethodDeclaration methodDecl) { + if (!methodDecl.hasBody()) { + return Iterators.concat(methodDecl.getParameters().iterator()); + } + return Iterators.concat(methodDecl.getParameters().iterator(), List.of(methodDecl.getBody()).iterator()); + } + + static boolean isMainMethod(FunctionDeclaration function) { + return function instanceof MethodDeclaration method && method.isStatic() && method.getName().getLocalName().equals("main") + && method.getReturnTypes().size() == 1 && method.getReturnTypes().getFirst().getTypeName().equals("void"); + } + + @NotNull + private static Iterator walkBlock(Block block) { + return block.getStatements().stream().map(TransformationUtil::getEntry).iterator(); + } + + /** + * Finds all child {@link Node}s of the given {@link Statement} in the order determined by the + * {@link NodeOrderStrategy}. + * @param statement the statement + * @return a list of all child nodes + */ + public static List flattenStatement(Statement statement) { + List astChildren = SubgraphWalker.INSTANCE.flattenAST(statement); + NodeOrderStrategy strategy = new NodeOrderStrategy(); + Node entry = TransformationUtil.getEntry(statement); + + List nodes = new ArrayList<>(astChildren.size()); + SubgraphWalker.IterativeGraphWalker walker = new SubgraphWalker.IterativeGraphWalker(); + walker.setStrategy(node -> Iterators.filter(strategy.getIterator(node), astChildren::contains)); + walker.registerOnNodeVisit((node, parent) -> { + nodes.add(node); + return Unit.INSTANCE; + }); + walker.iterate(entry); + return nodes; + } + + @Override + public @NotNull Iterator getIterator(Node node) { + if (node instanceof TranslationResult translationResult) { + this.methodOrder = new MethodOrderStrategy().setupMethodCallGraphOrder(translationResult); + return Strategy.INSTANCE.AST_FORWARD(node); + } else if (node instanceof Component c) { + return walkComponent(c); + } else if (node instanceof TranslationUnitDeclaration tu) { + return walkTranslationUnit(tu); + } else if (node instanceof RecordDeclaration recordDecl) { + return walkRecord(recordDecl); + } else if (node instanceof MethodDeclaration methodDecl) { + return walkMethod(methodDecl); + } else if (node instanceof WhileStatement whileStatement) { + return walkWhileStatement(whileStatement); + } else if (node instanceof DoStatement doStatement) { + return walkDoWhileStatement(doStatement); + } else if (node instanceof IfStatement ifStatement) { + return walkIfStatement(ifStatement); + } else if (node instanceof ForStatement forStatement) { + return walkForStatement(forStatement); + } else if (node instanceof Block block) { + return walkBlock(block); + } else if (node.getScope() instanceof BlockScope || node.getScope() instanceof LoopScope || node.getScope() instanceof TryScope + || node.getScope() instanceof ValueDeclarationScope) { + return Strategy.INSTANCE.EOG_FORWARD(node); + } else { + return Strategy.INSTANCE.AST_FORWARD(node); + } + } + + private Iterator walkTranslationUnit(TranslationUnitDeclaration tu) { + List topLevelRecords = getTopLevelRecords(tu); + return List.copyOf(topLevelRecords).iterator(); + } + + private Iterator walkComponent(Component c) { + Map> filePartition = c.getTranslationUnits().stream() + .collect(Collectors.groupingBy(tu -> getTopLevelRecords(tu).stream().anyMatch(NodeOrderStrategy::isMainClass))); + + List mainFiles = filePartition.getOrDefault(true, List.of()); + List otherFiles = filePartition.getOrDefault(false, List.of()); + + return Iterators.concat(mainFiles.iterator(), otherFiles.iterator()); + } + + private List getTopLevelRecords(Node node) { + List result = new ArrayList<>(); + List declarations = new ArrayList<>(List.of(node)); + while (!declarations.isEmpty()) { + Node declaration = declarations.removeFirst(); + switch (declaration) { + case Component component -> declarations.addAll(component.getTranslationUnits()); + case TranslationUnitDeclaration tu -> declarations.addAll(tu.getDeclarations()); + case NamespaceDeclaration namespaceDeclaration -> declarations.addAll(namespaceDeclaration.getDeclarations()); + case RecordDeclaration recordDeclaration -> result.add(recordDeclaration); + case null, default -> { + // do nothing + } + } + + } + return result; + } + + private Iterator walkRecord(RecordDeclaration recordDecl) { + List functions = new ArrayList<>(); + functions.addAll(recordDecl.getConstructors()); + functions.addAll(recordDecl.getMethods()); + return Iterators.concat(recordDecl.getFields().iterator(), + functions.stream().filter(m -> !Objects.isNull(m.getBody())).sorted(this::walkMethods).iterator(), + recordDecl.getTemplates().iterator(), recordDecl.getRecords().iterator()); + } + + private Iterator walkDoWhileStatement(DoStatement doStatement) { + // Condition is visited already at this point + Node body = doStatement.getStatement(); + if (Objects.isNull(body)) { + return Collections.emptyIterator(); + } + return Stream.of(body).iterator(); + + } + + private Iterator walkForStatement(ForStatement forStatement) { + // Condition is visited already at this point + Node body = forStatement.getStatement(); + if (Objects.isNull(body)) { + return Collections.emptyIterator(); + } + return Stream.of(body).iterator(); + } + + private Iterator walkIfStatement(IfStatement ifStatement) { + // Condition is already visited at this point + return Stream.of(ifStatement.getThenStatement(), ifStatement.getElseStatement()).filter(Objects::nonNull).iterator(); + } + + private int walkMethods(MethodDeclaration method1, MethodDeclaration method2) { + if (USE_CALL_GRAPH_ORDER && Objects.isNull(methodOrder)) { + return Comparator.comparing(m -> m.getLocation() == null ? null : m.getLocation().getRegion()).compare(method1, + method2); + } + + return Comparator.comparing(methodOrder::indexOf).compare(method1, method2); + + } + + private Iterator walkWhileStatement(WhileStatement whileStatement) { + // Condition is visited already at this point + Node body = whileStatement.getStatement(); + if (Objects.isNull(body)) { + return Collections.emptyIterator(); + } + return Stream.of(body).iterator(); + } +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/AbstractJavaCpgLanguageTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/AbstractJavaCpgLanguageTest.java new file mode 100644 index 0000000000..dd37682d43 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/AbstractJavaCpgLanguageTest.java @@ -0,0 +1,54 @@ +package de.jplag.java_cpg; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.jplag.ParsingException; +import de.jplag.Token; +import de.jplag.TokenPrinter; +import de.jplag.TokenType; + +/** + * Basic test class for testing the Java language module. + */ +public abstract class AbstractJavaCpgLanguageTest { + + protected static final Path BASE_PATH = Path.of("src", "test", "resources", "java"); + private static final String LOG_MESSAGE = "Tokens of {}: {}"; + private final Logger logger = LoggerFactory.getLogger(AbstractJavaCpgLanguageTest.class); + private JavaCpgLanguage language; + protected File baseDirectory; + + /** + * Sets up the base directory and the language module. + */ + @BeforeEach + void setUp() { + language = new JavaCpgLanguage(); + baseDirectory = BASE_PATH.toFile(); + assertTrue(baseDirectory.exists(), "Could not find base directory!"); + } + + /** + * Parses a java file in the {@code baseDirectory} and returns the list of token types. + * @param fileName is the name of the file to parse. + * @return the token types. + * @throws ParsingException if parsing fails. + */ + protected List parseJavaFile(String fileName, boolean transform) throws ParsingException { + List parsedTokens = language.parse(Set.of(new File(baseDirectory.getAbsolutePath(), fileName)), transform); + List tokenTypes = parsedTokens.stream().map(Token::getType).toList(); + logger.info(LOG_MESSAGE, fileName, tokenTypes); + logger.info(TokenPrinter.printTokens(parsedTokens, BASE_PATH.toAbsolutePath().toFile())); + return tokenTypes; + } + +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/CombinedPassTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/CombinedPassTest.java new file mode 100644 index 0000000000..ff94efd5a9 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/CombinedPassTest.java @@ -0,0 +1,98 @@ +package de.jplag.java_cpg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import de.jplag.ParsingException; +import de.jplag.Token; +import de.jplag.TokenType; +import de.jplag.java_cpg.token.CpgTokenType; + +/** + * Test class for testing the interaction between AiPass and TokenizationPass. + */ +class CombinedPassTest extends AbstractJavaCpgLanguageTest { + + /** + * @author Gemini 3 Flash + */ + @Test + void testDeadCodeRemovalInTokens() throws ParsingException { + String fileName = "ai/deadCode5/Submission-01/Main.java"; + JavaCpgLanguage language = new JavaCpgLanguage(); + // Parse the file with normalization enabled (which triggers AiPass) + List parsedTokens = language.parse(Set.of(new File(baseDirectory.getAbsolutePath(), fileName)), true); + // Check if any token belongs to DeadClass (lines 11-15) + boolean foundDeadClassToken = false; + for (Token token : parsedTokens) { + if (token.getLine() >= 11 && token.getLine() <= 15) { + foundDeadClassToken = true; + break; + } + } + assertFalse(foundDeadClassToken, "Tokens from DeadClass should have been removed by AiPass"); + // Also verify that we still have tokens from Main class (lines 3-9) + boolean foundMainClassToken = false; + for (Token token : parsedTokens) { + if (token.getLine() >= 3 && token.getLine() <= 9) { + foundMainClassToken = true; + break; + } + } + assertTrue(foundMainClassToken, "Tokens from Main class should be present"); + } + + @Test + @Disabled + void testDeadCodeRemovalInTokens2() throws ParsingException { + List parsedTokens = parseJavaFile("combined/One", true); + assertEquals(87, parsedTokens.size(), "Unexpected number of tokens after dead code removal"); + assertEquals(CpgTokenType.RECORD_DECL_BEGIN, parsedTokens.getFirst()); + assertEquals(CpgTokenType.METHOD_CALL, parsedTokens.get(10)); + assertEquals(CpgTokenType.IF_STATEMENT, parsedTokens.get(20)); + assertEquals(CpgTokenType.FIELD_DECL, parsedTokens.get(30)); + // assertEquals(CpgTokenType.IF_BLOCK_END, parsedTokens.get(40)); //FixMe + assertEquals(CpgTokenType.METHOD_BODY_BEGIN, parsedTokens.get(50)); + assertEquals(CpgTokenType.RECORD_DECL_BEGIN, parsedTokens.get(61)); + assertEquals(CpgTokenType.ELSE_BLOCK_BEGIN, parsedTokens.get(70)); + assertEquals(CpgTokenType.METHOD_CALL, parsedTokens.get(80)); + assertEquals(CpgTokenType.RECORD_DECL_END, parsedTokens.get(85)); + } + + @Test + @Disabled + void testDeadCodeRemovalInTokensInheritance() throws ParsingException { + List parsedTokens = parseJavaFile("combined/Two", true); + assertEquals(117, parsedTokens.size(), "Unexpected number of tokens after dead code removal"); + assertEquals(CpgTokenType.RECORD_DECL_BEGIN, parsedTokens.getFirst()); + assertEquals(CpgTokenType.CONSTRUCTOR_CALL, parsedTokens.get(10)); + assertEquals(CpgTokenType.IF_BLOCK_END, parsedTokens.get(20)); + assertEquals(CpgTokenType.METHOD_DECL_BEGIN, parsedTokens.get(30)); + assertEquals(CpgTokenType.METHOD_BODY_END, parsedTokens.get(40)); + assertEquals(CpgTokenType.METHOD_DECL_BEGIN, parsedTokens.get(50)); + assertEquals(CpgTokenType.METHOD_BODY_END, parsedTokens.get(60)); + assertEquals(CpgTokenType.METHOD_BODY_BEGIN, parsedTokens.get(70)); + assertEquals(CpgTokenType.METHOD_BODY_END, parsedTokens.get(80)); + assertEquals(CpgTokenType.FIELD_DECL, parsedTokens.get(90)); + assertEquals(CpgTokenType.ASSIGNMENT, parsedTokens.get(100)); + assertEquals(CpgTokenType.METHOD_PARAM, parsedTokens.get(110)); + } + + @Test + @Disabled + void testDeadCodeRemovalInTokensInheritanceComplex() throws ParsingException { + List parsedTokens = parseJavaFile("combined/multiInheritance1", true); + assertEquals(117, parsedTokens.size(), "Unexpected number of tokens after dead code removal"); // ToDo + assertEquals(CpgTokenType.RECORD_DECL_BEGIN, parsedTokens.getFirst()); + + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/CreateTransformTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/CreateTransformTest.java new file mode 100644 index 0000000000..52f9f19181 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/CreateTransformTest.java @@ -0,0 +1,64 @@ +package de.jplag.java_cpg; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.io.File; +import java.net.ConnectException; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import de.fraunhofer.aisec.cpg.TranslationContext; +import de.fraunhofer.aisec.cpg.TranslationResult; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.jplag.ParsingException; +import de.jplag.java_cpg.transformation.GraphTransformation; +import de.jplag.java_cpg.transformation.TransformationRepository; +import de.jplag.java_cpg.transformation.matching.CpgIsomorphismDetector; +import de.jplag.java_cpg.transformation.matching.pattern.GraphPattern; +import de.jplag.java_cpg.transformation.matching.pattern.Match; + +public class CreateTransformTest extends AbstractJavaCpgLanguageTest { + + private CpgIsomorphismDetector detector; + + public static Stream provideTuples() { + return Stream.of( + Arguments.of("UnusedVariableDeclaration.java", TransformationRepository.removeUnusedVariableDeclaration, VariableDeclaration.class), + Arguments.of("IfElseWithNegatedCondition.java", TransformationRepository.ifWithNegatedConditionResolution, IfStatement.class)); + } + + @ParameterizedTest + @MethodSource("provideTuples") + void createTransformTest(String fileName, GraphTransformation transformation) throws ParsingException, InterruptedException, ConnectException { + + Set files = Set.of(new File(baseDirectory, fileName)); + CpgAdapter cpgAdapter = new CpgAdapter(false, false, true); + cpgAdapter.clearTransformations(); + TranslationResult graph = cpgAdapter.translate(files); + + detector = new CpgIsomorphismDetector(); + detector.loadGraph(graph); + + instantiate(transformation); + + } + + private void instantiate(GraphTransformation transformation) { + GraphPattern sourcePattern = transformation.getSourcePattern(); + List maybeMatch = detector.getMatches(sourcePattern); + + assertFalse(maybeMatch.isEmpty()); + Match match = maybeMatch.getFirst(); + + TranslationContext ctx = match.get(sourcePattern.getRepresentingNode()).getCtx(); + transformation.apply(match, ctx); + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/EvaluationEngineTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/EvaluationEngineTest.java new file mode 100644 index 0000000000..87ca73cb4e --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/EvaluationEngineTest.java @@ -0,0 +1,439 @@ +package de.jplag.java_cpg; + +import static de.jplag.java_cpg.AbstractJavaCpgLanguageTest.BASE_PATH; +import static de.jplag.options.JPlagOptions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import de.jplag.*; +import de.jplag.clustering.ClusteringOptions; +import de.jplag.exceptions.ExitException; +import de.jplag.highlightextraction.FrequencyAnalysisOptions; +import de.jplag.java_cpg.ai.*; +import de.jplag.merging.MergingOptions; +import de.jplag.options.JPlagOptions; + +import kotlin.Pair; + +class EvaluationEngineTest { + + // 21/Acceptes/45/3 + // 21/WrongAnswer/28/2 -> multiple calls + // 18/Accepted/32/2 + // 18/Accepted/40/1 + // 18/Accepted/31/2 + + @NotNull + private static Stream testFiles() { + return Stream.of("aiGenerated/gemini/ProjectA.java", "aiGenerated/gemini/ProjectB.java", "aiGenerated/gemini/ProjectC.java", + "aiGenerated/gemini/ProjectD.java", "aiGenerated/gemini/ProjectE.java", "aiGenerated/gemini/ProjectF.java", + "aiGenerated/gemini/ProjectG.java", "aiGenerated/gemini/ProjectH.java", "aiGenerated/gemini/ProjectI.java", + "aiGenerated/gemini/ProjectJ.java", "aiGenerated/gemini/ProjectK.java", "aiGenerated/gemini/ProjectL.java", + "aiGenerated/gemini/ProjectM.java", "aiGenerated/gemini/ProjectN.java", "aiGenerated/gemini/ProjectO.java", + "aiGenerated/gemini/ProjectP.java", "aiGenerated/gemini/ProjectQ.java", + // "aiGenerated/gemini/ProjectR.java", "aiGenerated/gemini/ProjectS.java", //anonymus class causes problems + "aiGenerated/gemini/ProjectT.java", "aiGenerated/gemini/ProjectU.java", "aiGenerated/gemini/Project1.java", + "aiGenerated/gemini/Project2.java", "aiGenerated/gemini/Project3.java", "aiGenerated/gemini/Project4.java", + // "aiGenerated/gemini/Project5.java", //bug in GraphTransformations + "aiGenerated/gemini/Project7.java", "aiGenerated/gemini/Project8.java", + // + "aiGenerated/claude/Project1.java", "aiGenerated/claude/Project2.java", "aiGenerated/claude/Project3.java", + "aiGenerated/claude/Project4.java", "aiGenerated/claude/Project5.java", "aiGenerated/claude/Project6.java", + "aiGenerated/claude/Project7.java", "aiGenerated/claude/Project8.java", "aiGenerated/claude/Project9.java", + "aiGenerated/claude/Project10.java", + // + "aiGenerated/perplexityLabs/Project1.java", "aiGenerated/perplexityLabs/Project2.java", "aiGenerated/perplexityLabs/Project3.java", + "aiGenerated/perplexityLabs/Project4.java", + // + "aiGenerated/geminiPlag/NetworkController.java", "aiGenerated/geminiPlag/ServerProcessManager.java", + // "aiGenerated/geminiPlag/GridOverseer.java", //bug in my code + // + "aiGenerated/grok/project1.java", "aiGenerated/grok/project2.java", "aiGenerated/grok/project3.java", + "aiGenerated/grok/project4.java", "aiGenerated/grok/project5.java", "aiGenerated/grok/project6.java", + "aiGenerated/grok/project7.java", "aiGenerated/grok/project8.java"); + } + + @NotNull + private static Stream> testPlagFiles() { + return Stream.of(new Pair<>("aiGenerated/gemini/ProjectA.java", "aiGenerated/gemini/ProjectC.java"), + new Pair<>("aiGenerated/gemini/ProjectB.java", "aiGenerated/gemini/ProjectE.java"), + new Pair<>("aiGenerated/gemini/ProjectF.java", "aiGenerated/gemini/ProjectG.java"), + new Pair<>("aiGenerated/gemini/ProjectH.java", "aiGenerated/gemini/ProjectI.java"), + new Pair<>("aiGenerated/gemini/ProjectJ.java", "aiGenerated/gemini/ProjectK.java"), + new Pair<>("aiGenerated/gemini/ProjectL.java", "aiGenerated/gemini/ProjectM.java"), + new Pair<>("aiGenerated/gemini/ProjectN.java", "aiGenerated/gemini/ProjectO.java"), + new Pair<>("aiGenerated/gemini/ProjectP.java", "aiGenerated/gemini/ProjectQ.java"), + // new Pair<>("aiGenerated/gemini/ProjectR.java", "aiGenerated/gemini/ProjectS.java"), //anonymus class causes problems + new Pair<>("aiGenerated/gemini/ProjectT.java", "aiGenerated/gemini/ProjectU.java"), + new Pair<>("aiGenerated/gemini/Project1.java", "aiGenerated/gemini/Project2.java"), + new Pair<>("aiGenerated/gemini/Project1.java", "aiGenerated/gemini/Project3.java"), + // new Pair<>("aiGenerated/gemini/Project4.java", "aiGenerated/gemini/Project5.java"), //bug in GraphTransformations + new Pair<>("aiGenerated/gemini/Project7.java", "aiGenerated/gemini/Project8.java"), + new Pair<>("aiGenerated/gemini/Project4.java", "aiGenerated/gemini/Project7.java"), + new Pair<>("aiGenerated/gemini/Project4.java", "aiGenerated/gemini/Project8.java"), + // + new Pair<>("aiGenerated/gemini/ProjectH.java", "aiGenerated/geminiPlag/NetworkController.java"), + new Pair<>("aiGenerated/gemini/ProjectJ.java", "aiGenerated/geminiPlag/ServerProcessManager.java"), + // new Pair<>("aiGenerated/gemini/NetworkController.java", "aiGenerated/geminiPlag/GridOverseer.java"), //bug in my code + // + new Pair<>("aiGenerated/claude/Project1.java", "aiGenerated/claude/Project2.java"), + new Pair<>("aiGenerated/claude/Project4.java", "aiGenerated/claude/Project1.java"), + new Pair<>("aiGenerated/claude/Project4.java", "aiGenerated/claude/Project3.java"), + new Pair<>("aiGenerated/claude/Project5.java", "aiGenerated/claude/Project6.java"), + new Pair<>("aiGenerated/claude/Project7.java", "aiGenerated/claude/Project8.java"), + new Pair<>("aiGenerated/claude/Project9.java", "aiGenerated/claude/Project10.java"), + // + new Pair<>("aiGenerated/perplexityLabs/Project1.java", "aiGenerated/perplexityLabs/Project3.java"), + new Pair<>("aiGenerated/perplexityLabs/Project1.java", "aiGenerated/perplexityLabs/Project4.java"), + // + new Pair<>("aiGenerated/grok/project1.java", "aiGenerated/grok/project2.java"), + new Pair<>("aiGenerated/grok/project3.java", "aiGenerated/grok/project4.java"), + new Pair<>("aiGenerated/grok/project3.java", "aiGenerated/grok/project5.java"), + new Pair<>("aiGenerated/grok/project4.java", "aiGenerated/grok/project5.java"), + new Pair<>("aiGenerated/grok/project6.java", "aiGenerated/grok/project7.java"), + new Pair<>("aiGenerated/grok/project6.java", "aiGenerated/grok/project8.java"), + new Pair<>("aiGenerated/grok/project7.java", "aiGenerated/grok/project8.java")); + + } + + private static double similarity(@NotNull List s1, @NotNull List s2) { + // stolen from https://stackoverflow.com/questions/955110/similarity-string-comparison-in-java + List longer = s1, shorter = s2; + if (s1.size() < s2.size()) { // longer should always have greater length + longer = s2; + shorter = s1; + } + int longerLength = longer.size(); + if (longerLength == 0) { + return 1.0; /* both lists are zero length */ + } + double result = (longerLength - editDistance(longer, shorter)) / (double) longerLength; + result = Math.round(result * 10000.0) / 10000.0; + return result * 100; + } + + private static int editDistance(@NotNull List s1, @NotNull List s2) { + // stolen from https://stackoverflow.com/questions/955110/similarity-string-comparison-in-java + int[] costs = new int[s2.size() + 1]; + for (int i = 0; i <= s1.size(); i++) { + int lastValue = i; + for (int j = 0; j <= s2.size(); j++) { + if (i == 0) + costs[j] = j; + else { + if (j > 0) { + int newValue = costs[j - 1]; + if (!(s1.get(i - 1).equals(s2.get(j - 1)))) { + newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1; + } + costs[j - 1] = lastValue; + lastValue = newValue; + } + } + } + if (i > 0) { + costs[s2.size()] = lastValue; + } + } + return costs[s2.size()]; + } + + @NotNull + private static List getTokensFromFile(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize) throws ParsingException { + assert normalize || !reorder; + + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, JavaCpgLanguage.deadCodeRemovalTransformations(), + IntAiType.DEFAULT, FloatAiType.DEFAULT, StringAiType.DEFAULT, CharAiType.DEFAULT, ArrayAiType.DEFAULT); + // IntAiType.INTERVALS, FloatAiType.SET, StringAiType.CHAR_INCLUSION, CharAiType.SET, ArrayAiType.LENGTH); + File file = new File(BASE_PATH.toFile().getAbsolutePath(), fileName); + Set files = Set.of(file); + List result = language.parse(files, normalize); + result.removeLast(); // remove EOF token + return result; + } + + @NotNull + private static List getTokensFromFileWithoutDeadCode(@NotNull String fileName, boolean reorder) throws ParsingException { + try { + File originalFile = new File(BASE_PATH.toFile().getAbsolutePath(), fileName); + File tempFile = File.createTempFile("jplag_temp_", ".java"); + tempFile.deleteOnExit(); + BufferedReader reader = new BufferedReader(new FileReader(originalFile)); + java.io.PrintWriter writer = new java.io.PrintWriter(tempFile); + String line; + boolean inDeadCode = false; + while ((line = reader.readLine()) != null) { + String trimmed = line.trim(); + if (trimmed.contains("//DeadCodeStart")) { + inDeadCode = true; + } else if (trimmed.contains("//DeadCodeEnd")) { + inDeadCode = false; + } else if (!inDeadCode) { + writer.println(line); + } + } + reader.close(); + writer.close(); + JavaCpgLanguage language = new JavaCpgLanguage(false, false, reorder); + List result = language.parse(Set.of(tempFile), false); + result.removeLast(); // remove EOF token + return result; + } catch (IOException e) { + throw new ParsingException(new File(BASE_PATH.toFile().getAbsolutePath(), fileName), e.getMessage()); + } + } + + private static double getJPlagCpgPlagScore(@NotNull String fileNameA, @NotNull String fileNameB, boolean removeDeadCode, boolean detectDeadCode, + boolean reorder, boolean normalize) throws ExitException, IOException { + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder); + return getJPlagScore(fileNameA, fileNameB, normalize, language); + } + + private static double getJPlagPlagScore(@NotNull String fileNameA, @NotNull String fileNameB, boolean normalize) + throws ExitException, IOException { + de.jplag.java.JavaLanguage language = new de.jplag.java.JavaLanguage(); + return getJPlagScore(fileNameA, fileNameB, normalize, language); + } + + private static double getJPlagScore(@NotNull String fileNameA, @NotNull String fileNameB, boolean normalize, Language language) + throws ExitException, IOException { + File fileA = new File(BASE_PATH.toFile().getAbsolutePath(), fileNameA); + File fileB = new File(BASE_PATH.toFile().getAbsolutePath(), fileNameB); + // Create temporary directories for submissions + File tempDirA = createTempDir(); + File tempDirB = createTempDir(); + // Copy files to temp directories + File targetA = new File(tempDirA, "SubmissionA.java"); + File targetB = new File(tempDirB, "SubmissionB.java"); + java.nio.file.Files.copy(fileA.toPath(), targetA.toPath()); + java.nio.file.Files.copy(fileB.toPath(), targetB.toPath()); + Set submissionDirectories = Set.of(tempDirA, tempDirB); + JPlagOptions options = new JPlagOptions(language, null, submissionDirectories, Set.of(), null, null, null, null, DEFAULT_SIMILARITY_METRIC, + DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SHOWN_COMPARISONS, new ClusteringOptions(), false, new MergingOptions(), normalize, false, + new FrequencyAnalysisOptions()); + JPlagResult result = JPlag.run(options); + assert result.getAllComparisons().size() == 1; + JPlagComparison comparison = result.getAllComparisons().getFirst(); + double similarity = comparison.similarity(); + double maxSimilarity = comparison.maximalSimilarity(); + int matchedTokens = comparison.getNumberOfMatchedTokens(); + return similarity; + } + + @NotNull + private static File createTempDir() throws IOException { + File tempDir = File.createTempFile("jplag_submission_", ""); + tempDir.delete(); + tempDir.mkdir(); + tempDir.deleteOnExit(); + return tempDir; + } + + public static void checkNonDeadCodeNotRemoved(@NotNull List verifiedDeadCode, @NotNull List detectedDeadCode) { + // we check that all tokens in verifiedDeadCode are also in detectedDeadCode + Assertions.assertTrue(detectedDeadCode.size() >= verifiedDeadCode.size(), "Detected dead code is smaller than verified dead code" + + " (detected: " + detectedDeadCode.size() + ", verified: " + verifiedDeadCode.size() + ")"); + ArrayList detectedDeadCodeCopy = new ArrayList<>(detectedDeadCode); + int i = 0; + for (Token token : verifiedDeadCode) { + Token nextToken = detectedDeadCodeCopy.get(i); + while (!Objects.equals(nextToken.toString(), token.toString())) { + i++; + nextToken = detectedDeadCodeCopy.get(i); + } + } + Assertions.assertTrue(true); + } + + @NotNull + public static Stream progpediaFiles() { + return ProgpediaTests.progpediaFiles(); + } + + @ParameterizedTest + @Disabled + @MethodSource("testFiles") + void AiGeneratedTestDataDeadCodeEvaluation(String fileName) throws ParsingException { + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true); + long timeSimpleRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true); + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false); + + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + + File csvFile = new File("deadcode_results.csv"); + boolean fileExists = csvFile.exists(); + + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write("FileName,NoRemoval,SimpleRemoval,FullRemoval,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms)\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f%n", fileName, simNoRemoval, simSimpleRemoval, simFullRemoval, + timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + System.out.println("Similarity between manual and no dead code removal: " + simNoRemoval + "% (took " + timeNoRemoval / 1_000_000.0 + " ms)"); + System.out.println("Similarity between manual and automatic simple dead code removal: " + simSimpleRemoval + "% (took " + + timeSimpleRemoval / 1_000_000.0 + " ms)"); + System.out.println( + "Similarity between manual and automatic dead code removal: " + simFullRemoval + "% (took " + timeFullRemoval / 1_000_000.0 + " ms)"); + assertTrue(true); + } + + @ParameterizedTest + @Disabled + @MethodSource("progpediaFiles") + void ProgpediaDeadCodeEvaluation(String fileName) throws ParsingException { + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true); + long timeSimpleRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true); + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false); + + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + + File csvFile = new File("Progpedia_deadcode_results.csv"); + boolean fileExists = csvFile.exists(); + + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write("FileName,NoRemoval,SimpleRemoval,FullRemoval,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms)\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f%n", fileName, simNoRemoval, simSimpleRemoval, simFullRemoval, + timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + System.out.println("Similarity between manual and no dead code removal: " + simNoRemoval + "% (took " + timeNoRemoval / 1_000_000.0 + " ms)"); + System.out.println("Similarity between manual and automatic simple dead code removal: " + simSimpleRemoval + "% (took " + + timeSimpleRemoval / 1_000_000.0 + " ms)"); + System.out.println( + "Similarity between manual and automatic dead code removal: " + simFullRemoval + "% (took " + timeFullRemoval / 1_000_000.0 + " ms)"); + assertTrue(true); + } + + @Test + @Disabled + void AiGeneratedTestDataDeadCodeEvaluationSingle() throws ParsingException { + // String fileName = "progpedia/00000018/ACCEPTED/00095_00005/optica.java"; + // String fileName = "aiGenerated/perplexityLabs/Project2.java"; + String fileName = "aiGenerated/gemini/Project5.java"; + List tokens = getTokensFromFile(fileName, false, false, false, false); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true); + List tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true); + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false); + // Assert we don't remove non-dead code + // checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + // checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + + System.out.println("Similarity between manual and no dead code removal: " + similarity(tokensWithoutDeadCodeManual, tokens) + "%"); + System.out.println("Similarity between manual and simple dead code removal: " + + similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode) + "%"); + System.out + .println("Similarity between manual and dead code removal: " + similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode) + "%"); + assertTrue(true); + } + + @ParameterizedTest + @Disabled + @MethodSource("testPlagFiles") + void AiGeneratedTestDataPlagEvaluation(@NotNull Pair fileNames) throws ExitException, IOException { + String fileA = fileNames.getFirst(); + String fileB = fileNames.getSecond(); + + long startTime = System.nanoTime(); + double similarityJPlag = getJPlagPlagScore(fileA, fileB, false); + long timeJPlag = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + double similarityMinimalCpg = getJPlagCpgPlagScore(fileA, fileB, false, false, false, false); + long timeMinimalCpg = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + double similarityStandardCpg = getJPlagCpgPlagScore(fileA, fileB, false, false, false, true); + long timeStandardCpg = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + double similarityAi = getJPlagCpgPlagScore(fileA, fileB, true, true, false, true); + long timeAi = System.nanoTime() - startTime; + + File csvFile = new File("plagiarism_results.csv"); + boolean fileExists = csvFile.exists(); + + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write("FileA,FileB,JPlag,CpgMinimal,CpgStandard,CpgAI,TimeJPlag(ms),TimeMinimalCpg(ms),TimeStandardCpg(ms),TimeAi(ms)\n"); + } + writer.write(String.format("%s,%s,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%.2f%n", fileA, fileB, similarityJPlag, similarityMinimalCpg, + similarityStandardCpg, similarityAi, timeJPlag / 1_000_000.0, timeMinimalCpg / 1_000_000.0, timeStandardCpg / 1_000_000.0, + timeAi / 1_000_000.0)); + } + + System.out.println("Plagiarism scores for " + fileA + " and " + fileB + ":"); + System.out.println("JPlag standard: " + similarityJPlag + " (took " + timeJPlag / 1_000_000.0 + " ms)"); + System.out.println("Cpg minimal transformations: " + similarityMinimalCpg + " (took " + timeMinimalCpg / 1_000_000.0 + " ms)"); + System.out.println("Cpg standard transformations: " + similarityStandardCpg + " (took " + timeStandardCpg / 1_000_000.0 + " ms)"); + System.out.println("Cpg with AI dead code removal: " + similarityAi + " (took " + timeAi / 1_000_000.0 + " ms)"); + assertTrue(true); + } + + @Test + @Disabled + void AiGeneratedTestDataPlagEvaluationSingle() throws ExitException, IOException { + String fileA = "aiGenerated/gemini/ProjectD.java"; + String fileB = "aiGenerated/gemini/ProjectD.java"; + double similarityJPlag = getJPlagPlagScore(fileA, fileB, false); + double similarityMinimalCpg = getJPlagCpgPlagScore(fileA, fileB, false, false, false, false); + double similarityStandardCpg = getJPlagCpgPlagScore(fileA, fileB, false, false, false, true); + double similarityAi = getJPlagCpgPlagScore(fileA, fileB, true, true, false, true); + + System.out.println("Plagiarism scores for " + fileA + " and " + fileB + ":"); + System.out.println("JPlag standard: " + similarityJPlag); + System.out.println("Cpg minimal transformations: " + similarityMinimalCpg); + System.out.println("Cpg standard transformations: " + similarityStandardCpg); + System.out.println("Cpg with AI dead code removal: " + similarityAi); + assertTrue(true); + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/JavaBlockTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/JavaBlockTest.java new file mode 100644 index 0000000000..289ca1ce76 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/JavaBlockTest.java @@ -0,0 +1,30 @@ +package de.jplag.java_cpg; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Test cases regarding the extraction edge implicit vs. explicit blocks in Java code. + */ +class JavaBlockTest extends AbstractJavaCpgLanguageTest { + @ParameterizedTest + @MethodSource("provideSrcDirectories") + @DisplayName("Test pairs of classes with explicit vs. implicit blocks.") + void testJavaClassPair(String dir) { + Assertions.assertDoesNotThrow(() -> parseJavaFile(dir, false)); + } + + /** + * Argument source for the test case {@code testJavaClassPair}. + */ + private static Stream provideSrcDirectories() { + return Stream.of(Arguments.of("if"), // just if conditions + Arguments.of("verbosity")); // complex case with different blocks + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/JavaTryTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/JavaTryTest.java new file mode 100644 index 0000000000..d1caa3ec00 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/JavaTryTest.java @@ -0,0 +1,19 @@ +package de.jplag.java_cpg; + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import de.jplag.ParsingException; + +/** + * Test cases regarding the extraction edge try vs. try with resource. + */ +class JavaTryTest extends AbstractJavaCpgLanguageTest { + @Test + @DisplayName("Test difference between try block and try-with-resource block.") + void testJavaClassPair() throws ParsingException { + assertIterableEquals(parseJavaFile("try/Try.java", true), parseJavaFile("try/TryWithResource.java", true)); + } +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/MatchingTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/MatchingTest.java new file mode 100644 index 0000000000..abbdfa3970 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/MatchingTest.java @@ -0,0 +1,59 @@ +package de.jplag.java_cpg; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.fraunhofer.aisec.cpg.TranslationResult; +import de.fraunhofer.aisec.cpg.graph.Node; +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement; +import de.jplag.ParsingException; +import de.jplag.java_cpg.transformation.matching.CpgIsomorphismDetector; +import de.jplag.java_cpg.transformation.matching.PatternRepository; +import de.jplag.java_cpg.transformation.matching.pattern.Match; +import de.jplag.java_cpg.transformation.matching.pattern.SimpleGraphPattern; + +public class MatchingTest extends AbstractJavaCpgLanguageTest { + + public static final Logger LOGGER = LoggerFactory.getLogger(MatchingTest.class); + + public static Stream providePairs() { + return Stream.of(Arguments.of("IfElseWithNegatedCondition.java", IfStatement.class, PatternRepository.ifElseWithNegatedCondition().build()), + Arguments.of("GetterSetter.java", MethodDeclaration.class, PatternRepository.setterMethod().build())); + } + + @ParameterizedTest + @MethodSource("providePairs") + void testMatch(String filename, Class rootType, SimpleGraphPattern pattern) throws InterruptedException { + File file = new File(baseDirectory, filename); + try { + CpgAdapter cpgAdapter = new CpgAdapter(false, false, true); + cpgAdapter.clearTransformations(); + TranslationResult graph = cpgAdapter.translate(Set.of(file)); + CpgIsomorphismDetector detector = new CpgIsomorphismDetector(); + detector.loadGraph(graph); + List rootCandidates = detector.getNodesOfType(rootType); + + assertTrue(rootCandidates.stream().anyMatch(candidate -> { + List matches = pattern.recursiveMatch(candidate); + if (matches.isEmpty()) { + return false; + } + LOGGER.info("Mapping contained %d nodes.".formatted(matches.getFirst().getSize())); + return true; + })); + } catch (ParsingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/AbstractInterpretationTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/AbstractInterpretationTest.java new file mode 100644 index 0000000000..a45d51a777 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/AbstractInterpretationTest.java @@ -0,0 +1,273 @@ +package de.jplag.java_cpg.ai; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import de.fraunhofer.aisec.cpg.*; +import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage; +import de.fraunhofer.aisec.cpg.graph.Component; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.passes.*; +import de.jplag.ParsingException; +import de.jplag.java_cpg.ai.variables.VariableStore; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.numbers.IntValue; +import de.jplag.java_cpg.passes.*; + +import kotlin.jvm.JvmClassMappingKt; +import kotlin.reflect.KClass; + +/** + * Test that uses the CPG library and the existing java-cpg code. + * @author ujiqk + * @version 1.0 + */ +@Disabled +class AbstractInterpretationTest { + + /** + * a simple test with the main function only + */ + @Test + void testSimple() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple"); + JavaObject main = getMainObject(interpretation); + assertFalse(((IntValue) main.accessField("result")).getInformation()); + assertEquals(100, ((IntValue) main.accessField("result2")).getValue()); + } + + /** + * a simple test with the main function calling another function. + */ + @Test + void testSimple2() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple2"); + JavaObject main = getMainObject(interpretation); + assertFalse(((IntValue) main.accessField("result")).getInformation()); + assertFalse(((IntValue) main.accessField("result2")).getInformation()); + } + + /** + * a slightly more complex test with the main function calling other functions. with for loop and throw exception. + */ + @Test + void testSimple3() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple3"); + JavaObject main = getMainObject(interpretation); + assertEquals(1, ((IntValue) main.accessField("result")).getValue()); + assertEquals(2, ((IntValue) main.accessField("result2")).getValue()); + } + + /** + * simple switch test + */ + @Test + void testSwitch() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/switch"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertFalse(((IntValue) main.accessField("result")).getInformation()); // z + assertEquals(100, ((IntValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple switch test + */ + @Test + void testSwitch2() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/switch2"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertFalse(((IntValue) main.accessField("result")).getInformation()); // z + assertEquals(100, ((IntValue) main.accessField("result2")).getValue()); // y + } + + /** + * simplest loop test + */ + @Test + void testLoop() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/loop"); + JavaObject main = getMainObject(interpretation); + assertTrue(((IntValue) main.accessField("result")).getInformation()); + assertFalse(((IntValue) main.accessField("result2")).getInformation()); + } + + /** + * simplest for each loop test + */ + @Test + void testForEach() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/forEach"); + JavaObject main = getMainObject(interpretation); + assertFalse(((IntValue) main.accessField("result")).getInformation()); // z + assertEquals(100, ((IntValue) main.accessField("result2")).getValue()); // y + } + + /** + * nondeterministic test! (completes sometimes in debug mode) test creating a new class instance + */ + @Test + void testNewClass() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/new"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * test if without else + */ + @Test + void testIf() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/if"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((IntValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((IntValue) main.accessField("result2")).getValue()); // y + } + + /** + * test undetermined exception throw + */ + @Test + void testException() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/exception"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((IntValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((IntValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple enum test + */ + @Test + @Disabled + void testEnum() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/enum"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((IntValue) main.accessField("result")).getValue()); + assertFalse(((IntValue) main.accessField("result2")).getInformation()); + } + + /** + * simple hashmap test + */ + @Test + void testHashMap() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/map"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + // assertEquals(1, ((IntValue) main.accessField("result")).getValue()); //ToDo + // assertEquals(2, ((IntValue) main.accessField("result")).getValue()); + } + + @Test + void testQueensFarming() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/complex"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + @Test + void testQueensFarming2() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/complex2"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * simple try/catch test + */ + @Test + void testTryCatch() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/try"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertEquals(400, ((IntValue) main.accessField("result")).getValue()); // z + assertEquals(101, ((IntValue) main.accessField("result2")).getValue()); // y + } + + /** + * a simple try /catch test with throw inside called method + */ + @Test + void testTryCatch2() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/try2"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertEquals(250, ((IntValue) main.accessField("result")).getValue()); // z + assertEquals(200, ((IntValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple try/catch test with nothing thrown + */ + @Test + void testTryCatch3() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/try3"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertEquals(400, ((IntValue) main.accessField("result")).getValue()); // z + assertEquals(200, ((IntValue) main.accessField("result2")).getValue()); // y + } + + private TranslationResult translate(@NotNull Set files) throws ParsingException, InterruptedException { + InferenceConfiguration inferenceConfiguration = InferenceConfiguration.builder().inferRecords(true).inferDfgForUnresolvedCalls(true).build(); + TranslationResult translationResult; + try { + TranslationConfiguration.Builder configBuilder = new TranslationConfiguration.Builder().inferenceConfiguration(inferenceConfiguration) + .sourceLocations(files.toArray(new File[] {})).registerLanguage(new JavaLanguage()); + List>> passClasses = new ArrayList<>(List.of(TypeResolver.class, TypeHierarchyResolver.class, + JavaExternalTypeHierarchyResolver.class, JavaImportResolver.class, ImportResolver.class, SymbolResolver.class, + PrepareTransformationPass.class, FixAstPass.class, DynamicInvokeResolver.class, FilenameMapper.class, ReplaceCallCastPass.class, + AstTransformationPass.class, EvaluationOrderGraphPass.class, ControlDependenceGraphPass.class, ProgramDependenceGraphPass.class, + DfgSortPass.class, CpgTransformationPass.class)); + for (Class> passClass : passClasses) { + configBuilder.registerPass(getKClass(passClass)); + } + translationResult = TranslationManager.builder().config(configBuilder.build()).build().analyze().get(); + } catch (ExecutionException | ConfigurationException e) { + throw new ParsingException(List.copyOf(files).getFirst(), e); + } + return translationResult; + } + + @NotNull + private > KClass getKClass(Class javaPassClass) { + return JvmClassMappingKt.getKotlinClass(javaPassClass); + } + + private JavaObject getMainObject(@NotNull AbstractInterpretation interpretation) { + VariableStore variableStore = interpretation.getVariables(); + return (JavaObject) variableStore.getVariable("Main").getValue(); + } + + @NotNull + private AbstractInterpretation interpretFromResource(String resourceDir) throws ParsingException, InterruptedException { + ClassLoader classLoader = getClass().getClassLoader(); + File submissionsRoot = new File(Objects.requireNonNull(classLoader.getResource(resourceDir)).getFile()); + Set submissionDirectories = Set.of(submissionsRoot); + TranslationResult result = translate(submissionDirectories); + AbstractInterpretation interpretation = new AbstractInterpretation(new VisitedLinesRecorder(), true); + + Component comp = result.getComponents().getFirst(); + for (TranslationUnitDeclaration translationUnit : comp.getTranslationUnits()) { + Assertions.assertNotNull(translationUnit.getName().getParent()); + if (translationUnit.getName().getParent().getLocalName().endsWith("Main")) { + interpretation.runMain(translationUnit); + } + } + return interpretation; + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionIntervalTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionIntervalTest.java new file mode 100644 index 0000000000..f670f60572 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionIntervalTest.java @@ -0,0 +1,181 @@ +package de.jplag.java_cpg.ai; + +import static de.jplag.java_cpg.ai.DeadCodeDetectionTest.getMainObject; +import static de.jplag.java_cpg.ai.DeadCodeDetectionTest.interpretFromResource; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import de.jplag.ParsingException; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.numbers.FloatSetValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; +import de.jplag.java_cpg.ai.variables.values.numbers.IntSetValue; + +/** + * Test that only uses the CPG library. Specifically tests different integer interval analyses. + * @author ujiqk + * @version 1.0 + */ +class DeadCodeDetectionIntervalTest { + + /** + * A simple test with the main function only. Using integer interval analysis. + */ + @Test + void testSimpleInterval() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.INTERVALS); + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); + } + + @Test + void testSimpleSetInterval() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.SET); + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); + } + + /** + * a simple test with the main function calling another function. + */ + @Test + void testSimple2Interval() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.INTERVALS); + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple2"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + } + + /** + * a slightly more complex test with the main function calling other functions. with for loop and throw exception. + */ + @Test + void testSimple3() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.INTERVALS); + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple3"); + JavaObject main = getMainObject(interpretation); + assertEquals(1, ((INumberValue) main.accessField("result")).getValue()); + assertEquals(2, ((INumberValue) main.accessField("result2")).getValue()); + } + + /** + * test if without else + */ + @Test + void testIf() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.INTERVALS); + AbstractInterpretation interpretation = interpretFromResource("java/ai/if"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * Test the programming course final project: QueensFarming + */ + @Test + void testQueensFarming() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.INTERVALS); + AbstractInterpretation interpretation = interpretFromResource("java/ai/complex"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * simplest loop test + */ + @Test + void testLoop() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.INTERVALS); + AbstractInterpretation interpretation = interpretFromResource("java/ai/loop"); + JavaObject main = getMainObject(interpretation); + assertEquals(500, ((INumberValue) main.accessField("result")).getValue()); // z + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + assertFalse(((INumberValue) main.accessField("result3")).getInformation()); + } + + /** + * simplest test of nested loop + */ + @Test + void testLoop2x() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.INTERVALS); + AbstractInterpretation interpretation = interpretFromResource("java/ai/loopx2"); + JavaObject main = getMainObject(interpretation); + assertEquals(500, ((INumberValue) main.accessField("result")).getValue()); // z + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + assertFalse(((INumberValue) main.accessField("result3")).getInformation()); + } + + @Test + void testInterval() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.SET); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/interval"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); + assertEquals(110, ((IntSetValue) main.accessField("result")).getIntervals().getFirst().getLowerBound()); + assertEquals(210, ((IntSetValue) main.accessField("result")).getIntervals().getFirst().getUpperBound()); + assertEquals(161, ((INumberValue) main.accessField("result2")).getValue()); + assertFalse(((INumberValue) main.accessField("result3")).getInformation()); + assertEquals(150, ((IntSetValue) main.accessField("result3")).getIntervals().getFirst().getLowerBound()); + assertEquals(450, ((IntSetValue) main.accessField("result3")).getIntervals().getFirst().getUpperBound()); + assertFalse(((INumberValue) main.accessField("result4")).getInformation()); + assertEquals(501, ((IntSetValue) main.accessField("result4")).getIntervals().getFirst().getLowerBound()); + assertEquals(2501, ((IntSetValue) main.accessField("result4")).getIntervals().getFirst().getUpperBound()); + } + + @Test + void testMultipleInterval() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.SET); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/intervalMulti"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); + assertEquals(0, ((IntSetValue) main.accessField("result")).getIntervals().getFirst().getLowerBound()); + assertEquals(Integer.MAX_VALUE, ((IntSetValue) main.accessField("result")).getIntervals().getFirst().getUpperBound()); + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + assertEquals(2, ((IntSetValue) main.accessField("result2")).getIntervals().size()); // y + assertEquals(10, ((IntSetValue) main.accessField("result2")).getIntervals().getFirst().getLowerBound()); + assertEquals(50, ((IntSetValue) main.accessField("result2")).getIntervals().getFirst().getUpperBound()); + assertEquals(200, ((IntSetValue) main.accessField("result2")).getIntervals().getLast().getLowerBound()); + assertEquals(300, ((IntSetValue) main.accessField("result2")).getIntervals().getLast().getUpperBound()); + assertFalse(((INumberValue) main.accessField("result3")).getInformation()); + assertEquals(11, ((IntSetValue) main.accessField("result3")).getIntervals().getFirst().getLowerBound()); + assertEquals(Integer.MAX_VALUE, ((IntSetValue) main.accessField("result3")).getIntervals().getFirst().getUpperBound()); + assertFalse(((INumberValue) main.accessField("result4")).getInformation()); + assertEquals(2, ((IntSetValue) main.accessField("result4")).getIntervals().size()); + assertEquals(20, ((IntSetValue) main.accessField("result4")).getIntervals().getFirst().getLowerBound()); + assertEquals(100, ((IntSetValue) main.accessField("result4")).getIntervals().getFirst().getUpperBound()); + assertEquals(400, ((IntSetValue) main.accessField("result4")).getIntervals().getLast().getLowerBound()); + assertEquals(600, ((IntSetValue) main.accessField("result4")).getIntervals().getLast().getUpperBound()); + } + + @Test + void testIntervalDouble() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.SET); + Value.setUsedFloatAiType(FloatAiType.SET); + AbstractInterpretation interpretation = interpretFromResource("java/ai/intervalDouble"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); + assertEquals(60.5f, ((FloatSetValue) main.accessField("result")).getIntervals().getFirst().getLowerBound(), 0.0001); + assertEquals(110.5, ((FloatSetValue) main.accessField("result")).getIntervals().getFirst().getUpperBound(), 0.0001); + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + assertEquals(20.65f, ((FloatSetValue) main.accessField("result2")).getIntervals().getFirst().getLowerBound(), 0.0001); + assertEquals(20.9f, ((FloatSetValue) main.accessField("result2")).getIntervals().getFirst().getUpperBound(), 0.0001); + assertFalse(((INumberValue) main.accessField("result3")).getInformation()); + assertEquals(37.5f, ((FloatSetValue) main.accessField("result3")).getIntervals().getFirst().getLowerBound(), 0.0001); + assertEquals(112.5f, ((FloatSetValue) main.accessField("result3")).getIntervals().getFirst().getUpperBound(), 0.0001); + assertFalse(((INumberValue) main.accessField("result4")).getInformation()); + assertEquals(10.3f, ((FloatSetValue) main.accessField("result4")).getIntervals().getFirst().getLowerBound(), 0.0001); + assertEquals(1010.3f, ((FloatSetValue) main.accessField("result4")).getIntervals().getFirst().getUpperBound(), 0.0001); + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionStringTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionStringTest.java new file mode 100644 index 0000000000..ae22b2af16 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionStringTest.java @@ -0,0 +1,81 @@ +package de.jplag.java_cpg.ai; + +import static de.jplag.java_cpg.ai.DeadCodeDetectionTest.getMainObject; +import static de.jplag.java_cpg.ai.DeadCodeDetectionTest.interpretFromResource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import de.jplag.ParsingException; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.string.StringCharInclValue; +import de.jplag.java_cpg.ai.variables.values.string.StringRegexValue; +import de.jplag.java_cpg.ai.variables.values.string.regex.RegexChar; + +/** + * Test that only uses the CPG library. Specifically, tests different string analyses. + * @author ujiqk + * @version 1.0 + */ +class DeadCodeDetectionStringTest { + + /** + * simple test for character inclusion string analysis. + */ + @Test + void testString() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + Value.setUsedStringAiType(StringAiType.CHAR_INCLUSION); + AbstractInterpretation interpretation = interpretFromResource("java/ai/string"); + JavaObject main = getMainObject(interpretation); + assertEquals(new HashSet<>(Set.of(' ', 'D', 'e', 'h', 'J', 'l', ',', 'n', 'o')), + ((StringCharInclValue) main.accessField("result")).getCertainContained()); + assertEquals(new HashSet<>(Set.of('!', 'c', 'W', 'H', 'm')), ((StringCharInclValue) main.accessField("result")).getMaybeContained()); + assertEquals(new HashSet<>(Set.of('J', 'O', 'H', 'N', ' ', 'D', 'E')), + ((StringCharInclValue) main.accessField("result2")).getCertainContained()); + assertEquals(new HashSet<>(Set.of()), ((StringCharInclValue) main.accessField("result2")).getMaybeContained()); + } + + /** + * simple test for regex string analysis. + */ + @Test + void testRegexString() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + Value.setUsedStringAiType(StringAiType.REGEX); + AbstractInterpretation interpretation = interpretFromResource("java/ai/string"); + JavaObject main = getMainObject(interpretation); + assertEquals( + new ArrayList<>(List.of(new RegexChar('H'), new RegexChar('e'), new RegexChar('l'), new RegexChar('l'), new RegexChar('o'), + new RegexChar(','), new RegexChar(' '), new RegexChar('J'), new RegexChar('o'), new RegexChar('h'), new RegexChar('n'), + new RegexChar(' '), new RegexChar('D'), new RegexChar('o'), new RegexChar('e'), new RegexChar('!'))), + ((StringRegexValue) main.accessField("result")).getContentRegex()); + + assertEquals( + new ArrayList<>(List.of(new RegexChar('J'), new RegexChar('O'), new RegexChar('H'), new RegexChar('N'), new RegexChar(' '), + new RegexChar('D'), new RegexChar('O'), new RegexChar('E'))), + ((StringRegexValue) main.accessField("result2")).getContentRegex()); + } + + @Test + @Disabled + void testRegexStringComplex() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + Value.setUsedStringAiType(StringAiType.REGEX); + AbstractInterpretation interpretation = interpretFromResource("java/ai/stringComplex"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionTest.java new file mode 100644 index 0000000000..11e81b6feb --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionTest.java @@ -0,0 +1,573 @@ +package de.jplag.java_cpg.ai; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import de.fraunhofer.aisec.cpg.ConfigurationException; +import de.fraunhofer.aisec.cpg.InferenceConfiguration; +import de.fraunhofer.aisec.cpg.TranslationConfiguration; +import de.fraunhofer.aisec.cpg.TranslationManager; +import de.fraunhofer.aisec.cpg.TranslationResult; +import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage; +import de.fraunhofer.aisec.cpg.graph.Component; +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration; +import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass; +import de.fraunhofer.aisec.cpg.passes.DFGPass; +import de.fraunhofer.aisec.cpg.passes.DynamicInvokeResolver; +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass; +import de.fraunhofer.aisec.cpg.passes.FilenameMapper; +import de.fraunhofer.aisec.cpg.passes.ImportResolver; +import de.fraunhofer.aisec.cpg.passes.JavaExternalTypeHierarchyResolver; +import de.fraunhofer.aisec.cpg.passes.JavaImportResolver; +import de.fraunhofer.aisec.cpg.passes.Pass; +import de.fraunhofer.aisec.cpg.passes.ProgramDependenceGraphPass; +import de.fraunhofer.aisec.cpg.passes.ReplaceCallCastPass; +import de.fraunhofer.aisec.cpg.passes.SymbolResolver; +import de.fraunhofer.aisec.cpg.passes.TypeHierarchyResolver; +import de.fraunhofer.aisec.cpg.passes.TypeResolver; +import de.jplag.ParsingException; +import de.jplag.java_cpg.ai.variables.VariableStore; +import de.jplag.java_cpg.ai.variables.values.JavaObject; +import de.jplag.java_cpg.ai.variables.values.Value; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +import kotlin.jvm.JvmClassMappingKt; +import kotlin.reflect.KClass; + +/** + * Test that only uses the CPG library. + * @author ujiqk + * @version 1.0 + */ +class DeadCodeDetectionTest { + + public static JavaObject getMainObject(@NotNull AbstractInterpretation interpretation) { + VariableStore variableStore = interpretation.getVariables(); + return (JavaObject) variableStore.getVariable("Main").getValue(); + } + + @NotNull + public static AbstractInterpretation interpretFromResource(String resourceDir) throws ParsingException, InterruptedException { + ClassLoader classLoader = DeadCodeDetectionTest.class.getClassLoader(); + File submissionsRoot = new File(Objects.requireNonNull(classLoader.getResource(resourceDir)).getFile()); + Set submissionDirectories = Set.of(submissionsRoot); + TranslationResult result = translate(submissionDirectories); + AbstractInterpretation interpretation = new AbstractInterpretation(new VisitedLinesRecorder(), true); + + Component comp = result.getComponents().getFirst(); + for (TranslationUnitDeclaration translationUnit : comp.getTranslationUnits()) { + Assertions.assertNotNull(translationUnit.getName().getParent()); + if (translationUnit.getName().getParent().getLocalName().endsWith("Main") || comp.getTranslationUnits().size() == 1) { + interpretation.runMain(translationUnit); + } + } + return interpretation; + } + + static TranslationResult translate(@NotNull Set files) throws ParsingException, InterruptedException { + InferenceConfiguration inferenceConfiguration = InferenceConfiguration.builder().inferRecords(true).inferDfgForUnresolvedCalls(true).build(); + TranslationResult translationResult; + try { + TranslationConfiguration.Builder configBuilder = new TranslationConfiguration.Builder().inferenceConfiguration(inferenceConfiguration) + .sourceLocations(files.toArray(new File[] {})).registerLanguage(new JavaLanguage()); + List>> passClasses = new ArrayList<>( + List.of(TypeResolver.class, TypeHierarchyResolver.class, JavaExternalTypeHierarchyResolver.class, JavaImportResolver.class, + ImportResolver.class, SymbolResolver.class, DynamicInvokeResolver.class, FilenameMapper.class, ReplaceCallCastPass.class, + EvaluationOrderGraphPass.class, ControlDependenceGraphPass.class, ProgramDependenceGraphPass.class, DFGPass.class)); + for (Class> passClass : passClasses) { + configBuilder.registerPass(getKClass(passClass)); + } + translationResult = TranslationManager.builder().config(configBuilder.build()).build().analyze().get(); + } catch (ExecutionException | ConfigurationException e) { + throw new ParsingException(List.copyOf(files).getFirst(), e); + } + return translationResult; + } + + @NotNull + private static > KClass getKClass(Class javaPassClass) { + return JvmClassMappingKt.getKotlinClass(javaPassClass); + } + + @NotNull + private static java.net.URI getURI(@NotNull AbstractInterpretation interpretation, @NotNull String fileName) { + VisitedLinesRecorder recorder = getVisitedLinesRecorder(interpretation); + var nonVisited = recorder.getNonVisitedLines(); + for (var uri : nonVisited.keySet()) { + if (uri.getPath().endsWith(fileName)) { + return uri; + } + } + throw new RuntimeException("URI not found for " + fileName); + } + + @NotNull + private static VisitedLinesRecorder getVisitedLinesRecorder(@NotNull AbstractInterpretation interpretation) { + try { + java.lang.reflect.Field field = AbstractInterpretation.class.getDeclaredField("visitedLinesRecorder"); + field.setAccessible(true); + return (VisitedLinesRecorder) field.get(interpretation); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * a simple test with the main function only + */ + @Test + void testSimple() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); + } + + /** + * a simple test with the main function calling another function. + */ + @Test + void testSimple2() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple2"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + } + + /** + * a slightly more complex test with the main function calling other functions. with for loop and throw exception. + */ + @Test + void testSimple3() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/simple3"); + JavaObject main = getMainObject(interpretation); + assertEquals(1, ((INumberValue) main.accessField("result")).getValue()); + assertEquals(2, ((INumberValue) main.accessField("result2")).getValue()); + } + + /** + * simple switch test + */ + @Test + void testSwitch() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/switch"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple switch test + */ + @Test + void testSwitch2() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/switch2"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * simplest loop test + */ + @Test + void testLoop() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/loop"); + JavaObject main = getMainObject(interpretation); + assertEquals(500, ((INumberValue) main.accessField("result")).getValue()); // z + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + assertFalse(((INumberValue) main.accessField("result3")).getInformation()); + } + + /** + * simplest for each loop test + */ + @Test + void testForEach() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/forEach"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); // z + assertTrue(((INumberValue) main.accessField("result2")).getInformation()); // y + } + + /** + * Test creating a new class instance. + */ + @Test + void testNewClass() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/new"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * test if without else + */ + @Test + void testIf() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/if"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * test if with an else and another if inside the else block. + */ + @Test + void testNestedIf() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/nestedIf"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(50, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * test if with else-if and else. + */ + @Test + void testIfElse() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/ifElse"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * test if with 2x else-if and else. + */ + @Test + void testIfElse2x() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/ifElse2"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * test if with || in condition + */ + @Test + void testIfOr() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/ifOr"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * test if with && and || in condition + */ + @Test + void testIfAnd() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/ifAnd"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * test undetermined exception throw + */ + @Test + void testException() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/exception"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple enum test + */ + @Test + void testEnum() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/enum"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + } + + /** + * simple hashmap test + */ + @Test + void testHashMap() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/map"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + // assertEquals(1, ((INumberValue) main.accessField("result")).getValue()); //ToDo + // assertEquals(2, ((INumberValue) main.accessField("result")).getValue()); + } + + /** + * simple HashSet/TreeSet test + */ + @Test + void testSet() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/set"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * Test the programming course final project: QueensFarming + */ + @Test + void testQueensFarming() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/complex"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * Test another programming course final project. + */ + @Test + void testTrafficSym() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/complex2"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * simple break statement test + */ + @Test + void testBreak() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/break"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + // ToDo + } + + /** + * simple try/catch test + */ + @Test + void testTryCatch() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/try"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(101, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * a simple try /catch test with throw inside called method + */ + @Test + void testTryCatch2() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/try2"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertEquals(250, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(200, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple try/catch test with nothing thrown + */ + @Test + void testTryCatch3() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/try3"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertEquals(400, ((INumberValue) main.accessField("result")).getValue()); // z + assertEquals(200, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple stream test + */ + @Test + void testStream() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/stream"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertEquals(100, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple array test + */ + @Test + void testArray() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/arrayInit"); + JavaObject main = getMainObject(interpretation); + assertEquals(24, ((INumberValue) main.accessField("result2")).getValue()); // y + } + + /** + * simple test for ConditionalExpressions (a?b:c). + */ + @Test + @Disabled + void testConditional() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/conditional"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * simple array test + */ + @Test + void testMCEinClassField() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/simpleMCEinClassField"); + JavaObject main = getMainObject(interpretation); + assertEquals(43, ((INumberValue) main.accessField("x")).getValue()); + } + + /** + * simple test for while with variable assignment in the condition. + */ + @Test + void testWhileAssign() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/whileAssign"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); + } + + /** + * simple test for while inside while. + */ + @Test + void testNestedWhile() throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + Value.setUsedStringAiType(StringAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/ai/nestedWhile"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); + assertEquals(1, ((INumberValue) main.accessField("result2")).getValue()); + } + + /** + * a simple test for a return statement inside if. + */ + @Test + @Disabled + void testReturnInIf() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/returnInIf"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); // x + assertEquals(200, ((INumberValue) main.accessField("result2")).getValue()); // 2*y + assertEquals(500, ((INumberValue) main.accessField("result3")).getValue()); // z + } + + /** + * a simple test for return statements only inside if. + */ + @Test + @Disabled + void testReturnInIf2() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/returnInIf2"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); // x + assertEquals(200, ((INumberValue) main.accessField("result2")).getValue()); // 2*y + assertEquals(500, ((INumberValue) main.accessField("result3")).getValue()); // z + } + + /** + * a simple test for return statements inside two nested ifs. + */ + @Test + @Disabled + void testReturnInIf2x() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/returnInIf2x"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); // x + assertEquals(700, ((INumberValue) main.accessField("result2")).getValue()); // 2*y + assertEquals(500, ((INumberValue) main.accessField("result3")).getValue()); // z + } + + /** + * a simple test for a return statement inside a while loop. + */ + @Test + @Disabled + void testReturnInWhile() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/returnInWhile"); + JavaObject main = getMainObject(interpretation); + assertFalse(((INumberValue) main.accessField("result")).getInformation()); // x + assertFalse(((INumberValue) main.accessField("result2")).getInformation()); // y + assertEquals(25, ((INumberValue) main.accessField("result3")).getValue()); // z + } + + @Test + void testDeadCode2() throws ParsingException, InterruptedException { // code after return + AbstractInterpretation interpretation = interpretFromResource("java/ai/deadCode2"); + JavaObject main = getMainObject(interpretation); + assertEquals(1, ((INumberValue) main.accessField("result")).getValue()); + + VisitedLinesRecorder recorder = getVisitedLinesRecorder(interpretation); + assertTrue(recorder.checkIfCompletelyDead(getURI(interpretation, "Main.java"), 9, 9)); + } + + @Test + void testDeadCode3() throws ParsingException, InterruptedException { // unused method + AbstractInterpretation interpretation = interpretFromResource("java/ai/deadCode3"); + JavaObject main = getMainObject(interpretation); + assertEquals(1, ((INumberValue) main.accessField("result")).getValue()); + + VisitedLinesRecorder recorder = getVisitedLinesRecorder(interpretation); + assertTrue(recorder.checkIfCompletelyDead(getURI(interpretation, "Main.java"), 10, 12)); + } + + @Test + void testDeadCode5() throws ParsingException, InterruptedException { // dead class + AbstractInterpretation interpretation = interpretFromResource("java/ai/deadCode5"); + JavaObject main = getMainObject(interpretation); + assertEquals(1, ((INumberValue) main.accessField("result")).getValue()); + + VisitedLinesRecorder recorder = getVisitedLinesRecorder(interpretation); + // DeadClass on lines 11-15 + assertTrue(recorder.checkIfCompletelyDead(getURI(interpretation, "Main.java"), 11, 15)); + } + + @Test + void testDeadCode11() throws ParsingException, InterruptedException { // dead code in constructor and dead class + AbstractInterpretation interpretation = interpretFromResource("java/ai/deadCode11"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + VisitedLinesRecorder recorder = getVisitedLinesRecorder(interpretation); + // DeadClass on lines 20-32 + assertTrue(recorder.checkIfCompletelyDead(getURI(interpretation, "Main.java"), 20, 32)); + } + + @Test + void testInheritance() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/inheritance"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/ProgpediaTests.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/ProgpediaTests.java new file mode 100644 index 0000000000..f8a77a7193 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/ProgpediaTests.java @@ -0,0 +1,162 @@ +package de.jplag.java_cpg.ai; + +import static de.jplag.java_cpg.ai.DeadCodeDetectionTest.translate; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.File; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import de.fraunhofer.aisec.cpg.TranslationResult; +import de.fraunhofer.aisec.cpg.graph.Component; +import de.jplag.ParsingException; +import de.jplag.java_cpg.ai.variables.VariableStore; +import de.jplag.java_cpg.ai.variables.values.Value; + +/** + * Tests from the Progpedia data set. + *

+ * José Carlos Paiva, José Paulo Leal, and Álvaro Figueira. PROGpedia. Dec. 2022. 10.5281/zenodo.7449056 + * zenodo.org/records/7449056 (visited on 11/04/2025). + * @author ujiqk + * @version 1.0 + */ +public class ProgpediaTests { + + @NotNull + private static AbstractInterpretation interpretFromResource(String resourceDir) throws ParsingException, InterruptedException { + ClassLoader classLoader = DeadCodeDetectionTest.class.getClassLoader(); + File submissionsRoot = new File(Objects.requireNonNull(classLoader.getResource(resourceDir)).getFile()); + Set submissionDirectories = Set.of(submissionsRoot); + TranslationResult result = translate(submissionDirectories); + AbstractInterpretation interpretation = new AbstractInterpretation(new VisitedLinesRecorder(), true); + + assert result.getComponents().size() == 1; + Component comp = result.getComponents().getFirst(); + assert comp.getTranslationUnits().size() == 1; + interpretation.runMain(comp.getTranslationUnits().getFirst()); + return interpretation; + } + + @NotNull + public static Stream progpediaResourceDirs() { + return Stream + .of("00000006", "00000016", "00000018", "00000019", "00000021", "00000022", "00000023", "00000034", "00000035", "00000039", + "00000042", "00000043", "00000045", "00000048", "00000053", "00000056") + .flatMap(problemId -> Stream.of("ACCEPTED", "WRONG_ANSWER").flatMap(category -> getResourceDirsForProblem(problemId, category))); + } + + @NotNull + public static Stream progpediaFiles() { + return progpediaResourceDirs().flatMap(dir -> { + ClassLoader classLoader = DeadCodeDetectionTest.class.getClassLoader(); + java.net.URL url = classLoader.getResource(dir); + if (url == null) + return Stream.empty(); + File directory = new File(url.getFile()); + File[] javaFiles = directory.listFiles((d, name) -> name.endsWith(".java")); + if (javaFiles == null) + return Stream.empty(); + return Arrays.stream(javaFiles).map(f -> dir + f.getName()); + }).map(s -> s.substring(5)); // remove first "java/" + } + + private static Stream getResourceDirsForProblem(String problemId, String category) { + ClassLoader classLoader = DeadCodeDetectionTest.class.getClassLoader(); + java.net.URL url = classLoader.getResource("java/progpedia/" + problemId + "/" + category); + if (url == null) + return Stream.empty(); + File base = new File(Objects.requireNonNull(url).getFile()); + File[] dirs = base.listFiles(File::isDirectory); + if (dirs == null) + return Stream.empty(); + return Arrays.stream(dirs).map(f -> "java/progpedia/" + problemId + "/" + category + "/" + f.getName() + "/"); + } + + @ParameterizedTest + @Disabled + @MethodSource("progpediaResourceDirs") + void testProgpedia(String resourceDir) throws ParsingException, InterruptedException { + Value.setUsedIntAiType(IntAiType.DEFAULT); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + Value.setUsedStringAiType(StringAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource(resourceDir); + VariableStore variableStore = interpretation.getVariables(); + assertNotNull(variableStore); + } + + @Test + @Disabled + void testSingle1() throws ParsingException, InterruptedException { // for(Node cursor=invert.top;cursor!=null;cursor=cursor.next) + Value.setUsedIntAiType(IntAiType.DEFAULT); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + Value.setUsedStringAiType(StringAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource("java/progpedia/00000006/ACCEPTED/00130_00001"); + VariableStore variableStore = interpretation.getVariables(); + assertNotNull(variableStore); + } + + @Test + @Disabled + void testSingle2() throws ParsingException, InterruptedException { // for (int k=0, i=0; i getArguments() { + return Stream.of(Arguments.of("singleUseVariable", new GraphTransformation[] {inlineSingleUseVariable}), + Arguments.of("constantClass", new GraphTransformation[] {moveConstantToOnlyUsingClass, removeLibraryRecord, removeLibraryField}), + Arguments.of("for2While", new GraphTransformation[] {forStatementToWhileStatement}), + Arguments.of("negatedIf", new GraphTransformation[] {ifWithNegatedConditionResolution}), + Arguments.of("unusedVariables", new GraphTransformation[] {removeUnusedVariableDeclaration, removeEmptyDeclarationStatement}), + Arguments.of("dfgLinearization", new GraphTransformation[] {})); + } + + @ParameterizedTest + @MethodSource("getArguments") + void testPlagiarismPair(String submissionsPath, GraphTransformation[] transformation) { + language.resetTransformations(); + language.addTransformations(transformation); + + File root = new File(baseDirectory, submissionsPath); + File[] content = root.listFiles(); + Assertions.assertNotNull(content); + List> submissions = new ArrayList<>(); + if (Arrays.stream(content).anyMatch(File::isDirectory)) { + Arrays.stream(content).map(submissionDir -> { + try (Stream stream = Files.walk(submissionDir.toPath())) { + return stream.map(Path::toFile).filter(File::isFile).collect(Collectors.toSet()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).forEach(submissions::add); + } else { + // single-file submission + Arrays.stream(content).map(Set::of).forEach(submissions::add); + } + + List> results = new ArrayList<>(); + for (Set submissionFiles : submissions) { + try { + List tokens = language.parse(submissionFiles, true); + results.add(tokens); + } catch (ParsingException e) { + throw new RuntimeException(e); + } + } + + for (int i = 0; i < results.size(); i++) { + for (int j = i + 1; j < results.size(); j++) { + Assertions.assertIterableEquals(results.get(i), results.get(j)); + } + } + } + + @AfterEach + public void resetTransformations() { + language.resetTransformations(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/GetterSetter.java b/languages/java-cpg/src/test/resources/java/GetterSetter.java new file mode 100644 index 0000000000..f12d80f4cc --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/GetterSetter.java @@ -0,0 +1,14 @@ +package de.jplag.java_cpg; + +public class GetterSetter { + + private GetterSetter instance; + + public GetterSetter getInstance() { + return instance; + } + + public void setInstance(GetterSetter instance) { + this.instance = instance; + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/IfElseWithNegatedCondition.java b/languages/java-cpg/src/test/resources/java/IfElseWithNegatedCondition.java new file mode 100644 index 0000000000..3f62e13cd2 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/IfElseWithNegatedCondition.java @@ -0,0 +1,14 @@ +class IfElseWithNegatedCondition { + + public void function(String string) { + if (!(string.length() == 10)) { + System.out.println("'%s' is NOT of length 10.".formatted(string)); + } else { + System.out.println("'%s' IS of length 10.".formatted(string)); + } + } + + public static void main(String[] args) { + new IfElseWithNegatedCondition().function("Fellow burled"); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/UnusedVariableDeclaration.java b/languages/java-cpg/src/test/resources/java/UnusedVariableDeclaration.java new file mode 100644 index 0000000000..e3c5f4d10f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/UnusedVariableDeclaration.java @@ -0,0 +1,11 @@ + +class UnusedVariableDeclaration { + + private int multiply(int factor1, int factor2) { + int product = 0; + for (int i = 0; factor2 > 0; factor2--) { // <- i is not used + product += factor1; + } + return product; + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/VariableDeclaration.java b/languages/java-cpg/src/test/resources/java/VariableDeclaration.java new file mode 100644 index 0000000000..7374947bd3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/VariableDeclaration.java @@ -0,0 +1,8 @@ +public class VariableDeclaration { + + public static void main(String[] args) { + // This produces a non-tree sourceGraph AST structure + String string = "Hello World!"; + System.out.println(string); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/ai/arrayInit/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/arrayInit/Submission-01/Main.java new file mode 100644 index 0000000000..cc8c17ed45 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/arrayInit/Submission-01/Main.java @@ -0,0 +1,30 @@ +package edu.kit.informatik; + +public final class Main { + + private static int result; + private static int result2; + private int[] array; + private int[] array3 = {1}; + private int[] array2 = new int[10]; + private int[] array4 = new int[]{1, 2, 3, 4}; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int x = Integer.parseInt(args[0]); + int z = 500; + int y = 5; + + array = new int[x]; + + array[0] = z; + array2[y] = 24; + + result = array[0]; + result2 = array2[5]; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/break/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/break/Submission-01/Main.java new file mode 100644 index 0000000000..e9a9a299eb --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/break/Submission-01/Main.java @@ -0,0 +1,29 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + + int z = 500; + int y = 100; + + while (true) { + z = z - 100; + if (z == 400) { + break; + } + y = y + 100; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/Main.java new file mode 100644 index 0000000000..2b2f5bdf26 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/Main.java @@ -0,0 +1,35 @@ +package edu.kit.informatik; + +import edu.kit.informatik.ui.StartUserInterface; + +/** + * Main Klasse eines "Queens Farming" Spieles das über die Kommandozeile gespielt werden kann. + * + * @author ujiqk + * @version 1.0 + */ +public final class Main { + + private static final String ERROR_ARGUMENTS_NOT_SUPPORTED = "Error: Commandline arguments not supported"; + + private Main() { + throw new IllegalStateException(); + } + + /** + * Startet das Spiel + * + * @param args Kommandozeilenparameter muss leer sein. + */ + public static void main(String[] args) { + + + if (args.length != 0) { + //System.err.println(ERROR_ARGUMENTS_NOT_SUPPORTED); + return; + } + + //Spiel starten + StartUserInterface ui1 = new StartUserInterface(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/LogicException.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/LogicException.java new file mode 100644 index 0000000000..23c5c949fa --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/LogicException.java @@ -0,0 +1,17 @@ +package edu.kit.informatik.logik; + +/** Logischer Fehler in der Spiellogik + * @author ujiqk + * @version 1.0 */ +public class LogicException extends Exception { + /** Standardkonstruktor + */ + public LogicException() { } + + /** Standardkonstruktor mit Errornachricht + * @param message Die Errornachricht als String + */ + public LogicException(String message) { + super(message); + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Market.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Market.java new file mode 100644 index 0000000000..c90a5bd7b8 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Market.java @@ -0,0 +1,143 @@ +package edu.kit.informatik.logik; + +import java.util.ArrayList; +import java.util.List; + +/** Klasse die einen Markt des "Queens Farming" Spiels implementiert. + * Er legt die dynamischen Preise für Gemüse fest. + * + * @author ujiqk + * @version 1.0 */ + +public class Market { + + private static final int[][] MUSH_CAR_PRICE = {{12, 15, 16, 17, 20}, {3, 2, 2, 2, 1}}; + private static final int[][] TOM_SAL_PRICE = {{3, 5, 6, 7, 9}, {6, 5, 4, 3, 2}}; + private int mushCarIndicator; //Die Indikatoren, wo im Array der Preis gerade steht + private int tomSalIndikator; + private List updateWaitList; //List um sich verkaufte Gemüse in einem Zug zu merken + + /** Konstruktor: Initialisiert einen Standardmarkt. + */ + public Market() { + mushCarIndicator = 2; + tomSalIndikator = 2; + updateWaitList = new ArrayList<>(); + } + + /** Speichert Gemüse zur späteren Marktaktualisierung ab. + * @param vegetables Liste an Gemüsen. Darf nicht null sein. + */ + public void soldThisTurn(List vegetables) { + updateWaitList.addAll(vegetables); + } + + /** + * Aktualisiert den Markt mithilfe der vorher abgespeicherten Gemüse. + * Verändert dabei die Preise der Gemüse + */ + public void update() { + if (updateWaitList == null) { + return; + } + //Alle Einträge durchgehen und doppelte entfernen + int index = 0; + while (index < updateWaitList.size() - 1) { + int index2 = 0; + Vegetable entry = updateWaitList.get(index); + while ( index2 < updateWaitList.size()) { + if (entry == pair(updateWaitList.get(index2))) { + updateWaitList.remove(index); //beide Elemente entfernen + updateWaitList.remove(Math.abs(index2 - 1)); + index = Math.max(0, (index - 1)); //Index anpassen, da Elemente entfernt + break; //nach einem gefundenem aufhören + } + index2++; + } + index++; + } + //pro zwei übrige die Indikatoren verschieben + while (!updateWaitList.isEmpty()) { + Vegetable entry = updateWaitList.get(0); + updateWaitList.remove(0); + //Falls ein weiterer Eintrag existiert → entfernen, Preis updaten + for (int i = 0; i < updateWaitList.size(); i++) { + if (updateWaitList.get(i) == entry) { + adjustPrice(entry); + updateWaitList.remove(i); + break; + } + } + } + updateWaitList = new ArrayList<>(); + } + + /** Gibt den aktuellen preis eines Gemüses zurück. + * @param vegetable das Gemüse + * @return Der Preis des Gemüses als Ganzzahl + * @throws IllegalArgumentException wenn das Gemüse null ist + */ + public int getPrice(Vegetable vegetable) { + //Preise sind in der Array-struktur festgeschrieben + if (vegetable == Vegetable.MUSHROOM) { + return MUSH_CAR_PRICE[0][mushCarIndicator]; + } + else if (vegetable == Vegetable.CARROT) { + return MUSH_CAR_PRICE[1][mushCarIndicator]; + } + else if (vegetable == Vegetable.TOMATO) { + return TOM_SAL_PRICE[0][tomSalIndikator]; + } + else if (vegetable == Vegetable.SALAD) { + return TOM_SAL_PRICE[1][tomSalIndikator]; + } + else { + throw new IllegalArgumentException(); + } + } + + private Vegetable pair(Vegetable vegetable) { + switch (vegetable) { + case SALAD -> { + return Vegetable.TOMATO; + } + case TOMATO -> { + return Vegetable.SALAD; + } + case MUSHROOM -> { + return Vegetable.CARROT; + } + case CARROT -> { + return Vegetable.MUSHROOM; + } + default -> throw new IllegalStateException(); + } + } + + private void adjustPrice(Vegetable vegetable) { + switch (vegetable) { + case CARROT -> { + if (mushCarIndicator < MUSH_CAR_PRICE[0].length - 1) { + mushCarIndicator++; + } + } + case MUSHROOM -> { + if (mushCarIndicator > 0) { + mushCarIndicator--; + } + } + case TOMATO -> { + if (tomSalIndikator > 0) { + tomSalIndikator--; + } + } + case SALAD -> { + if (tomSalIndikator < TOM_SAL_PRICE[0].length) { + tomSalIndikator++; + } + } + default -> throw new IllegalStateException(); + } + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Player.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Player.java new file mode 100644 index 0000000000..4e233e4e53 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Player.java @@ -0,0 +1,156 @@ +package edu.kit.informatik.logik; + +import edu.kit.informatik.logik.tiles.Barn; +import edu.kit.informatik.logik.tiles.Board; +import edu.kit.informatik.logik.tiles.Coordinates; +import edu.kit.informatik.logik.tiles.Tile; + +import java.util.Collections; +import java.util.Map; + +/** Klasse die einen Spieler/eine Spielerin des "Queens Farming" Spiels implementiert. + * Zusätzlich wird der Besitz des Spielers und sein Spielfeld implementiert + * + * @author ujiqk + * @version 1.0 */ +public class Player { + + private final String name; + private int gold; + private final int winGold; + + private final Board board; //Das Spielfeld + + /** Konstruktor der einen Standardspieler mit einem Standardspielfeld erstellt. + * @param name Der Name als String + * @param gold Der Goldanfangsbesitz. Eine Ganzzahl + * @param winGold Das Gold das zum Gewinnen erreicht werden muss. Eine Ganzzahl + */ + public Player(String name, int gold, int winGold) { + this.name = name; + this.gold = gold; + this.winGold = winGold; + //Spielfeld init + board = new Board(); + } + + /** Kauft dem Spieler eine neue Landkachel an den angegebenen Koordinaten. + * + * @param tile Die Kachel (Tile) die gekauft werden soll. Sollte nicht null sein. + * @param coordinates Die Koordinaten an denen gekauft werden soll. Sollte nicht null sein. + * @throws LogicException Wenn die Koordinaten nicht frei oder einen linken/rechten/unten Nachbar haben oder + * der Preis zu teuer ist. + */ + public void buyLand(Tile tile, Coordinates coordinates) throws LogicException { + int price = board.getPrice(coordinates); //LogicException, wenn die Koordinaten falsch sind + if (price <= gold) { + board.addTile(tile, coordinates); //LogicException, wenn die Koordinaten falsch sind + //Falls es funktioniert, Geld abziehen + gold -= price; + } + else { + throw new LogicException(); + } + } + + /** Getter für das Gold des Spielers + * @return das Gold als Ganzzahl + */ + public int getGold() { + return gold; + } + + /** Setter für das Gold des Spielers + * @param gold Eine Ganzzahl. Setzt das aktuelle Gold auf diesen Wert + */ + public void setGold(int gold) { + this.gold = gold; + } + + /** Fügt Gold zum aktuellen Gold des Spielers dazu. + * @param income Das zu hinzufügende Gold als Ganzzahl + */ + public void addGold(int income) { + gold += income; + } + + /** Prüft ob der Spieler mehr oder gleichviel Gold, wie zum Gewinnen notwendig ist, hat. + * @return True: der Spieler hat gewonnen; False: Er hat noch nicht gewonnen + */ + public boolean hasWon() { + return gold >= winGold; + } + + /** Getter für den Namen des Spielers + * @return Der Name als String + */ + public String getName() { + return name; + } + + /** Gibt alle wichtigen Besitztümer eines Spielers als 'Possessions' zurück. + * (Inkludiert nicht die Kacheln) + * @return Ein Possession Objekt mit den Informationen + */ + public Possessions getPossessions() { + Map barnContent = board.getBarn().getContent(); + //Alle Gemüse mit Anzahl 0 entfernen HashMap kann leer sein + barnContent.values().removeAll(Collections.singleton(0)); + return new Possessions( + barnContent, + gold, + board.getBarn().getCountdown()); + } + + /** Getter für das Spielfeld + * @return Das Spielfeld + */ + public Board getBoard() { + return board; + } + + /** Getter für nur die Scheune aus dem Spielfeld + * @return Die Scheune des Spielers + */ + public Barn getBarn() { + return board.getBarn(); + } + + /** Pflanzt ein Gemüse an den angegebenen Koordinaten an. + * Das Gemüse wird dabei aus der Scheune genommen + * @param coordinates Die Koordinaten an denen gepflanzt wird. Darf nicht null sein. + * @param vegetable das Gemüse was angebaut werden soll. Darf nicht null sein. + * @throws LogicException Falls der Spieler das Gemüse nicht besitzt oder + * auf den Koordinaten nicht angebaut werden kann. + */ + public void plant(Coordinates coordinates, Vegetable vegetable) throws LogicException { + board.plant(coordinates, vegetable); + } + + /** Erntet Gemüse von einem Feld auf dem Spielfeld + * @param coordinates Die Koordinaten an denen geerntet wird. Darf nicht null sein. + * @param number Die Anzahl die geerntet werden soll. + * @return Gibt das Gemüse (als Vegetable) zurück das geerntet wurde. + * @throws LogicException Wenn das Feld nicht existiert oder + * nicht die angegebene Anzahl auf dem Feld zum Ernten ist. + */ + public Vegetable harvest(Coordinates coordinates, int number) throws LogicException { + return board.harvest(coordinates, number); + } + + /** Lässt alle angebauten Gemüse auf dem Spielfeld des Spielers wachsen + * @return Eine Ganzzahl die angibt wie viele Gemüse gewachsen sind. + */ + public int growVegetables() { + return board.growCropAreas(); + } + + /** Lässt die Zeit in der Scheune des Spielers vergehen + * @return True: wenn die Scheune vergammelt ist, False: wenn nicht + */ + public boolean updateBarn() { + //True, wenn Gemüse schlecht wird + return board.getBarn().updateTimer(); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Possessions.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Possessions.java new file mode 100644 index 0000000000..a6d23b0801 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Possessions.java @@ -0,0 +1,12 @@ +package edu.kit.informatik.logik; + +import java.util.Map; + +/** Record der alle wichtigen Werte eines Spielers speichern kann. + * + * @param barnContent Die in der Scheune gespeicherten Gemüse + * @param gold Das aktuelle Gold + * @param spoilTime Die Zeit in Runden bis die Scheune verschimmelt + * @author ujiqk + * @version 1.0 */ +public record Possessions(Map barnContent, int gold, int spoilTime) { } diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/QueensFarming.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/QueensFarming.java new file mode 100644 index 0000000000..21d6c234d4 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/QueensFarming.java @@ -0,0 +1,289 @@ +package edu.kit.informatik.logik; + +import edu.kit.informatik.logik.tiles.Board; +import edu.kit.informatik.logik.tiles.Coordinates; +import edu.kit.informatik.logik.tiles.Deck; +import edu.kit.informatik.logik.tiles.Tile; +import edu.kit.informatik.logik.tiles.TileType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** Klasse die ein komplettes "Queens Farming" Spiel implementiert. + * + * @author ujiqk + * @version 1.0 */ +public class QueensFarming { + + //Array mit Spielern + private final Player[] players; + + //index des aktuellen Spielers + private int playersTurn; + + //Kartenstapel + private final Deck cardDeck; + + //Market + private final Market market; + + /** Konstruktor: Erzeugt ein neues Spiel + * @param playerNames Die Namen der Spieler in Spielreihenfolge als String-Array + * @param winGold Das zum Gewinnen nötige Gold als Ganzzahl + * @param startGold Das Startgold als Ganzzahl + * @param seed Der Seed zum Mischeln als Ganzzahl + */ + public QueensFarming(String[] playerNames, int winGold, int startGold, int seed) { + //Game init + market = new Market(); + //Kartenstapel erschaffen + cardDeck = new Deck(playerNames.length); + cardDeck.cardShuffle(seed); + //Spieler erschaffen + players = new Player[playerNames.length]; + for (int i = 0; i < playerNames.length; i++) { + players[i] = new Player(playerNames[i], startGold, winGold ); + } + playersTurn = 0; + } + + /** Gibt die Spieler an, die gewonnen haben. + * @return Ein String-array mit den Spielern die gewonnen haben, hat niemand gewonnen + * werden die Spieler mit dem meisten Gold zurückgegeben. + */ + public List getWinningPlayers() { + List winningNames = new ArrayList<>(); + List maxGoldPlayer = new ArrayList<>(); + int maxGold = 0; + //Es können zwei Spieler gleichzeitig gewinnen + for (Player player : players) { + if (player.hasWon()) { + winningNames.add(player.getName()); + } + //Spieler mit maximalem Gold merken + if (player.getGold() == maxGold) { + maxGoldPlayer.add(player.getName()); + } + if (player.getGold() > maxGold) { + maxGold = player.getGold(); + maxGoldPlayer = new ArrayList<>(); + maxGoldPlayer.add(player.getName()); + } + } + //Falls niemand gewonnen hat, der mit am meisten Gold + if (winningNames.isEmpty()) { + return maxGoldPlayer; + } + return winningNames; + } + + /** Ermittelt ob schon jemand gewonnen hat + * @return True: wenn jemand gewonnen hat, False: niemand hat gewonnen + */ + public boolean hasSomebodyWon() { + for (Player player : players) { + if (player.hasWon()) { + return true; + } + } + return false; + } + + /** Getter für die Namen der Spieler + * @return Die Namen als String-Array + */ + public String[] getNames() { + String[] playerNames = new String[players.length]; + for (int i = 0; i < players.length; i++) { + playerNames[i] = players[i].getName(); + } + return playerNames; + } + + /** Getter für den Index des Spielers der gerade dran ist. + * @return der Index als Ganzzahl + */ + public int getPlayersTurn() { + return playersTurn; + } + + /** Setter für den Index des Spielers der gerade dran ist. + * @param playersTurn der neue Index als Ganzzahl + */ + public void setPlayersTurn(int playersTurn) { + this.playersTurn = playersTurn; + } + + /** Getter für den Besitz des Spielers der gerade dran ist. + * @return Der Inhalt der Scheune als 'Possession' + */ + public Possessions getPlayerPossessions() { + return players[playersTurn].getPossessions(); + } + + /** Getter für das Spielfeld des Spielers der gerade dran ist. + * @return das Spielfeld Objekt + */ + public Board getPlayerBoard() { + return players[playersTurn].getBoard(); + } + + /** Getter für den Markt des Spieles + * @return der Markt + */ + public Market getMarket() { + return market; + } + + /** Getter für die Spieler + * @return Eine Kopie der Spieler-Objekte + */ + public Player[] getPlayers() { + return players.clone(); + } + + /** Verkauft eine Liste an Gemüse aus der Scheune des aktuellen Spielers am Markt und schreibt ihm das Gold zu. + * @param vegetablesStrings Die zu verkaufenden Gemüse + * @return 1: Gold, 2: Anzahl Gemüse, als Ganzzahlen + * @throws LogicException Wenn der Spieler nicht genug in der Scheune hat + */ + public Entry sell(List vegetablesStrings) throws LogicException { + List content = new ArrayList<>(getPlayerBoard().getBarn().getContentListCopy()); + List forSale = new ArrayList<>(); + int gold = 0; + for (String veg : vegetablesStrings) { //Schauen ob der Spieler genug hat + Vegetable vegetable = getVegetable(veg); + if (content.contains(vegetable)) { + content.remove(vegetable); + forSale.add(vegetable); + } + else { + throw new LogicException(); //Der Spieler hat nicht genug in der Scheune + } + } + for (Vegetable veg : forSale) { //Verkaufen + players[playersTurn].addGold(market.getPrice(veg)); + players[playersTurn].getBarn().removeItem(veg); + gold += market.getPrice(veg); + } + market.soldThisTurn(forSale); + return Map.entry(gold, forSale.size()); + } + + /** Verkauft alles Gemüse des aktuellen Spielers aus seiner Scheune. + * @return 1: Gold, 2: Anzahl Gemüse, als Ganzzahlen + */ + public Entry sellAll() { + List vegetables = new ArrayList<>(); + int gold = 0; + //alles verkaufen + for (Vegetable veg : players[playersTurn].getPossessions().barnContent().keySet()) { + for (int i = 0; i < players[playersTurn].getPossessions().barnContent().get(veg); i++) { + vegetables.add(veg); + gold += market.getPrice(veg); + } + } + players[playersTurn].getBoard().emptyBarn(); //barn leeren + players[playersTurn].addGold(gold); + market.soldThisTurn(new ArrayList<>(vegetables)); + return Map.entry(gold, vegetables.size()); + } + + private Vegetable getVegetable(String input) throws IllegalArgumentException { + return Vegetable.valueOf(input.toUpperCase()); //Gemüse als String --> Gemüse + } + + /** Kauft ein Gemüse für den aktuellen Spieler und legt es in seine Scheune. + * @param vegetable das zu kaufende Gemüse + * @return der Preis zu dem verkauft wurde als Ganzzahl + * @throws TooExpensiveException Wenn das gemüse zu teuer ist. + */ + public int buyVegetable(Vegetable vegetable) throws TooExpensiveException { + int price = market.getPrice(vegetable); + int playerGold = players[playersTurn].getGold(); + if (playerGold >= price) { + players[playersTurn].setGold(playerGold - price); //Geld abziehen + players[playersTurn].getBarn().addItem(vegetable); //Gemüse geben + } + else { + throw new TooExpensiveException(); //zu teuer + } + return price; + } + + /** Kauft eine neue Kachel für den aktuellen Spieler. Die Kachel wird vom Kartenstapel gezogen + * @param coordinates Die Koordinaten an denen gekauft werden soll. Darf nicht null sein. + * @return 1: die gekaufte Kachel als Kacheltyp, 2: der bezahlte Preis als Ganzzahl + * @throws LogicException Wenn die Koordinaten nicht valide sind + * @throws TooExpensiveException Wenn der Spieler nicht genug Gold hat + */ + public Entry buyLand(Coordinates coordinates) throws LogicException, TooExpensiveException { + int price = players[playersTurn].getBoard().getPrice(coordinates); //Wirft Exception bei schlechten Koordinaten + int playerGold = players[playersTurn].getGold(); + if (playerGold >= price) { + //Geld wird im Player abgezogen + //Tile kaufen + Tile tile; + if (cardDeck.getSize() > 0) { + tile = cardDeck.getTile(); + } + else { + throw new LogicException(); //Alle Karten weg + } + try { + players[playersTurn].buyLand(tile, coordinates); + } catch (IllegalArgumentException e) { + cardDeck.restoreLastTile(); //weil getTile tile entfernt + throw new LogicException(); + } + return Map.entry(tile.getType(), price); //Paar an zwei Werten + } + else { + throw new TooExpensiveException(); + } + } + + /** Pflanzt für den aktuellen Spieler ein Gemüse an. + * @param vegetable das Gemüse, darf nicht null sein. + * @param coordinates Die Koordinaten an die gepflanzt wird, darf nicht null sein. + * @throws LogicException wenn das Anpflanzen schiefgeht (Schon etwas auf der Kachel, Kachel existiert nicht) + */ + public void plantVegetable(Vegetable vegetable, Coordinates coordinates) throws LogicException { + players[playersTurn].plant(coordinates, vegetable); + } + + /** Erntet für den aktuellen Spieler. + * @param coordinates Die Koordinaten an denen geerntet wird, darf nicht null sein. + * @param number Die Anzahl die geerntet werden soll. + * @return Das Gemüse das geerntet wurde. + * @throws LogicException wenn das Ernten schiefgeht (Kachel existiert nicht, nicht genug Gemüse auf der Kachel) + */ + public Vegetable harvest(Coordinates coordinates, int number) throws LogicException { + return players[playersTurn].harvest(coordinates, number); + } + + /** Lässt eine Runde für die anbaubaren Kacheln des aktuellen Spielers vergehen. + * @return die Anzahl der gewachsenen Gemüse als Ganzzahl + */ + public int growVegetables() { + //(nur fur den aktuellen Spieler) + return players[playersTurn].growVegetables(); + } + + /** Lässt eine Runde für die Scheune des aktuellen Spielers vergehen. + * @return True: wenn die Scheune verschimmelt ist, False: wenn nichts passiert ist + */ + public boolean updateBarn() { + //(nur fur den aktuellen Spieler) + return players[playersTurn].updateBarn(); + } + + /** Aktualisiert den Markt mit den vorher gespeicherten Gemüse. + */ + public void updateMarket() { + market.update(); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/TooExpensiveException.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/TooExpensiveException.java new file mode 100644 index 0000000000..332a9e1f4a --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/TooExpensiveException.java @@ -0,0 +1,17 @@ +package edu.kit.informatik.logik; + +/** Fehler in der Spiellogik, etwas ist zu teuer um gekauft zu werden. + * @author ujiqk + * @version 1.0 */ +public class TooExpensiveException extends LogicException { + /** Standardkonstruktor + */ + public TooExpensiveException() { } + + /** Standardkonstruktor mit Errornachricht + * @param message Die Errornachricht als String + */ + public TooExpensiveException(String message) { + super(message); + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Vegetable.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Vegetable.java new file mode 100644 index 0000000000..fcfd605311 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/Vegetable.java @@ -0,0 +1,20 @@ +package edu.kit.informatik.logik; + +/** Enum um die vier verschiedenen Gemüsearten zu modellieren + * @author ujiqk + * @version 1.0 */ +public enum Vegetable { + /** Eine Karotte + */ + CARROT, + /** Ein Pilz (sind Pilze wirklich Gemüse??) + */ + MUSHROOM, + /** Ein Salat + */ + SALAD, + /** Eine Tomate + */ + TOMATO, + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Barn.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Barn.java new file mode 100644 index 0000000000..225b75cff8 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Barn.java @@ -0,0 +1,128 @@ +package edu.kit.informatik.logik.tiles; + +import edu.kit.informatik.logik.Vegetable; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +/** Klasse die eine Scheune des "Queens Farming" Spiels implementiert. + * Die Scheune speichert nicht angebautes Gemüse für einen Spieler + * @author ujiqk + * @version 1.0 */ +public class Barn extends Tile { + + private static final int STALE_TIME = 6; + private final List content; + private int countdown; + + /** Konstruktor, erzeugt eine Scheune in Startkonfiguration: (Eine Scheune mit je einem Gemüse jeder Art) + */ + public Barn() { + content = new ArrayList<>(); + countdown = STALE_TIME + 1; //+1 Für die erste Runde + content.add(Vegetable.CARROT); + content.add(Vegetable.MUSHROOM); + content.add(Vegetable.SALAD); + content.add(Vegetable.TOMATO); + } + + /** Standardkonstruktor. Erzeugt eine neue Scheune mit dem übergebenem Inhalt + * @param content der Anfangsinhalt der Scheune. Darf nicht null sein + */ + public Barn(List content) { + this.content = new ArrayList<>(content); + if (!content.isEmpty()) { + countdown = STALE_TIME; + } + } + + /** Lässt das Gemüse eine Runde älter werden + * @return True: wenn der Inhalt verfault, False: wenn nichts passiert + */ + @Override + public boolean updateTimer() { + //True, wenn gemüse schlecht wird + if (!content.isEmpty()) { + countdown--; + if (countdown <= 0) { + content.clear(); + countdown = 0; + return true; + } + } + return false; + } + + @Override + public TileType getType() { + return TileType.BARN; + } + + /** Gibt den Inhalt der Scheune als HashMap zurück. + * @return der Inhalt: Die Gemüse mit ihrer Anzahl (auch 0) aufgelistet. + */ + public Map getContent() { + int carrots = 0; + int tomatoes = 0; + int mushrooms = 0; + int salads = 0; + for (Vegetable vegetable : content) { //Unsortierte Liste durchgehen + if (vegetable == Vegetable.CARROT) { + carrots++; + } else if (vegetable == Vegetable.TOMATO) { + tomatoes++; + } else if (vegetable == Vegetable.MUSHROOM) { + mushrooms++; + } else if (vegetable == Vegetable.SALAD) { + salads++; + } + } + Map contentMap = new EnumMap<>(Vegetable.class); + contentMap.put(Vegetable.CARROT, carrots); + contentMap.put(Vegetable.TOMATO, tomatoes); + contentMap.put(Vegetable.MUSHROOM, mushrooms); + contentMap.put(Vegetable.SALAD, salads); + return contentMap; + } + + /** Gibt eine Kopie des Inhalts der Scheune aus. + * @return der Inhalt als unsortierte Liste + */ + public List getContentListCopy() { + return List.copyOf(content); + } + + @Override + public int getCountdown() { + return countdown; + } + + /** Entfernt das angegebene Gemüse einmal aus der Scheune + * @param vegetable Das zu entfernende Gemüse. Darf nicht null sein. + * @throws IllegalArgumentException wenn das Gemüse nicht vorhanden ist. + */ + public void removeItem(Vegetable vegetable) throws IllegalArgumentException { + if (content.contains(vegetable)) { + content.remove(vegetable); + } + else { + throw new IllegalArgumentException(); + } + if (content.isEmpty()) { + countdown = 0; + } + } + + /** Fügt ein Gemüse zur Scheune hinzu. + * @param vegetable Das Gemüse. Darf nicht null sein. + */ + public void addItem(Vegetable vegetable) { + if (content.isEmpty()) { + countdown = STALE_TIME; + } + content.add(vegetable); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Board.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Board.java new file mode 100644 index 0000000000..7407eeb61d --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Board.java @@ -0,0 +1,233 @@ +package edu.kit.informatik.logik.tiles; + +import edu.kit.informatik.logik.LogicException; +import edu.kit.informatik.logik.Vegetable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** Klasse die ein Spielfeld des "Queens Farming" Spiels implementiert. + * @author ujiqk + * @version 1.0 */ +public class Board { + private static final int TILE_PRICE_FACTOR = 10; + + private final Map boardMap; //Koordinatensystem mit Einträgen + + /** Standardkonstruktor der ein Standardboard erzeugt. + * Standard: Scheune bei (0,0), Gärten bei (1,0) und (-1,0) und Feld bei (0,1). + */ + public Board() { + //init Board + boardMap = new HashMap<>(); + boardMap.put(new Coordinates(0, 0), new Barn()); + boardMap.put(new Coordinates(1, 0), new CropArea(TileType.GARDEN)); + boardMap.put(new Coordinates(-1, 0), new CropArea(TileType.GARDEN)); + boardMap.put(new Coordinates(0, 1), new CropArea(TileType.FIELD)); + } + + /** Schaut, ob die gegebenen Koordinaten frei sind + * @param coordinates die Koordinaten an denen geschaut werden soll. + * @return True: wenn das Feld noch frei ist, False: wenn schon ein Feld da ist + */ + public boolean isFree(Coordinates coordinates) { + return !boardMap.containsKey(coordinates); + } + + private boolean hasNeighbour(Coordinates coordinates) { + int xCoordinate = coordinates.getXCoordinate(); + int yCoordinate = coordinates.getYCoordinate(); + //Nachbarn sind links/rechts oder unten (NICHT oben) + return !isFree(new Coordinates(xCoordinate + 1, yCoordinate)) + || !isFree(new Coordinates(xCoordinate - 1, yCoordinate)) + //|| !isFree(new Coordinates(xCoordinate, yCoordinate + 1)) + || !isFree(new Coordinates(xCoordinate, yCoordinate - 1)); + } + + /** Ermittelt den Preis eines noch nicht besetzten Feldes + * @param coordinates Die Koordinaten. Darf nicht null sein. + * @return Der Preis als Ganzzahl + * @throws LogicException wenn kein Preis für das Feld existiert (schon belegt, + * kein Nachbar links, rechts oder drunter). + */ + public int getPrice(Coordinates coordinates) throws LogicException { + //Checkt auf erlaubte Koordinaten: rechts, links, darüber muss ein Feld sein + //geht nicht für schon gekaufte/irgendwelche Felder + if (!isFree(coordinates) || !hasNeighbour(coordinates)) { + throw new LogicException(); + } + //Kosten = Manhattan Distanz zum Ursprung + return TILE_PRICE_FACTOR + * ((Math.abs(coordinates.getXCoordinate()) + + Math.abs(coordinates.getYCoordinate())) - 1); + } + + /** Fügt eine Kachel zum Spielfeld hinzu. + * @param tile Die Kachel zum Hinzufügen. + * @param coordinates Die Koordinaten. Darf nicht null sein. + * @throws LogicException Wenn die Koordinaten schon besetzt sind oder die Kachel null ist. + */ + public void addTile(Tile tile, Coordinates coordinates) throws LogicException { + //Checkt nicht auf erlaubte Koordinaten + if (!isFree(coordinates) || tile == null) { + throw new LogicException(); + } + boardMap.put(coordinates, tile); + } + + /** Getter für die Scheune vom Spielfeld + * @return Die Scheune + */ + public Barn getBarn() { + Tile barn = boardMap.get(new Coordinates(0, 0)); + return (Barn) barn; //An Stelle (0,0) ist immer die Scheune + } + + /** Gibt die maximale Höhe des Koordinatensystems aus (die Höhe startet bei null). + * @return Die höhe als Ganzzahl + */ + public int getMaxHeight() { + int maxY = 0; + for (Coordinates key : boardMap.keySet()) { + if (key.getYCoordinate() > maxY ) { + maxY = key.getYCoordinate(); + } + } + return maxY; + } + + /** Gibt die maximale Breite des Koordinatensystems in positive Richtung (die Breite startet bei null). + * @return Die Breite als Ganzzahl + */ + public int getMaxPositiveX() { + int maxX = 0; + for (Coordinates key : boardMap.keySet()) { + if (key.getXCoordinate() > maxX ) { + maxX = key.getXCoordinate(); + } + } + return maxX; + } + + /** Gibt die maximale Breite des Koordinatensystems in negative Richtung (die Breite startet bei null). + * @return Die Breite als Ganzzahl + */ + public int getMaxNegativeX() { //Gibt negative Zahl zurück + int maxMinusX = 0; + for (Coordinates key : boardMap.keySet()) { + if (key.getXCoordinate() < maxMinusX ) { + maxMinusX = key.getXCoordinate(); + } + } + return maxMinusX; + } + + /** Gibt nur anbaubare Kacheln an den Koordinaten zurück. + * Gibt null zurück, wenn die Koordinaten nicht besetzt sind oder an der Stelle die Scheune ist. + * @param coordinates Die Koordinaten. Darf nicht null sein. + * @return Die anbaubare Kachel an der Stelle. Null, wenn sie nicht existiert. + */ + private CropArea getCropArea(Coordinates coordinates) { //Return null erlaubt!, gibt die Scheune nicht aus + if (isFree(coordinates) || (coordinates.getXCoordinate() == 0 && coordinates.getYCoordinate() == 0)) { + return null; + } + else { + return (CropArea) boardMap.get(coordinates); + } + } + + /** Gibt die Kachel an den Koordinaten zurück. + * Gibt null zurück, wenn die Koordinaten nicht besetzt sind. + * @param coordinates Die Koordinaten. Darf nicht null sein. + * @return Die Kachel an der Stelle. Null, wenn sie nicht existiert. + */ + public Tile getTile(Coordinates coordinates) { //Return null erlaubt! + if (isFree(coordinates)) { + return null; + } + else { + return boardMap.get(coordinates); + } + } + + /** Ermittelt ob Links frei ist + * @param coordinates Die Koordinaten. Darf nicht null sein. + * @return True: wenn links von den Koordinaten frei ist, False: wenn belegt + */ + public boolean isLeftFree(Coordinates coordinates) { + return isFree(new Coordinates(coordinates.getXCoordinate() - 1, coordinates.getYCoordinate())); + } + + /** Setzt die Scheune auf einen komplett leeren Zustand zurück. + */ + public void emptyBarn() { + boardMap.remove(new Coordinates(0, 0)); + //Scheune mit leerer anfangskonfiguration + boardMap.put(new Coordinates(0, 0), new Barn(new ArrayList<>())); + } + + /** Lässt alle angebauten Gemüse auf dem Spielfeld wachsen. + * @return Die anzahl der gewachsenen Gemüse. Eine Ganzzahl. + */ + public int growCropAreas() { + int amount = 0; + for (Tile tile: boardMap.values()) { + if (tile.getClass() == CropArea.class) { //Barn nicht behandeln + int amountOnTileBefore = ((CropArea) tile).getAmount(); + boolean grown = tile.updateTimer(); + int amountOnTileAfter = ((CropArea) tile).getAmount(); + if (grown) { + amount += (amountOnTileAfter - amountOnTileBefore); + } + } + } + return amount; + } + + /** Erntet Gemüse von einem Feld auf dem Spielfeld + * @param coordinates Die Koordinaten an denen geerntet wird. Darf nicht null sein. + * @param number Die Anzahl die geerntet werden soll. + * @return Gibt das Gemüse (als Vegetable) zurück das geerntet wurde. + * @throws LogicException Wenn das Feld nicht existiert oder + * nicht die angegebene Anzahl auf dem Feld zum Ernten ist. + */ + public Vegetable harvest(Coordinates coordinates, int number) throws LogicException { + if (isFree(coordinates) + || (coordinates.getXCoordinate() == 0 && coordinates.getYCoordinate() == 0)) { + throw new LogicException(); //Land noch nicht gekauft oder Land ist Scheune + } + if (getCropArea(coordinates).getAmount() < number) { + throw new LogicException(); //nicht genug Gemüse zum Ernten + } + Vegetable veg = getCropArea(coordinates).harvest(number); //ernten + for (int i = 0; i < number; i++) { //Gemüse in die Scheune legen + getBarn().addItem(veg); + } + return veg; + } + + /** Pflanzt ein Gemüse an den angegebenen Koordinaten an. + * Das Gemüse wird dabei aus der Scheune genommen. + * @param coordinates Die Koordinaten an denen gepflanzt wird. Darf nicht null sein. + * @param vegetable das Gemüse was angebaut werden soll. Darf nicht null sein. + * @throws LogicException Falls der Spieler das Gemüse nicht besitzt oder + * auf den Koordinaten nicht angebaut werden kann. + */ + public void plant(Coordinates coordinates, Vegetable vegetable) throws LogicException { + if (isFree(coordinates) + || (coordinates.getXCoordinate() == 0 && coordinates.getYCoordinate() == 0)) { + throw new LogicException(); //Land noch nicht gekauft oder Scheune an der Stelle + } + Map barnContent = getBarn().getContent(); + barnContent.values().removeAll(Collections.singleton(0)); + if (!barnContent.containsKey(vegetable)) { + throw new LogicException(); //Gemüse nicht in der Scheune + } + //Anpflanzen und Gemüse abziehen + getCropArea(coordinates).plant(vegetable); //LogicException wenn: schon belegt, falsche Kachel Art + getBarn().removeItem(vegetable); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Coordinates.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Coordinates.java new file mode 100644 index 0000000000..0605086f82 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Coordinates.java @@ -0,0 +1,47 @@ +package edu.kit.informatik.logik.tiles; + +/** Implementiert zweidimensionale Koordinaten. + * @author ujiqk + * @version 1.0 */ +public class Coordinates { + private final int xCoordinate; + private final int yCoordinate; + + /** Konstruktor für Koordinaten + * @param xCoordinate Die x-Koordinate als Ganzzahl + * @param yCoordinate Die y-Koordinate als Ganzzahl + */ + public Coordinates(int xCoordinate, int yCoordinate) { + this.xCoordinate = xCoordinate; + this.yCoordinate = yCoordinate; + } + + /** Getter für die x-Koordinate + * @return Die Koordinate als Ganzzahl + */ + public int getXCoordinate() { + return xCoordinate; + } + + /** Getter für die y-Koordinate + * @return Die Koordinate als Ganzzahl + */ + public int getYCoordinate() { + return yCoordinate; + } + + @Override + public boolean equals(Object obj) { + return obj != null && obj.getClass() == this.getClass() + && ((Coordinates) obj).getXCoordinate() == this.getXCoordinate() + && ((Coordinates) obj).getYCoordinate() == this.getYCoordinate(); + //Vergleicht die beiden Koordinaten miteinander + } + + @Override + public int hashCode() { + int result = xCoordinate; + result += yCoordinate; + return result; + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/CropArea.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/CropArea.java new file mode 100644 index 0000000000..6484b66bba --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/CropArea.java @@ -0,0 +1,181 @@ +package edu.kit.informatik.logik.tiles; + +import edu.kit.informatik.logik.LogicException; +import edu.kit.informatik.logik.Vegetable; + +/** Klasse, die ein Feld auf dem angebaut werden kann, des "Queens Farming" Spiels implementiert. + * @author ujiqk + * @version 1.0 */ +public class CropArea extends Tile { + private static final int CARROT_GROW_TIME = 1; + private static final int SALAD_GROW_TIME = 2; + private static final int MUSHROOM_GROW_TIME = 4; + private static final int TOMATO_GROW_TIME = 3; + private static final int GARDEN_CAPACITY = 2; + private static final int SMALL_CAPACITY = 4; + private static final int LARGE_CAPACITY = 8; + + private final TileType type; //Art des Feldes + private final int capacity; + private int countdown; //Wenn nichts wächst: Countdown null + + //Die angepflanzten Gemüse + private Vegetable plantType; + private int amount; + + /** Konstruktor, erzeugt aus dem Kacheltyp ein Feld auf dem angebaut werden kann. + * @param type der Kacheltyp der die Kachel haben soll. Darf nicht null oder eine Scheune sein. + */ + public CropArea(TileType type) { + this.type = type; + switch (type) { //Der Typ legt Kapazität fest + case FIELD, FOREST -> { + capacity = SMALL_CAPACITY; + } + case GARDEN -> { + capacity = GARDEN_CAPACITY; + } + case LARGE_FIELD, LARGE_FOREST -> { + capacity = LARGE_CAPACITY; + } + default -> throw new IllegalStateException("Unexpected value: " + type); + } + } + + private void grown() { + if (plantType == null) { + return; //Wenn nichts angebaut ist + } + if (capacity > (amount * 2)) { //Die Pflanzen sind gewachsen und auf der Kachel ist Platz + amount *= 2; + countdown = timeToGrow(plantType); + } + else { //die Kachel ist voll + amount = capacity; + countdown = 0; + } + } + + /** Pflanzt ein Gemüse auf der Kachel an + * @param vegetable Das Gemüse was angepflanzt wird. Darf nicht null sein. + * @throws LogicException falls schon etwas auf der Kachel wächst oder der Kacheltyp das Gemüse nicht zulässt + */ + public void plant(Vegetable vegetable) throws LogicException { + if (plantType != null) { + throw new LogicException(); //schon belegt + } + //Vegetable Type checken + if (!canPlant(vegetable)) { + throw new LogicException(); //falscher Typ + } + plantType = vegetable; + amount = 1; + countdown = timeToGrow(vegetable); + } + + private boolean canPlant(Vegetable vegetable) { + //Ob das Gemüse angepflanzt werden kann. + if (type == TileType.GARDEN) { + return true; + } else if (type == TileType.FOREST || type == TileType.LARGE_FOREST) { + return vegetable == Vegetable.CARROT || vegetable == Vegetable.MUSHROOM; + + } else if (type == TileType.FIELD || type == TileType.LARGE_FIELD) { + return vegetable == Vegetable.CARROT || vegetable == Vegetable.SALAD || vegetable == Vegetable.TOMATO; + } + return false; + } + + private int timeToGrow(Vegetable vegetable) { + //Wachstumszeiten der Gemüse + switch (vegetable) { + case MUSHROOM -> { + return MUSHROOM_GROW_TIME; + } + case SALAD -> { + return SALAD_GROW_TIME; + } + case CARROT -> { + return CARROT_GROW_TIME; + } + case TOMATO -> { + return TOMATO_GROW_TIME; + } + default -> throw new IllegalStateException(); + } + } + + /** Getter für die Anzahl der Gemüse auf der Kachel + * @return die Anzahl als Ganzzahl + */ + public int getAmount() { + return amount; + } + + /** Erntet Gemüse von der Kachel. Dabei wird das Gemüse von der Kachel entfernt. + * @param number Die Anzahl die entfernt werden soll. Eine Ganzzahl. + * @return Das Gemüse (Vegetable) das geerntet wurde + * @throws LogicException falls nichts angebaut ist + */ + public Vegetable harvest(int number) throws LogicException { + //Number muss positiv sein + if (plantType != null && amount >= number) { + amount -= number; + Vegetable vegType = plantType; + if (countdown == 0) { //Falls die Kachel voll war + countdown = timeToGrow(plantType); + } + if (amount <= 0) { //Wenn die Kachel jetzt leer ist + plantType = null; + amount = 0; + countdown = 0; + } + return vegType; + } + else { + throw new LogicException(); //Wenn nichts angebaut ist + } + } + + /** Lässt eine Spielrunde vergehen + * @return True: wenn etwas gewachsen ist, False: wenn nicht + */ + @Override + public boolean updateTimer() { + if (plantType == null || countdown == 0) { + return false; //Wenn nichts angebaut ist, oder Kachel voll + } + countdown--; + if (countdown <= 0) { + //Pflanzen sind gewachsen + grown(); + return true; + } + return false; + } + + @Override + public TileType getType() { + return type; + } + + @Override + public int getCountdown() { + return countdown; + } + + /** Getter für den aktuelle angebauten Gemüsetyp + * @return der Gemüsetyp, kann null sein, wenn nichts angebaut ist + */ + public Vegetable getPlantType() { + return plantType; //kann und soll null sein. + } + + /** Getter für die Kapazität der Kachel. + * @return die Kapazität als Ganzzahl + */ + public int getCapacity() { + return capacity; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Deck.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Deck.java new file mode 100644 index 0000000000..4cd78a3d4b --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Deck.java @@ -0,0 +1,79 @@ +package edu.kit.informatik.logik.tiles; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +/** Klasse, die einen Kartenstapel des "Queens Farming" Spiels implementiert. + * Der Kartenstapel hält für alle Spieler Landkachel, die sie, wenn sie Land kaufen zufällig bekommen. + * @author ujiqk + * @version 1.0 */ +public class Deck { + + private final List cardDeck; //Der Kartenstapel + private Tile lastTile; + + /** Standardkonstruktor der aus der Spieleranzahl ein Standardkartenstapel erzeugt. + * (Anzahl Garten: 2xn, Anzahl Feld: 3xn, Anzahl großes Feld: 2xn, Anzahl Wald: 2xn, Anzahl großer Wald: n, + * wobei n=Anzahl Spieler) + * @param playerCount die Anzahl Spieler als Ganzzahl. + */ + public Deck(int playerCount) { + //Den Kartenstapel das erste Mal befüllen + cardDeck = new ArrayList<>(); + for (int i = 0; i < (2 * playerCount); i++) { + cardDeck.add(new CropArea(TileType.GARDEN)); + } + for (int i = 0; i < (3 * playerCount); i++) { + cardDeck.add(new CropArea(TileType.FIELD)); + } + for (int i = 0; i < (2 * playerCount); i++) { + cardDeck.add(new CropArea(TileType.LARGE_FIELD)); + } + for (int i = 0; i < (2 * playerCount); i++) { + cardDeck.add(new CropArea(TileType.FOREST)); + } + for (int i = 0; i < (playerCount); i++) { + cardDeck.add(new CropArea(TileType.LARGE_FOREST)); + } + } + + /** Mischt den Kartenstapel + * @param seed der Seed für den java.util.Random Zufälligkeitsgenerator + */ + public void cardShuffle(int seed) { + Random randomGenerator = new Random(seed); + Collections.shuffle(cardDeck, randomGenerator); + } + + /** Gibt die nächste Kachel aus dem Kartenstapel aus. + * @return eine Kachel, null, falls der Stapel leer ist + */ + public Tile getTile() { + if (cardDeck.isEmpty()) { + return null; + } + Tile item = cardDeck.get(0); + lastTile = item; //Speichern zum vielleicht später wiederherstellen + cardDeck.remove(0); + return item; + } + + /** Getter für die größe des Kartenstapels + * @return die größe als positive Ganzzahl + */ + public int getSize() { + return cardDeck.size(); + } + + /** Stellt die zuletzt ausgegebene Karte wieder ganz oben auf den Kartenstapel + */ + public void restoreLastTile() { + if (lastTile != null) { + cardDeck.add(0, lastTile); + lastTile = null; + } + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Tile.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Tile.java new file mode 100644 index 0000000000..1746e18b1e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/Tile.java @@ -0,0 +1,29 @@ +package edu.kit.informatik.logik.tiles; + +/** Abstrakte Klasse die Kachel-arten des "Queens Farming" Spiels implementiert. + * @author ujiqk + * @version 1.0 */ +public abstract class Tile { + + /** Leere Konstruktor der nichts macht + */ + protected Tile() { + + } + + /** Lässt eine Runde Zeit vergehen + * @return True: wenn etwas passiert, False: wenn nichts passiert + */ + public abstract boolean updateTimer(); + + /** Getter für den Kacheltyp einer Kachel + * @return der Kacheltyp + */ + public abstract TileType getType(); + + /** Getter für den Countdown einer Kachel + * @return der countdown als Ganzzahl + */ + public abstract int getCountdown(); + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/TileType.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/TileType.java new file mode 100644 index 0000000000..2bd9351d79 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/logik/tiles/TileType.java @@ -0,0 +1,25 @@ +package edu.kit.informatik.logik.tiles; + +/** Stellt alle Zustände dar, die eine Kachel haben kann. + * @author ujiqk + * @version 1.0 */ +public enum TileType { + /** Ein Garten + */ + GARDEN, + /** Ein Feld + */ + FIELD, + /** Ein großes Feld + */ + LARGE_FIELD, + /** Ein Wald + */ + FOREST, + /** Ein großer Wald + */ + LARGE_FOREST, + /** Die Scheune + */ + BARN, +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/GamePrinter.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/GamePrinter.java new file mode 100644 index 0000000000..5c722015e4 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/GamePrinter.java @@ -0,0 +1,321 @@ +package edu.kit.informatik.ui; + +import edu.kit.informatik.logik.Market; +import edu.kit.informatik.logik.Possessions; +import edu.kit.informatik.logik.QueensFarming; +import edu.kit.informatik.logik.Vegetable; +import edu.kit.informatik.logik.tiles.*; + +import java.util.*; +import java.util.regex.Pattern; + +/** + * Kann aktuelle Spielzustände eines "Queens Farming" Spiels auf der Kommandozeile ausgeben. + * + * @author ujiqk + * @version 1.0 + */ +public class GamePrinter { + + private static final String SHOW_BARN = "Barn"; + private static final String SHOW_BARN1 = " (spoils in "; + private static final String SHOW_BARN2 = " turns)"; + private static final String SHOW_BARN3 = " turn)"; + private static final String TOMATOES = "tomatoes:%s%d"; + private static final String SALADS = "salads:%s%d"; + private static final String CARROTS = "carrots:%s%d"; + private static final String MUSHROOMS = "mushrooms:%s%d"; + private static final String SUMM = "Sum:%s%d"; + private static final String GOLD = "Gold:%s%d"; + private static final String SPACER = "|"; + private static final String SPACER_VERTICAL = "-"; + private static final String BARN_NAME = " B %s "; + private static final String GARDE_NAME = " G %s "; + private static final String FIELD_NAME = " Fi %s"; + private static final String LFIELD_NAME = "LFi %s"; + private static final String FOREST_NAME = " Fo %s"; + private static final String LFOREST_NAME = "LFo %s"; + private static final String NO_COUNTDOWN = "*"; + private static final String STRING_FORMAT = "%s"; + private static final String MAP_EMPTY_ROW = " "; + private static final String CARROT_SHORT = " C "; + private static final String MUSHROOM_SHORT = " M "; + private static final String SALAD_SHORT = " S "; + private static final String TOMATO_SHORT = " T "; + private static final String AMOUNT_CAPACITY = " %d/%d "; + private static final String BREAK = System.lineSeparator(); + private final QueensFarming game; + + /** + * Konstruktor, um die Zustandsausgabe zu initialisieren. + * + * @param game Das Spiel von dem die Daten kommen + */ + public GamePrinter(QueensFarming game) { + this.game = game; + } + + /** + * Druckt den Zustand einer Scheune, des Markts oder des Spielfelds. + * Scheune und Spielfeld sind vom Spieler, der gerade dran ist. + * + * @param input Die Eingabe des Benutzers. Ist sie falsch passiert nichts. + */ + public void show(String input) { + if (Pattern.matches("show barn", input)) { + printPossessions(game.getPlayerPossessions()); + } else if (Pattern.matches("show board", input)) { + printBoard(game.getPlayerBoard()); + } else if (Pattern.matches("show market", input)) { + printMarket(game.getMarket()); + } + } + + private void printPossessions(Possessions possessions) { + int maxLength = getLongestBarnString(possessions); + System.out.print(SHOW_BARN); + //Wenn die Scheune nicht leer ist + if (!possessions.barnContent().isEmpty()) { + printBarn(possessions, maxLength); + } + System.out.println(); + //Gold + String print = String.format(GOLD, STRING_FORMAT, possessions.gold()); + String fill = ""; + for (int i = 0; i < maxLength + 2 - print.length(); i++) { //2 wegen %s + fill += " "; + } + System.out.println(String.format(print, fill)); + } + + private void printBarn(Possessions possessions, int maxLength) { + if (possessions.spoilTime() == 1) { + System.out.println(SHOW_BARN1 + possessions.spoilTime() + SHOW_BARN3); + } else { + System.out.println(SHOW_BARN1 + possessions.spoilTime() + SHOW_BARN2); + } + int sum = 0; + //Auflistung der Gemüse + while (!possessions.barnContent().isEmpty()) { //Alle Elemente durchgehen + sum = printNextBarnContent(possessions, maxLength, sum); + } + //Spacer + for (int i = 0; i <= maxLength - 2; i++) { //2 wegen %s im String + System.out.print(SPACER_VERTICAL); + } + System.out.println(SPACER_VERTICAL); + //Summe + String print = String.format(SUMM, STRING_FORMAT, sum); + String fill = ""; + for (int i = 0; i < maxLength + 2 - print.length(); i++) { //2 wegen %s im String + fill += " "; + } + System.out.println(String.format(print, fill)); + } + + private int printNextBarnContent(Possessions possessions, int maxLength, int total) { + int sum = total; + int min = Collections.min(possessions.barnContent().values()); + //HashMap ist nicht sortiert → mit TreeSet alphabetisch sortieren, + //sortiert in alphabetischer Reihenfolge + Set> entries = possessions.barnContent().entrySet(); +// Set> sortedEntries = new TreeSet<>( +// Comparator.comparing((Map.Entry::getKey))); + Set> sortedEntries = new TreeSet<>(); + sortedEntries.addAll(entries); + //Durch die HasMap durchgehen und kleinstes finden + for (Map.Entry entry : sortedEntries) { + if (entry.getValue() == min) { + //Nächster Wert printen + String print; + String fill = ""; + switch (entry.getKey()) { + case MUSHROOM -> { + print = String.format(MUSHROOMS, STRING_FORMAT, min); + } + case TOMATO -> { + print = String.format(TOMATOES, STRING_FORMAT, min); + } + case SALAD -> { + print = String.format(SALADS, STRING_FORMAT, min); + } + case CARROT -> { + print = String.format(CARROTS, STRING_FORMAT, min); + } + default -> throw new IllegalStateException(); + } + //Auffüllen + for (int i = 0; i < maxLength + 2 - print.length(); i++) { //2 wegen %s im String + fill += " "; + } + System.out.println(String.format(print, fill)); + sum += min; + possessions.barnContent().remove(entry.getKey()); + } + } + return sum; + } + + private int getLongestBarnString(Possessions possessions) { + //größte Zahl bekommen + int sum = 0; + for (Integer entry : possessions.barnContent().values()) { + sum += entry; + } + List numbers = new ArrayList<>(possessions.barnContent().values()); + numbers.add(possessions.gold()); + numbers.add(sum); + int biggestNumber = Collections.max(numbers); + //Längsten String bekommen + List values = new ArrayList<>(); + if (possessions.barnContent().get(Vegetable.MUSHROOM) != null) { + values.add(String.format(MUSHROOMS, " ", biggestNumber)); + } + if (possessions.barnContent().get(Vegetable.TOMATO) != null) { + values.add(String.format(TOMATOES, " ", biggestNumber)); + } + if (possessions.barnContent().get(Vegetable.SALAD) != null) { + values.add(String.format(SALADS, " ", biggestNumber)); + } + if (possessions.barnContent().get(Vegetable.CARROT) != null) { + values.add(String.format(CARROTS, " ", biggestNumber)); + } + values.add(String.format(GOLD, " ", biggestNumber)); + //return values.stream().map(String::length).max(Integer::compareTo).get(); + return values.size(); + } + + private void printBoard(Board board) { + String output = ""; + //Wir brauchen hier konkrete x werte + for (int y = 0; y <= board.getMaxHeight(); y++) { + String row1 = ""; + String row2 = ""; + String row3 = ""; + for (int x = board.getMaxNegativeX(); x <= board.getMaxPositiveX(); x++) { + //Zeilenweise durch das Spielfeld gehen + String[] rows = boardRowToString(new String[]{row1, row2, row3}, x, y, board); + row1 = rows[0]; + row2 = rows[1]; + row3 = rows[2]; + } + //Falls am Ende ein Feld ist → End-spacer, wenn nicht: auffüllen + if (!board.isFree(new Coordinates(board.getMaxPositiveX(), y))) { + row1 += SPACER; + row2 += SPACER; + row3 += SPACER; + } else { + row1 += " "; + row2 += " "; + row3 += " "; + } + if (y == 0) { //Keine leere Zeile + output = row1 + BREAK + row2 + BREAK + row3 + output; + } else { + output = row1 + BREAK + row2 + BREAK + row3 + BREAK + output; + } + } + System.out.println(output); + } + + private String[] boardRowToString(String[] row, int xCoordinate, int yCoordinate, Board board) { + String row1 = row[0]; + String row2 = row[1]; + String row3 = row[2]; + //Zeilenweise die Felder durchgehen von minus nach plus + Tile tile = board.getTile(new Coordinates(xCoordinate, yCoordinate)); //nächste Tile bekommen (ohne Barn) + if (tile == null) { + //Spacer oder Leerzeichen + if (!board.isLeftFree(new Coordinates(xCoordinate, yCoordinate))) { + row1 += SPACER; + row2 += SPACER; + row3 += SPACER; + } else { + row1 += " "; + row2 += " "; + row3 += " "; + } + row1 += MAP_EMPTY_ROW; + row2 += MAP_EMPTY_ROW; + row3 += MAP_EMPTY_ROW; + } else { //tile != null + String[] rows = tileToString(new String[]{row1, row2, row3}, tile); + row1 = rows[0]; + row2 = rows[1]; + row3 = rows[2]; + } + return new String[]{row1, row2, row3}; + } + + private String[] tileToString(String[] row, Tile tile) { + String row1 = row[0] + SPACER; + String row2 = row[1] + SPACER; + String row3 = row[2] + SPACER; + String countdown; + if (tile.getCountdown() == 0) { //Countdown bekommen + countdown = NO_COUNTDOWN; + } else { + countdown = String.valueOf(tile.getCountdown()); + } + switch (tile.getType()) { //Obere Zeile: Namen/Countdown + case GARDEN -> { + row1 += String.format(GARDE_NAME, countdown); + } + case FIELD -> { + row1 += String.format(FIELD_NAME, countdown); + } + case LARGE_FIELD -> { + row1 += String.format(LFIELD_NAME, countdown); + } + case FOREST -> { + row1 += String.format(FOREST_NAME, countdown); + } + case LARGE_FOREST -> { + row1 += String.format(LFOREST_NAME, countdown); + } + case BARN -> { + row2 += String.format(BARN_NAME, countdown); + } + default -> throw new IllegalStateException(); + } + if (tile.getType() == TileType.BARN) { //Barn extra + row1 += MAP_EMPTY_ROW; + row3 += MAP_EMPTY_ROW; + } else { //Alles andere: Pflanzentyp + CropArea cropTile = (CropArea) tile; + //Mittlere Zeile Gemüsetyp + if (cropTile.getPlantType() == null) { + row2 += MAP_EMPTY_ROW; + } else { + switch (cropTile.getPlantType()) { + case CARROT -> { + row2 += CARROT_SHORT; + } + case SALAD -> { + row2 += SALAD_SHORT; + } + case TOMATO -> { + row2 += TOMATO_SHORT; + } + case MUSHROOM -> { + row2 += MUSHROOM_SHORT; + } + default -> { + row2 += MAP_EMPTY_ROW; + } //Kein Gemüse + } + } + //Letzte Zeile: belegt/Kapazität + row3 += String.format(AMOUNT_CAPACITY, cropTile.getAmount(), cropTile.getCapacity()); + } + return new String[]{row1, row2, row3}; + } + + private void printMarket(Market market) { + System.out.println(String.format(MUSHROOMS, " ", market.getPrice(Vegetable.MUSHROOM))); + System.out.println(String.format(CARROTS, " ", market.getPrice(Vegetable.CARROT))); + System.out.println(String.format(TOMATOES, " ", market.getPrice(Vegetable.TOMATO))); + System.out.println(String.format(SALADS, " ", market.getPrice(Vegetable.SALAD))); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/GameUI.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/GameUI.java new file mode 100644 index 0000000000..bc31dab188 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/GameUI.java @@ -0,0 +1,311 @@ +package edu.kit.informatik.ui; + +import edu.kit.informatik.logik.LogicException; +import edu.kit.informatik.logik.Player; +import edu.kit.informatik.logik.QueensFarming; +import edu.kit.informatik.logik.TooExpensiveException; +import edu.kit.informatik.logik.Vegetable; +import edu.kit.informatik.logik.tiles.Coordinates; +import edu.kit.informatik.logik.tiles.TileType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map.Entry; +import java.util.Scanner; +import java.util.regex.Pattern; + +/** Klasse die ein "Queens Farming" Spiel in der Kommandozeile implementiert. + * Mit ihr kann man ein komplettes Spiel spielen + * @author ujiqk + * @version 1.0 */ +public class GameUI { + + private static final String PLAYER_TURN1 = "It is "; + private static final String PLAYER_TURN2 = "'s turn!"; + private static final String PLAYER = "Player "; + private static final String HAS_WON = " has won!"; + private static final String HAVE_WON = "%s and %s have won!"; + private static final String COMMA = ","; + private static final String CLAMP1 = ")"; + private static final String CLAMP2 = "("; + private static final String SPOILED = "The vegetables in your barn are spoiled."; + private static final String VEGETABLE_GROWN = "1 vegetable has grown since your last turn."; + private static final String VEGETABLES_GROWN = " vegetables have grown since your last turn."; + private static final String TOMATOES = "tomatoes"; + private static final String SALADS = "salads"; + private static final String CARROTS = "carrots"; + private static final String MUSHROOMS = "mushrooms"; + private static final String COLON = ":"; + private static final String SOLD = "You have sold %d %s for %d gold."; + private static final String VEGETABLE = "vegetable"; + private static final String VEGETABLES = "vegetables"; + private static final String BOUGHT_ITEM = "You have bought a %s for %d gold."; + private static final String TOMATO = "tomato"; + private static final String CARROT = "carrot"; + private static final String MUSHROOM = "mushroom"; + private static final String SALAD = "salad"; + private static final String FOREST = "Forest"; + private static final String GARDEN = "Garden"; + private static final String LARGE_FOREST = "Large Forest"; + private static final String FIELD = "Field"; + private static final String LARGE_FIELD = "Large Field"; + private static final String HARVESTED = "You have harvested %d %s."; + private static final String ERROR_ILLEGAL_COMMAND = "Error: Illegal command"; + private static final String ERROR_NOT_ENOUGH_ITEMS = "Error: Not enough items"; + private static final String ERROR_NOT_ENOUGH_GOLD = "Error: Not enough gold"; + private static final String ERROR_INVALID_COORDINATES = "Error: Coordinates invalid"; + + private final QueensFarming game; + private final Scanner inputScanner; + private boolean quit; + private boolean playerFinished; + private int playerAction; + private final GamePrinter gamePrinter; + + /** + * Konstruktor, macht I/O für ein übergebenes "Queens Farming" Spiel. + * + * @param game Das "Queens Farming" Spiel. Darf nicht null sein. + * @param inputScanner Der java Scanner um Eingaben einzulesen. darf nicht null sein + */ + public GameUI(QueensFarming game, Scanner inputScanner) { + this.game = game; + this.inputScanner = inputScanner; + playerAction = 0; + quit = false; + playerFinished = false; + gamePrinter = new GamePrinter(game); + //quit ist Klassen-variable + while (!quit) { + //Eine Runde spielen + round(); + } + } + + private void playerTurn() { //max 2 Aktionen oder "end turn" + System.out.println(); + System.out.println(PLAYER_TURN1 + game.getNames()[game.getPlayersTurn()] + PLAYER_TURN2); + //Gemüse wächst + int grownVeg = game.growVegetables(); + //Dinge werden schlecht + boolean spoiled = game.updateBarn(); + //Print Update der Felder + if (grownVeg == 1) { + System.out.println(VEGETABLE_GROWN); + } else if (grownVeg > 1) { + System.out.println(grownVeg + VEGETABLES_GROWN); + } + //Print update der Barn + if (spoiled) { + System.out.println(SPOILED); + } + //Auf Befehle warten + while (!playerFinished) { + readInput(); + if (playerAction >= 2 || quit) { + playerFinished = true; + } + } + playerFinished = false; + playerAction = 0; + //Markt anpassen, nächster Spieler + game.updateMarket(); + game.setPlayersTurn(game.getPlayersTurn() + 1); + } + + private void round() { + //Spieler haben ihre Züge + for (int i = 0; i < game.getNames().length; i++) { + playerTurn(); + if (quit) { + printWin(game.getWinningPlayers()); + return; + } + } + //Siegesbedingungen prüfen + if (game.hasSomebodyWon()) { + printWin(game.getWinningPlayers()); + } + game.setPlayersTurn(0); + } + + private void printWin(List winningPlayers) { + Player[] players = game.getPlayers(); + //Gold der Spieler ausgeben + for (int i = 0; i < players.length; i++) { + System.out.println(PLAYER + (i + 1) + " " + CLAMP2 + players[i].getName() + + CLAMP1 + COLON + " " + players[i].getGold()); + } + //Gewinner ausgeben + if (winningPlayers.size() == 1) { + System.out.println(winningPlayers.get(0) + HAS_WON); + } else if (winningPlayers.size() >= 2) { + String output = ""; + for (int i = 0; i < winningPlayers.size() - 2; i++) { + output = output + winningPlayers.get(i) + COMMA + " "; + } + output += winningPlayers.get(winningPlayers.size() - 2); + System.out.println(String.format(HAVE_WON, output, winningPlayers.get(winningPlayers.size() - 1))); + } + quit = true; + } + + private void readInput() { + String input = inputScanner.nextLine(); + //quit befehl befolgen + if (Pattern.matches("quit", input)) { + quit = true; + //Hiernach darf nichts mehr kommen + } else if (Pattern.matches("end turn", input)) { + playerFinished = true; + } else if (Pattern.matches("show (barn|market|board)", input)) { + gamePrinter.show(input); + } +// else if (Pattern.matches("sell( mushroom| carrot| tomato | salad)?( mushroom| carrot| tomato| salad)*", input) +// || Pattern.matches("sell all", input)) { +// sell(input); + } else if (Pattern.matches("buy ((vegetable (mushroom|carrot|tomato|salad))|(land -?\\d* \\d*))", input)) { + buy(input); + } else if (Pattern.matches("harvest -?\\d* \\d* ([1-9]\\d*)", input)) { + harvest(input); + } else if (Pattern.matches("plant -?\\d* \\d* (mushroom|carrot|tomato|salad)", input)) { + plant(input); + } else { + System.err.println(ERROR_ILLEGAL_COMMAND); + } + } + + private void sell(String input) { + Entry soldInfo; //1: Gold, 2: Anzahl Gemüse + List vegetables = new ArrayList<>(); + if (Pattern.matches("sell all", input)) { //Alles Verkaufen + soldInfo = game.sellAll(); + } + else { //String durchgehen + Collections.addAll(vegetables, input.split(" ")); //Korrektheit des Strings wird vorher überprüft + vegetables.remove(0); //"sell" entfernen + try { //Verkaufen + soldInfo = game.sell(vegetables); + } catch (LogicException e) { + System.err.println(ERROR_NOT_ENOUGH_ITEMS); + return; + } + } + //Output + if (soldInfo.getValue() == 1) { + System.out.println(String.format(SOLD, soldInfo.getValue(), VEGETABLE, soldInfo.getKey())); + } + else { + System.out.println(String.format(SOLD, soldInfo.getValue(), VEGETABLES, soldInfo.getKey())); + } + playerAction++; + } + + private void buy(String input) { + String [] splitInput = input.split(" "); + if (Pattern.matches("buy (vegetable (mushroom|carrot|tomato|salad))", input)) { + int gold; + //Gemüse kaufen + try { + gold = game.buyVegetable(Vegetable.valueOf(splitInput[2].toUpperCase())); + } catch (TooExpensiveException e) { + System.err.println(ERROR_NOT_ENOUGH_GOLD); + return; + } + //Ausgabe + printBuyVeg(Vegetable.valueOf(splitInput[2].toUpperCase()), gold); + playerAction++; + } + else if (Pattern.matches("buy land -?\\d* \\d*", input)) { + Entry output; + //Land kaufen + try { + int xCoord = Integer.parseInt(splitInput[2]); + int yCoord = Integer.parseInt(splitInput[3]); + output = game.buyLand(new Coordinates(xCoord, yCoord)); + } catch (TooExpensiveException e) { + System.err.println(ERROR_NOT_ENOUGH_GOLD); + return; + } catch (LogicException e) { + System.err.println(ERROR_INVALID_COORDINATES); + return; + } + printBuyLAND(output.getKey(), output.getValue()); + playerAction++; + } + } + + private void printBuyVeg(Vegetable vegetable, int cost) { + switch (vegetable) { + case CARROT -> System.out.println(String.format(BOUGHT_ITEM, CARROT, cost)); + case SALAD -> System.out.println(String.format(BOUGHT_ITEM, SALAD, cost)); + case TOMATO -> System.out.println(String.format(BOUGHT_ITEM, TOMATO, cost)); + case MUSHROOM -> System.out.println(String.format(BOUGHT_ITEM, MUSHROOM, cost)); + default -> throw new IllegalStateException(); + } + } + + private void printBuyLAND(TileType type, int cost) { + switch (type) { + case FOREST -> System.out.println(String.format(BOUGHT_ITEM, FOREST, cost)); + case LARGE_FOREST -> System.out.println(String.format(BOUGHT_ITEM, LARGE_FOREST, cost)); + case FIELD -> System.out.println(String.format(BOUGHT_ITEM, FIELD, cost)); + case LARGE_FIELD -> System.out.println(String.format(BOUGHT_ITEM, LARGE_FIELD, cost)); + case GARDEN -> System.out.println(String.format(BOUGHT_ITEM, GARDEN, cost)); + default -> throw new IllegalStateException(); + } + } + + private void harvest(String input) { + String [] splitInput = input.split(" "); + int xCoord = Integer.parseInt(splitInput[1]); + int yCoord = Integer.parseInt(splitInput[2]); + int amount = Integer.parseInt(splitInput[3]); + Vegetable vegetable; + try { + vegetable = game.harvest(new Coordinates(xCoord, yCoord), amount); + } + catch (LogicException e) { + System.err.println(ERROR_INVALID_COORDINATES); + //Oder Item nicht auf der Kachel/Falsche Anzahl + return; + } + //Output + if (amount == 1 || amount == 0) { + switch (vegetable) { + case MUSHROOM -> System.out.println(String.format(HARVESTED, amount, MUSHROOM)); + case TOMATO -> System.out.println(String.format(HARVESTED, amount, TOMATO)); + case SALAD -> System.out.println(String.format(HARVESTED, amount, SALAD)); + case CARROT -> System.out.println(String.format(HARVESTED, amount, CARROT)); + default -> throw new IllegalStateException(); + } + } + else { + switch (vegetable) { //Mehrzahlnamen sind im vorderen Teil des Strings für die Barn + case MUSHROOM -> System.out.println(String.format(HARVESTED, amount, MUSHROOMS)); + case TOMATO -> System.out.println(String.format(HARVESTED, amount, TOMATOES)); + case SALAD -> System.out.println(String.format(HARVESTED, amount, SALADS)); + case CARROT -> System.out.println(String.format(HARVESTED, amount, CARROTS)); + default -> throw new IllegalStateException(); + } + } + playerAction++; + } + + private void plant(String input) { + String [] splitInput = input.split(" "); + int xCoord = Integer.parseInt(splitInput[1]); + int yCoord = Integer.parseInt(splitInput[2]); + try { + game.plantVegetable(Vegetable.valueOf(splitInput[3].toUpperCase()), new Coordinates(xCoord, yCoord)); + } + catch (LogicException e) { + System.err.println(ERROR_INVALID_COORDINATES); + //Oder Item nicht in der Barn + return; + } + playerAction++; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/InputState.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/InputState.java new file mode 100644 index 0000000000..b6076241ae --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/InputState.java @@ -0,0 +1,27 @@ +package edu.kit.informatik.ui; + +/** Beschreibt die Zustände des Spielstarts + * @author ujiqk + * @version 1.0 + */ +public enum InputState { + /** Einlesen der Spieleranzahl + */ + PLAYER_COUNT, + /** Einlesen der Namen der Spieler + */ + PLAYER_NAMES, + /** einlesen des Startgoldes + */ + START_GOLD, + /** Einlesen des für den Gewinn notwendigen Goldes + */ + WIN_GOLD, + /** Einlesen des Seeds für das Mischeln + */ + SHUFFLE, + /** Alles erfolgreich eingelesen + */ + READY + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/ParseException.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/ParseException.java new file mode 100644 index 0000000000..e7318ddc70 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/ParseException.java @@ -0,0 +1,17 @@ +package edu.kit.informatik.ui; + +/** Fehler beim Parsen einer Benutzereingabe + * @author ujiqk + * @version 1.0 */ +public class ParseException extends Exception { + /** Standardkonstruktor + */ + public ParseException() { } + + /** Standardkonstruktor mit Errornachricht + * @param message Die Errornachricht als String + */ + public ParseException(String message) { + super(message); + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/StartUserInterface.java b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/StartUserInterface.java new file mode 100644 index 0000000000..ae1652023e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex/Submission-01/ui/StartUserInterface.java @@ -0,0 +1,192 @@ +package edu.kit.informatik.ui; + +import edu.kit.informatik.logik.QueensFarming; + +import java.util.Scanner; +import java.util.regex.Pattern; + +/** + * Klasse die den Start eines "Queens Farming" Spiels auf der Kommandozeile implementiert. + * Alle zum Start wichtigen Parameter werden eingelesen und danach das Spiel gestartet. + * + * @author ujiqk + * @version 1.0 + */ +public class StartUserInterface { + private static final String PLAYER_QUESTION = "How many players?"; + private static final String NAME_QUESTION = "Enter the name of player "; + private static final String START_GOLD_QUESTION = "With how much gold should each player start?"; + private static final String WIN_GOLD_QUESTION = "With how much gold should a player win?"; + private static final String SEED_QUESTION = "Please enter the seed used to shuffle the tiles:"; + private static final String COLON = ":"; + private static final String PIXEL_ART = """ + _.-^-._ .--. \s + .-' _ '-. |__| \s + / |_| \\| | \s + / \\ | \s + /| _____ |\\ | \s + | |==|==| | | \s + |---|---|---|---|---| |--|--| | | \s + |---|---|---|---|---| |==|==| | | \s + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ^^^^^^^^^^^^^^^ QUEENS FARMING ^^^^^^^^^^^^^^^ + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"""; + private static final String ERROR_ILLEGAL_NUMBER_OF_PLAYERS = "Error: Illegal number of players"; + private static final String ERROR_ILLEGAL_PLAYER_NAME = "Error: Illegal player name"; + private static final String ERROR_ILLEGAL_GOLD_AMOUNT = "Error: Illegal amount of gold"; + private static final String ERROR_SEED = "Error: Illegal shuffle seed"; + + //Wichtige Sachen um das Spiel zu erzeugen + private InputState state; + private int playerCount; + private int startGold; + private int winGold; + private int seed; + private String[] playerNames; + + /** + * Konstruktor, liest Eingabe ein und übergibt sie gesammelt an das Spiel User Interface + * eingelesene Eingaben: Spieleranzahl, Namen, Startgold, Gold um zu gewinnen, Seed zum Mischeln + */ + public StartUserInterface() { + System.out.println(PIXEL_ART); + + //Status des Einlesens + state = InputState.PLAYER_COUNT; + playerCount = 0; + playerNames = null; + + System.out.println(PLAYER_QUESTION); + + Scanner inputScanner = new Scanner(System.in); + //Schleife um Eingaben einlesen + boolean quit = false; + while (!quit) { + String input = inputScanner.nextLine(); + //'quit' befehl befolgen (beendet auch, falls der Name eines Spielers "quit" ist) + if (Pattern.matches("quit", input)) { + break; + //Hiernach darf nichts mehr kommen + } + quit = gameInitUi(input); + } + if (state == InputState.READY) { + GameUI gameUI = new GameUI(new QueensFarming(playerNames, winGold, startGold, seed), inputScanner); + } + inputScanner.close(); + //Ende des Programms + } + + /** + * Liest eine Benutzereingabe ein und parst sie je nach aktuellem Eingabestatus. + * Ist die Eingabe erfolgreich wird in den nächsten Eingabestatus gesprungen. + * + * @param input Die Benutzereingabe, ungefiltert + * @return True: wenn die Eingabe komplett fertig ist + */ + private boolean gameInitUi(String input) { + if (state == InputState.PLAYER_COUNT) { //Einlesen der Spielerzahl + state = readPlayerCount(input); + } + //Einlesen der Namen + else if (state == InputState.PLAYER_NAMES) { + state = readPlayerNames(input); + } + //Einlesen des Startgoldes + else if (state == InputState.START_GOLD) { + state = readStartGold(input); + } + //Einlesen des Gewinngoldes + else if (state == InputState.WIN_GOLD) { + state = readWinGold(input); + } + //Einlesen des Seeds + else if (state == InputState.SHUFFLE) { + try { + seed = Integer.parseInt(input); + //Speichern und in den nächsten state wechseln + state = InputState.READY; + return true; //das Einlesen ist fertig + } catch (NumberFormatException e) { + System.err.println(ERROR_SEED); + } + } + return false; + } + + private InputState readWinGold(String input) { + try { + winGold = parseNumberGreaterOne(input); + } catch (NumberFormatException | ParseException e) { + System.err.println(ERROR_ILLEGAL_GOLD_AMOUNT); + return state; + } + System.out.println(SEED_QUESTION); //zum nächsten Wechseln + return InputState.SHUFFLE; + } + + private InputState readStartGold(String input) { + try { + startGold = parsePositiveNumber(input); + } catch (NumberFormatException | ParseException e) { + System.err.println(ERROR_ILLEGAL_GOLD_AMOUNT); + return state; + } + System.out.println(WIN_GOLD_QUESTION); + return InputState.WIN_GOLD; + } + + private InputState readPlayerNames(String input) { + try { + playerNames[playerNames.length - playerCount] = parseNames(input); + playerCount--; //Player count als Zählvariable + } catch (ParseException e) { + System.err.println(ERROR_ILLEGAL_PLAYER_NAME); + return state; //Nicht nochmal fragen + } + if (playerCount <= 0) { + System.out.println(START_GOLD_QUESTION); + return InputState.START_GOLD; //zum nächsten Wechseln + } else { + System.out.println(NAME_QUESTION + (playerNames.length - playerCount + 1) + COLON); + } + return state; + } + + private InputState readPlayerCount(String input) { + try { + playerCount = parseNumberGreaterOne(input); + } catch (NumberFormatException | ParseException e) { + System.err.println(ERROR_ILLEGAL_NUMBER_OF_PLAYERS); + return state; + } + playerNames = new String[playerCount]; + System.out.println(NAME_QUESTION + 1 + COLON); //Erste Frage nach Spieleranzahl + return InputState.PLAYER_NAMES; + } + + private String parseNames(String input) throws ParseException { + if (Pattern.matches("[a-zA-Z]+", input)) { + return input; + } else { + throw new ParseException(); + } + } + + private int parseNumberGreaterOne(String input) throws NumberFormatException, ParseException { + int number = Integer.parseInt(input); + if (number < 1) { + throw new ParseException(); //Ist das schön? + } + return number; + } + + private int parsePositiveNumber(String input) throws NumberFormatException, ParseException { + int number = Integer.parseInt(input); + if (number < 0) { + throw new ParseException(); //Ist das schön? + } + return number; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/Main.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/Main.java new file mode 100644 index 0000000000..cb649506a3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/Main.java @@ -0,0 +1,26 @@ +package edu.kit.informatik; + +import edu.kit.informatik.ui.SimUi; + +/** Das Programm simuliert ein vereinfachtes Straßennetzwerk. + * Das Netzwerk wird aus Dateien eingelesen. + * Der Zustand kann über das Terminal ausgegeben werden. + * @author ujiqk + * @version 1.0 + */ +public final class Main { + + private Main() { + throw new IllegalStateException(); + } + + /** Main Methode die das Programm startet. + * @param args Der Kommandozeilenparameter. Wird vom Programm ignoriert. + */ + public static void main(String[] args) { + + //Simulation starten + SimUi simulationUI = new SimUi(); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/LogicException.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/LogicException.java new file mode 100644 index 0000000000..17ebf8b3fa --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/LogicException.java @@ -0,0 +1,15 @@ +package edu.kit.informatik.logic; + +/** Exception die geworfen wird, wenn ein Fehler in der Logik auftritt. + * @author ujiqk + * @version 1.0 + */ +public class LogicException extends Exception { + + /** Standardkonstruktor mit Errornachricht + * @param message Die Errornachricht als String + */ + public LogicException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/SimEngine.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/SimEngine.java new file mode 100644 index 0000000000..f39525f85e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/SimEngine.java @@ -0,0 +1,194 @@ +package edu.kit.informatik.logic; + +import edu.kit.informatik.logic.entitys.Car; +import edu.kit.informatik.logic.entitys.Crossing; +import edu.kit.informatik.logic.entitys.CrossingWithLight; +import edu.kit.informatik.logic.entitys.Entity; +import edu.kit.informatik.logic.entitys.Street; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Die Klasse SimEngine ist die zentrale Klasse der Programmlogik. + * Sie verwaltet die Straßen, Autos und Kreuzungen. + * Mit ihr kann man im Netzwerk Zeit vergehen lassen und Autos bewegen. + * @author ujiqk + * @version 1.0 + */ +public class SimEngine { + + private final Map streetMap; //Straßen mit IDs + private final Map carMap; + private final Map crossingMap; + + + /** Erzeugt eine neue SimEngine mit den als Strings gegebenen Straßen, Autos und Kreuzungen. + * @param cars Die Autos als Strings. + * @param streets Die Straßen als Strings. + * @param crossings Die Kreuzungen als Strings. + * @throws LogicException Wenn das gegebene Straßennetz nicht korrekt ist. + */ + public SimEngine(List cars, List streets, List crossings) throws LogicException { + carMap = new HashMap<>(); + streetMap = new HashMap<>(); + crossingMap = new HashMap<>(); + //Crossings, Cars, Streets überprüfen und Straßennetz erzeugen + //1: IDs überprüfen + if (!checkIds(cars) || !checkIds(crossings)) { + throw new LogicException("wrong Car or Crossing ID format"); + } + //2: Autos und Straßen Initialisieren + for (Entity entity : cars) { + carMap.put(entity.getId(), (Car) entity); + } + for (Entity entity : streets) { + streetMap.put(entity.getId(), (Street) entity); + } + //3: Straßen-IDs der Autos kontrollieren, zu viele Autos auf einer Straße kontrollieren + for (Entity entity : cars) { + Car car = (Car) entity; + if (streetMap.get(car.getStreetID()) == null) { + //wenn die Straßen nicht existiert + throw new LogicException("Street " + car.getStreetID() + " does not exist"); + } + streetMap.get(car.getStreetID()).addCarForemost((Car) entity); //throws Logic Exception wenn voll + } + //4: Kreuzungen kontrollieren/Initialisieren: - keine Kreuzungen ohne Straßen + // - Kreuzungen max. vier Eingehende und ausgehende Straßen + for (Entity entity : crossings) { + initCrossing((Crossing) entity); + } + //5: Straßen können invalide Kreuzungen haben + for (Street street : streetMap.values()) { + if (!checkStreets(street)) { + throw new LogicException("Street " + street.getId() + " has invalid crossings."); + } + } + initEntity(); + } + + private boolean checkIds(List entities) { + //True: alles ok; False: mehrfache IDs + List usedIds = new ArrayList<>(); + for (Entity entity : entities) { + int id = entity.getId(); + if (usedIds.contains(id)) { + return false; + } + else { + usedIds.add(id); + } + } + return true; + } + + private void initCrossing(Crossing crossing) throws LogicException { + crossingMap.put(crossing.getId(), crossing); + for (Street street : streetMap.values()) { + if (street.getEndNodeID() == crossing.getId()) { + crossing.addIncoming(street.getId()); //throws Logic Exception wenn die Kreuzung voll + } + if (street.getStartNodeID() == crossing.getId()) { + crossing.addOutgoing(street.getId()); //throws Logic Exception wenn die Kreuzung voll + } + } + if (!crossing.validCrossing()) { + throw new LogicException("Crossing " + crossing.getId() + " not valid"); //zu wenig Straßen + } + } + + private boolean checkStreets(Street street) { + //keine gleichen oder nicht existenten Kreuzungen + return street.getEndNodeID() != street.getStartNodeID() + && crossingMap.containsKey(street.getStartNodeID()) + && crossingMap.containsKey(street.getEndNodeID()); + } + + private void initEntity() { + //Den Straßen ihre Kreuzungen geben + for (Street street : streetMap.values()) { + street.setEndNode(crossingMap.get(street.getEndNodeID())); + } + //Den Kreuzungen ihre ausgehenden Straßen geben + for (Crossing crossing : crossingMap.values()) { + List outgoingStreets = new ArrayList<>(); + List outgoingStreetIDs = crossing.getOutgoingStreetsID(); + for (Integer id : outgoingStreetIDs) { + outgoingStreets.add(streetMap.get(id)); + } + crossing.setOutgoingStreets(outgoingStreets); + } + } + + /** Lässt eine Einheit Zeit vergehen. + * Aktualisiert alle Autos im Straßennetzwerk. + */ + public void doOneTick() { + //1. Aktualisierung aller Straßen aufsteigend dem Identifikator nach + //1.1 Geschwindigkeit der Autos anpassen + updateCars(); + //1.2 Autos bewegen: - nur mit eingehaltenem Abstand + // - Überholen möglich + // - nur ein Auto + // - Abstände einhalten + // - danach nicht abbiegen, davor nicht abbiegen + // - wenn am Ender der Straße und kann noch weiter fahren: Abbiegen + // - Maximalgeschwindigkeit der neuen Straße ignorieren, + // nicht zweimal abbiegen, nicht nochmal aktualisieren + // - Ampeln: nur wenn die Straße grün hat + // - Geschwindigkeit wird beim Nichtbewegen null + updateStreets(); + //2. Aktualisierung aller Kreuzungen aufsteigend dem Identifikator nach + updateTrafficLights(); //alle Ampeln umstellen + } + + private void updateTrafficLights() { + for (Crossing crossing : crossingMap.values()) { + if (crossing.getClass() == CrossingWithLight.class) { + ((CrossingWithLight) crossing).updateLights(); + } + } + } + + private void updateCars() { + //Aktualisiert die Geschwindigkeit der Autos + for (Car car : carMap.values()) { + int streetID = car.getStreetID(); + car.updateOneTick(streetMap.get(streetID).getMaxVelocity()); + } + } + + private void updateStreets() { + for (Street street : streetMap.values()) { + street.updateOneTick(); + } + //Setzt die Autos wieder auf standard + for (Car car : carMap.values()) { + if (!car.wasMoved()) { + car.setVelocity(0); + } + car.setWasMoved(true); //die Standardwerte + car.setHasTurned(false); + } + } + + /** Getter für ein beliebiges Auto. + * @param id Die Identifikationsnummer des Autos. + * @return Das Auto mit der entsprechenden ID. null, wenn es nicht existiert. + */ + public Car getCar(int id) { + return carMap.get(id); + } + + /** Gibt die Position eines Autos auf seiner Straße zurück. + * @param id Die Identifikationsnummer des Autos. + * @return Die Position des Autos auf seiner Straße. Als Ganzzahl. + * @throws LogicException Wenn das Auto nicht existiert. + */ + public int getCarLocation(int id) throws LogicException { + return streetMap.get(carMap.get(id).getStreetID()).getCarLocation(id); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Car.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Car.java new file mode 100644 index 0000000000..1081ca1efa --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Car.java @@ -0,0 +1,130 @@ +package edu.kit.informatik.logic.entitys; + +/** Klasse die ein Auto im Netzwerk implementiert. + * @author ujiqk + * @version 1.0 + */ +public class Car extends Entity { + + private static final int MINIMAL_VELOCITY = 20; + private static final int MAXIMAL_VELOCITY = 40; + private static final int MINIMAL_ACCELERATION = 1; + private static final int MAXIMAL_ACCELERATION = 10; + private final int desiredVelocity; //minimal 20, maximal 40 + private final int acceleration; //minimal 1, maximal 10 + private int desiredDirection; //0-3 + private int velocity; + private int streetID; + private boolean hasTurned; //bezieht sich auf den letzten Tick, standard ist false + private boolean wasMoved; //bezieht sich auf den letzten Tick, standard ist true + + /** Erzeugt ein neues Auto. + * @param id Die ID des Autos. Eine Ganzzahl größer gleich null. + * @param desiredVelocity Die Wunschgeschwindigkeit des Autos. Eine Ganzzahl. Sollte zwischen 20 und 40 liegen. + * @param acceleration Die Beschleunigung des Autos. Eine Ganzzahl. Sollte zwischen 1 und 10 liegen. + * @param streetId Die ID der Straße auf der das Auto starten soll. Eine Ganzzahl. Sollte größer 0 sein. + * @throws IllegalArgumentException Falls Parameter nicht im erlaubten Bereich liegen. + */ + public Car(int id, int desiredVelocity, int acceleration, int streetId) throws IllegalArgumentException { + super(id); + if (desiredVelocity < MINIMAL_VELOCITY || desiredVelocity > MAXIMAL_VELOCITY + || acceleration < MINIMAL_ACCELERATION || acceleration > MAXIMAL_ACCELERATION) { + throw new IllegalArgumentException("Illegal car parameters on street " + id); + } + if (streetId < 0) { + throw new IllegalArgumentException("Illegal car-street id " + id); + } + this.streetID = streetId; + this.desiredVelocity = desiredVelocity; + this.acceleration = acceleration; + desiredDirection = 0; + velocity = 0; + hasTurned = false; + wasMoved = true; + } + + /** Aktualisiert die Geschwindigkeit des Autos. + * der minimale Wert aus: Beschleunigung + Geschwindigkeit, Wunschgeschwindigkeit oder Höchstgeschwindigkeit + * @param maxStreetVelocity Die Höchstgeschwindigkeit der Straße auf der das Auto sich befindet. + */ + public void updateOneTick(int maxStreetVelocity) { + //Minimaler Wert aus: Beschleunigung + Geschwindigkeit, Wunschgeschwindigkeit oder Höchstgeschwindigkeit + velocity = Math.min(maxStreetVelocity, Math.min(desiredVelocity, velocity + acceleration)); + } + + /** Aktualisiert die Wunschrichtung des Autos. + * Der Wert rotiert von 1 bis 4. + */ + public void updateTurnDirection() { + desiredDirection++; + if (desiredDirection > 3) { + desiredDirection = 0; + } + } + + /** Setzt den Wert der angibt, ob das Auto in diesem Tick abgebogen ist. + * @param hasTurned Der neue Wert als boolean. + */ + public void setHasTurned(boolean hasTurned) { + this.hasTurned = hasTurned; + } + + /** Getter für den Wert der angibt, ob das Auto in diesem Tick schon abgebogen ist. + * @return True: Das Auto hat diesem Tick abgebogen. False: Das Auto hat in diesem Tick nicht abgebogen. + */ + public boolean hasTurned() { + return hasTurned; + } + + /** Setzt den Wert der angibt, ob das Auto in diesem Tick bewegt wurde. + * @param wasMoved Der neue Wert als boolean. + */ + public void setWasMoved(boolean wasMoved) { + this.wasMoved = wasMoved; + } + + /** Getter für den Wert der angibt, ob das Auto in diesem Tick schon bewegt wurde. + * @return True: Das Auto wurde in diesem Tick bewegt. False: Das Auto wurde in diesem Tick nicht bewegt. + */ + public boolean wasMoved() { + return wasMoved; + } + + /** Setzt die ID der Straße, auf der das Auto sich befindet. + * @param id Die ID der Straße auf der das Auto sich befindet. Eine Ganzzahl. + */ + public void setStreetID(int id) { + streetID = id; + } + + /** Getter für die ID der Straße, auf der das Auto sich befindet. + * @return Die ID der Straße auf der das Auto sich befindet. Eine Ganzzahl. + */ + public int getStreetID() { + return streetID; + } + + /** Setzt die Geschwindigkeit des Autos auf einen neuen Wert. + * @param velocity Die neue Geschwindigkeit des Autos. Eine positive Ganzzahl. + */ + public void setVelocity(int velocity) { + if (velocity < 0) { + throw new IllegalArgumentException("Illegal velocity " + velocity); + } + this.velocity = velocity; + } + + /** Getter für die aktuelle Geschwindigkeit des Autos. + * @return Die aktuelle Geschwindigkeit des Autos. Eine positive Ganzzahl. + */ + public int getVelocity() { + return velocity; + } + + /** Getter für die Wunschrichtung des Autos. + * @return Die Wunschrichtung des Autos. Eine Ganzzahl zwischen 0 und 3. + */ + public int getDesiredDirection() { + return desiredDirection; + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Crossing.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Crossing.java new file mode 100644 index 0000000000..03b73d3dc8 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Crossing.java @@ -0,0 +1,126 @@ +package edu.kit.informatik.logic.entitys; + +import edu.kit.informatik.logic.LogicException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** Implementiert einen Kreisel im Straßennetz. + * Ein Kreisel ist eine Kreuzung ohne Ampel, an der alle gleichzeitig abbiegen können. + * @author ujiqk + * @version 1.0 + */ +public class Crossing extends Entity { + + private static final int MAX_CONNECTED_STREETS = 4; + + private final List outgoingStreetsID; //Die Straßen + private List outgoingStreets; + private final List incomingStreetsID; + + /** Konstruktor der eine neue Kreuzung ohne verbundene Straßen erstellt. + * @param id Die ID der Kreuzung. Eine Ganzzahl größer gleich null. + * @throws IllegalArgumentException Falls Parameter nicht im erlaubten Bereich liegen. + */ + public Crossing(int id) throws IllegalArgumentException { + super(id); + outgoingStreetsID = new ArrayList<>(); + incomingStreetsID = new ArrayList<>(); + } + + /** Methode um ein Auto über die Kreuzung abbiegen zu lassen. + * @param car Das Auto das abbiegen soll. + * @param distance Die Distanz, die das Auto nach der Abbiegung fahren soll. + * @return True, wenn das Auto abbiegen konnte; False, wenn nicht. + */ + public boolean turnCar(Car car, int distance) { + if (outgoingStreets == null) { //outgoingStreets muss gesetzt sein + throw new IllegalArgumentException(); + } + int direction = car.getDesiredDirection(); //falls die Richtung nicht existiert + if (outgoingStreets.size() <= direction) { + direction = 0; + } + Street street = outgoingStreets.get(direction); + //Versuchen abzubiegen //-1 da drivablePosition die alte position ignoriert + int drivableDistance = street.drivablePosition(-1, distance); + if (drivableDistance < 0) { + return false; //Abbiegen nicht möglich + } + else { //Abbiegen + street.addCar(car, Math.min(street.drivablePosition(0, distance), street.getLength())); + car.setHasTurned(true); + car.updateTurnDirection(); + car.setStreetID(street.getId()); + if (drivableDistance > 0) { + car.setWasMoved(true); + } + return true; + } + } + + /** Setzt die ausgehenden Straßen der Kreuzung. + * @param outgoingStreets Alle ausgehenden Straßen der Kreuzung als geordnete Liste. + * Muss mit den IDs der ausgehenden Straßen übereinstimmen. + */ + public void setOutgoingStreets(List outgoingStreets) { + for (int i = 0; i < outgoingStreets.size(); i++) { + if (outgoingStreets.get(i).getId() != outgoingStreetsID.get(i)) { + throw new IllegalArgumentException(); + } + } + this.outgoingStreets = outgoingStreets; + } + + /** Getter für die eingehenden StraßenIDs. + * @return Eine Liste mit den IDs aller eingehenden Straßen. + */ + public List getIncomingStreetsID() { + return Collections.unmodifiableList(incomingStreetsID); + } + + /** Getter für die ausgehenden StraßenIDs. + * @return Eine Liste mit den IDs aller ausgehenden Straßen. + */ + public List getOutgoingStreetsID() { + return Collections.unmodifiableList(outgoingStreetsID); + } + + /** Fügt der Kreuzung eine eingehende Straße hinzu. + * @param street Die ID der eingehenden Straße. + * @throws LogicException Wenn die Kreuzung zu viele eingehende Straßen hat. + */ + public void addIncoming(int street) throws LogicException { + if (incomingStreetsID.size() < MAX_CONNECTED_STREETS) { + incomingStreetsID.add(street); + } + else { + throw new LogicException("Too much Streets for crossing: " + getId()); + } + } + + /** Fügt der Kreuzung eine ausgehende Straße hinzu. + * @param street Die ID der ausgehenden Straße. + * @throws LogicException Wenn die Kreuzung zu viele ausgehende Straßen hat. + */ + public void addOutgoing(int street) throws LogicException { + if (outgoingStreetsID.size() < MAX_CONNECTED_STREETS) { + outgoingStreetsID.add(street); + } + else { + throw new LogicException("Too much Streets for crossing: " + getId()); + } + } + + /** Prüft, ob die Kreuzung gültig ist. + * Sie ist gültig, wenn sie mindestens eine eingehende und eine ausgehende Straße hat. + * Außerdem darf sie nicht mehr als 4 eingehende und 4 ausgehende Straßen haben. + * @return True, wenn die Kreuzung gültig ist; False, wenn nicht. + */ + public boolean validCrossing() { + return !outgoingStreetsID.isEmpty() && !incomingStreetsID.isEmpty() + && outgoingStreetsID.size() <= MAX_CONNECTED_STREETS && incomingStreetsID.size() <= MAX_CONNECTED_STREETS; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/CrossingWithLight.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/CrossingWithLight.java new file mode 100644 index 0000000000..e3a0d17b82 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/CrossingWithLight.java @@ -0,0 +1,63 @@ +package edu.kit.informatik.logic.entitys; + +/** Implementiert eine Kreuzung mit Ampelschaltung. + * Nur von der einen Straße die gerade Grün hat, können Autos abbiegen. + * @author ujiqk + * @version 1.0 + */ +public class CrossingWithLight extends Crossing { + + private static final int MIN_GREEN_PHASE_DURATION = 3; + private static final int MAX_GREEN_PHASE_DURATION = 10; + private final int greenPhaseDuration; + private int greenPhase; + private int greenIndicator; + + /** Konstruktor für eine Kreuzung mit Ampelschaltung. + * @param id Die ID der Kreuzung. Eine Ganzzahl größer gleich null. + * @param greenPhaseDuration Die Dauer der Grünphase. Eine Ganzzahl. Sollte zwischen 3 und 10 liegen. + * @throws IllegalArgumentException Falls Parameter nicht im erlaubten Bereich liegen. + */ + public CrossingWithLight(int id, int greenPhaseDuration) throws IllegalArgumentException { + super(id); + if (greenPhaseDuration < MIN_GREEN_PHASE_DURATION || greenPhaseDuration > MAX_GREEN_PHASE_DURATION) { + throw new IllegalArgumentException("Illegal crossing duration on crossing " + id); + } + this.greenPhaseDuration = greenPhaseDuration; + greenIndicator = 0; + greenPhase = greenPhaseDuration; + } + + /** Methode um ein Auto über die Kreuzung abbiegen zu lassen. + * @param car Das Auto das abbiegen will. + * @param distance Die Distanz, die das Auto nach der Abbiegung fahren soll. + * @return True, wenn das Auto abbiegen konnte; False, wenn nicht. + */ + @Override + public boolean turnCar(Car car, int distance) { + //Schauen, wo das Auto herkommt + int direction = getIncomingStreetsID().indexOf(car.getStreetID()); + //Abbiegen + if (direction != greenIndicator) { //Ampel ist Rot + return false; + } + else { //Ampel ist Grün + return super.turnCar(car, distance); //--> normal Abbiegen + } + } + + /** Aktualisiert die Ampelschaltung um eine Zeiteinheit. + */ + public void updateLights() { + greenPhase--; + if (greenPhase == 0) { + //Ampel umschalten + greenPhase = greenPhaseDuration; + greenIndicator++; + if (greenIndicator >= getIncomingStreetsID().size()) { + greenIndicator = 0; + } + } + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Entity.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Entity.java new file mode 100644 index 0000000000..016909113f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Entity.java @@ -0,0 +1,30 @@ +package edu.kit.informatik.logic.entitys; + +/** Überklasse für alle Elemente des Straßennetzwerkes. + * Sie enthält die ID jedes Elements. + * @author ujiqk + * @version 1.0 + */ +public class Entity { + + private final int id; //Der Identifikator ist eine Ganzzahl im Bereich [0, Integer.MAX_VALUE] + + /** Erzeugt ein Element mit einer ID + * @param id Die ID. Muss größer oder gleich 0 sein. + * @throws IllegalArgumentException Wenn die ID kleiner als 0 ist. + */ + protected Entity(int id) throws IllegalArgumentException { + if (id < 0) { + throw new IllegalArgumentException("Illegal ID " + id); + } + this.id = id; + } + + /** Getter für die ID + * @return Die ID des Elements. Eine Ganzzahl im Bereich [0, Integer.MAX_VALUE]. + */ + public int getId() { + return id; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Street.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Street.java new file mode 100644 index 0000000000..2347883ce7 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/Street.java @@ -0,0 +1,235 @@ +package edu.kit.informatik.logic.entitys; + +import edu.kit.informatik.logic.LogicException; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** Implementiert eine Straße im Straßennetzwerk. + * Auf ihr können Autos fahren. + * @author ujiqk + * @version 1.0 + */ +public class Street extends Entity { + + private static final int MIN_LENGTH = 10; + private static final int MAX_LENGTH = 10000; + private static final int MIN_VELOCITY = 5; + private static final int MAX_VELOCITY = 40; + private static final int CAR_SPACING = 10; + private final int length; //minimal 10, maximal 10000 + private final int maxVelocity; //minimal 5, maximal 40 + private final int startNodeID; + private final int endNodeID; + private Crossing endNode; + + //Startknoten: 0 --- length :Endknoten + private final HashMap cars; // + + /** Erzeugt eine neue Straße mit den gegebenen Parametern. + * @param id Die ID der Straße. Eine Ganzzahl größer gleich null. + * @param length Die Länge der Straße. Eine Ganzzahl. + * @param maxVelocity Die Höchstgeschwindigkeit auf der Straße. Eine Ganzzahl. + * @param startNodeID Die ID der Kreuzung, an der die Straße beginnt. Eine Ganzzahl. + * @param endNodeID Die ID der Kreuzung, an der die Straße endet. Eine Ganzzahl. + * @throws IllegalArgumentException Wenn Parameter nicht den Vorgaben entsprechen. + */ + public Street(int id, int length, int maxVelocity, int startNodeID, int endNodeID) throws IllegalArgumentException { + super(id); + if (length < MIN_LENGTH || length > MAX_LENGTH + || maxVelocity < MIN_VELOCITY || maxVelocity > MAX_VELOCITY) { + throw new IllegalArgumentException("Illegal street parameters on street " + id); + } + this.length = length; + this.maxVelocity = maxVelocity; + this.endNodeID = endNodeID; + this.startNodeID = startNodeID; + cars = new HashMap<>(length); + } + + /** Updatet die Straße um eine Zeiteinheit. // + * Verschiebt dabei die Autos auf der Straße und lässt sie gegeben falls abbiegen oder überholen. + * Kann erst aufgerufen werden, wenn eine Endkreuzung als Element gesetzt wurde. + */ + public void updateOneTick() { + if (endNode == null) { + throw new IllegalArgumentException(); + } + //alle Autos durchgehen: + //Alle Positionen rückwärts sortiert + Set carPositions = new TreeSet<>(Comparator.reverseOrder()); + carPositions.addAll(cars.keySet()); + for (Integer position : carPositions) { + //mit dem letzten unbewegten Auto anfangen + Car lastCar = cars.get(position); + if (!lastCar.hasTurned()) { + updateCarOnStreet(lastCar, position); + } + } + } + + private void updateCarOnStreet(Car lastCar, int position) { + int speed = lastCar.getVelocity(); + int newPosition = position + speed; //Die Position wo das Auto hin will + //3 Möglichkeiten: 1: Kreuzung erreicht, 2: neue Position frei, 3: Auto im Weg + int drivablePosition = drivablePosition(position, newPosition); + if (drivablePosition == position) { + lastCar.setWasMoved(false); + return; + } + //1 Kreuzung + if (drivablePosition > length) { + if (position == length) { + lastCar.setWasMoved(false); + } + //Der verbleibende Weg kann nach dem Abbiegen weitergefahren werden. + boolean canTurn = endNode.turnCar(lastCar, newPosition - length); + if (!canTurn) { //Abbiegen war nicht möglich + cars.put(length, lastCar); //Das Auto bleibt am Ende stehen + if (position != length) { + cars.remove(position); //Auto war vorher nicht am Ende + } + return; + } + } + //2/3 fahren + else { + //Das Auto fährt so weit es kann + cars.put(drivablePosition, lastCar); + } + cars.remove(position); + } + + /** Gibt die Position zurück, an der das Auto beim Fahren aufgrund von Abstandsregeln stehenbleiben muss. + * @param oldPosition Die Position des Autos vor dem Fahren. Eine Ganzzahl zwischen 0 und length. + * @param newPosition Die Position, an die das Auto optimalerweise fahren will. Eine Ganzzahl zwischen 0 und length. + * @return Die Position, an der das Auto stehenbleiben muss. Eine Ganzzahl zwischen 0 und length. + */ + public int drivablePosition(int oldPosition, int newPosition) { + //Geht durch alle Koordinaten von der alten Position bis CAR_SPACING mehr als die neue durch. + int nextCar = -1; + for (int i = oldPosition + 1; i < newPosition + CAR_SPACING; i++) { + if (cars.get(i) != null) { + nextCar = i; + break; + } + } + if (nextCar == -1) { + return newPosition; //Alles frei + } + else { + return nextCar - CAR_SPACING; //Auto im Weg + } + } + + /** Fügt ein Auto an die hinterste freie Stelle der Straße hinzu. + * Hält dabei Abstand zu den anderen Autos ein. + * @param car Das Auto, das hinzugefügt werden soll. + * @throws LogicException Falls die Straße bereits voll ist. + */ + public void addCarForemost(Car car) throws LogicException { + //cars: + if (cars.size() < length / CAR_SPACING + 1) { + //Straße noch nicht voll + int position; + if (cars.isEmpty()) { + position = length; + } + else { + position = Collections.min(cars.keySet()) - CAR_SPACING; + } + cars.put(position, car); //fügt ein Auto mit Abstand hinten an + } + else { //Straße voll + throw new LogicException("Street " + getId() + " is already full"); + } + } + + /** Fügt ein Auto an der gegebenen Position hinzu. + * @param car Das Auto, das hinzugefügt werden soll. + * @param position Die Position, an der das Auto hinzugefügt werden soll. Eine Ganzzahl zwischen 0 und length. + */ + public void addCar(Car car, int position) { + cars.put(position, car); + } + + /** Getter für die Autos auf der Straße. + * @return Die Autos auf der Straße. + * Eine sortierte Map mit den Positionen der Autos als Schlüssel und den Autos als Werte. + */ + public Map getCars() { + return cars; + } + + /** Getter für die ID der Kreuzung, an der die Straße beginnt. + * @return Die ID der Kreuzung, an dem die Straße beginnt. + */ + public int getStartNodeID() { + return startNodeID; + } + + /** Setzt die Kreuzung als Objekt, an der die Straße endet. + * @param crossing Die Kreuzung, an der die Straße endet. Muss die Kreuzung mit der Konstruktoren-ID sein.S + */ + public void setEndNode(Crossing crossing) { + if (endNodeID != crossing.getId()) { + throw new IllegalArgumentException("Crossing ID does not match"); + } + endNode = crossing; + } + + /** Getter für die Kreuzung, an der die Straße endet. + * @return Die Kreuzung, an der die Straße endet.S + */ + public Crossing getEndNode() { + return endNode; + } + + /** Getter für die ID der Kreuzung, an der die Straße endet. + * @return Die ID der Kreuzung, an dem die Straße endet. Eine Ganzzahl größer gleich 0. + */ + public int getEndNodeID() { + return endNodeID; + } + + /** Getter für die maximale Geschwindigkeit der Straße. + * @return Die maximale Geschwindigkeit der Straße. Eine Ganzzahl. + */ + public int getMaxVelocity() { + return maxVelocity; + } + + /** Getter für die Länge der Straße. + * @return Die Länge der Straße. Eine Ganzzahl. + */ + public int getLength() { + return length; + } + + /** Getter für den mindestabstand zwischen Autos. + * @return Der mindestabstand zwischen Autos. Eine Ganzzahl. + */ + public int getCarSpacing() { + return CAR_SPACING; + } + + /** Gibt die Position des Autos mit der gegebenen ID zurück. + * @param id Die ID des Autos. Eine Ganzzahl größer gleich 0. + * @return Die Position des Autos auf der Straße. Eine Ganzzahl zwischen 0 und length. + * @throws LogicException Falls das Auto nicht auf der Straße ist. + */ + public int getCarLocation(int id) throws LogicException { + //cars: + for (Map.Entry entry : cars.entrySet()) { + if (entry.getValue().getId() == id) { + return entry.getKey(); + } + } + throw new LogicException("Car with id " + id + " not found"); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/StreetWithFastLane.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/StreetWithFastLane.java new file mode 100644 index 0000000000..57dac488ae --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/logic/entitys/StreetWithFastLane.java @@ -0,0 +1,116 @@ +package edu.kit.informatik.logic.entitys; + +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; + +/** Implementiert eine Straße mit Überholspur für das Straßennetz. + * Auf ihr können Autos überholen, wenn alle Mindestabstände eingehalten werden. + * @author ujiqk + * @version 1.0 + */ +public class StreetWithFastLane extends Street { + + /** Erzeugt eine neue Straße mit Überholspur mit den gegebenen Parametern. + * @param id Die ID der Straße. Eine Ganzzahl größer gleich null. + * @param length Die Länge der Straße. Eine Ganzzahl. + * @param maxVelocity Die Höchstgeschwindigkeit auf der Straße. Eine Ganzzahl. + * @param startNodeID Die ID der Kreuzung, an der die Straße beginnt. Eine Ganzzahl. + * @param endNodeID Die ID der Kreuzung, an der die Straße endet. Eine Ganzzahl. + * @throws IllegalArgumentException Wenn Parameter nicht den Vorgaben entsprechen. + */ + public StreetWithFastLane(int id, int length, int maxVelocity, int startNodeID, int endNodeID) + throws IllegalArgumentException { + super(id, length, maxVelocity, startNodeID, endNodeID); + } + + /** Updatet die Straße um eine Zeiteinheit. + * Verschiebt dabei die Autos auf der Straße und lässt sie gegeben falls abbiegen oder überholen. + * Kann erst aufgerufen werden, wenn eine Endkreuzung als Element gesetzt wurde. + */ + @Override + public void updateOneTick() { + if (getEndNode() == null) { + throw new IllegalArgumentException(); + } + //alle Autos von hinten (Ende der Straße) durchgehen: + Set carPositions = new TreeSet<>(Comparator.reverseOrder()); + carPositions.addAll(getCars().keySet()); + for (Integer position : carPositions) { + //mit dem letzten unbewegten Auto anfangen + Car lastCar = getCars().get(position); + if (!lastCar.hasTurned()) { + updateCarOnStreet(lastCar, position); + } + } + } + + private void updateCarOnStreet(Car lastCar, int position) { + int speed = lastCar.getVelocity(); + int newPosition = position + speed; //Die Position wo das Auto hin will + //3 Möglichkeiten: 1: Kreuzung erreicht, 2: fahren, 3: Überholen + int drivablePosition = drivablePosition(position, newPosition); + //1 Kreuzung + if (drivablePosition > getLength()) { + if (position == getLength()) { + lastCar.setWasMoved(false); + } + //Der verbleibende Weg kann nach dem Abbiegen weitergefahren werden. + boolean canTurn = getEndNode().turnCar(lastCar, newPosition - getLength()); + if (!canTurn) { //Abbiegen war nicht möglich + getCars().put(getLength(), lastCar); //Das Auto bleibt am Ende stehen + if (position != getLength()) { + getCars().remove(position); //Auto war vorher nicht am Ende + } + return; + } + } + //2 Strecke fahren + else { + //Das Auto fährt so weit es kann oder überholt + if (drivablePosition == newPosition) { + getCars().put(drivablePosition, lastCar); + } + //3 Überholen oder vorher stehenbleiben + else { + overtakeIfPossible(position, newPosition, lastCar); + } + } + if (lastCar.wasMoved() || lastCar.hasTurned()) { + getCars().remove(position); + } + } + + private void overtakeIfPossible(int position, int newPosition, Car car) { + boolean seenCar = false; + int nextCarPosition = 0; //Existiert auf jeden Fall + int nextNextCarPosition = Math.min(newPosition, getLength()) + getCarSpacing(); //muss nicht existieren + //1: Positionen der nächsten Autos bekommen + for (int i = position + 1; i <= newPosition + getCarSpacing(); i++) { + if (getCars().get(i) != null) { //Stelle ist besetzt + if (!seenCar) { + nextCarPosition = i; + seenCar = true; + } + else { + nextNextCarPosition = i; + break; + } + } + } + //2: Schauen, bis wo wir fahren können + if (nextNextCarPosition - nextCarPosition >= getCarSpacing() * 2) { //Wir können überholen + getCars().put(nextNextCarPosition - getCarSpacing(), car); + car.setWasMoved(true); + } + else { + if (nextCarPosition - getCarSpacing() == position) { //Stau + car.setWasMoved(false); + } + else { + getCars().put(nextCarPosition - getCarSpacing(), car); //bis zum nächsten Fahren + } + } + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/ui/SimUi.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/ui/SimUi.java new file mode 100644 index 0000000000..82c3a3172a --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/informatik/ui/SimUi.java @@ -0,0 +1,239 @@ +package edu.kit.informatik.ui; + +import edu.kit.informatik.logic.LogicException; +import edu.kit.informatik.logic.SimEngine; +import edu.kit.informatik.logic.entitys.Car; +import edu.kit.informatik.logic.entitys.Crossing; +import edu.kit.informatik.logic.entitys.CrossingWithLight; +import edu.kit.informatik.logic.entitys.Entity; +import edu.kit.informatik.logic.entitys.Street; +import edu.kit.informatik.logic.entitys.StreetWithFastLane; +import edu.kit.kastel.trafficsimulation.io.SimulationFileLoader; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Pattern; + +/** Diese Klasse macht die Benutzerinteraktion des Programms. + * Sie liest die Eingaben ein und gibt die Ausgaben aus. + * @author ujiqk + * @version 1.0 + */ +public class SimUi { + + private static final String READY = "READY"; + private static final String OUTPUT = "Car %d on street %d with speed %d and position %d"; + private static final String ERROR_INVALID_TICK_NUMBER = "Error: Invalid Number of Ticks!"; + private static final String ERROR_DATA_NOT_LOADED = "Error: Data not loaded!"; + private static final String ERROR_INVALID_ID = "Error: Invalid Id!"; + private static final String ERROR_INVALID_FOLDER_PATH = "Error: Invalid Folder Path!"; + private static final String ERROR_INVALID_COMMAND = "Error: Invalid Command!"; + private static final String ERROR_DEFAULT = "Error: "; + + private boolean quit; + private boolean fileLoaded; + private SimEngine simEngine; + private int lastStreetId; //nur zum Netzwerke laden + + /** Erstellt eine standard Benutzerwinteraktionsklasse. + * Sie liest die Eingaben ein und gibt die Ausgaben aus. + */ + public SimUi() { + lastStreetId = -1; + fileLoaded = false; + Scanner inputScanner = new Scanner(System.in); + quit = false; + while (!quit) { + readInput(inputScanner.nextLine()); + } + inputScanner.close(); + } + + private void readInput(String input) { + //'quit' befehl befolgen + if (Pattern.matches("quit", input)) { + quit = true; + //Hiernach darf nichts mehr kommen + } + //'load' Befehl + else if (Pattern.matches("load \\S+", input)) { //Regex für Dateien + loadFile(input.split(" ")[1]); + } + //'simulate' Befehl + else if (Pattern.matches("simulate \\d+", input)) { + simulate(input.split(" ")[1]); + } + //'position' Befehl + else if (Pattern.matches("position \\d+", input)) { + printPosition(input.split(" ")[1]); + } + //kein gültiger Befehl + else { + System.err.println(ERROR_INVALID_COMMAND); + } + } + + private void loadFile(String path) { + List cars; + List streets; + List crossings; + lastStreetId = -1; + //Dateien laden + try { + SimulationFileLoader fileLoader = new SimulationFileLoader(path); + cars = fileLoader.loadCars(); + streets = fileLoader.loadStreets(); + crossings = fileLoader.loadCrossings(); + } + catch (IOException e) { + System.err.println(ERROR_INVALID_FOLDER_PATH); + return; + } + //Versuchen ein Straßennetzwerk zu erzeugen + try { + simEngine = new SimEngine(parseCars(cars), parseStreets(streets), parseCrossings(crossings)); + } + catch (LogicException | IllegalArgumentException e) { + System.err.println(ERROR_DEFAULT + e.getMessage()); + return; + } + fileLoaded = true; + System.out.println(READY); + } + + private List parseCars(List carsStrings) throws IllegalArgumentException { + //aus Strings Autos bekommen + List cars = new ArrayList<>(); + for (String entry : carsStrings) { + if (Pattern.matches("\\d+,\\d+,\\d+,\\d+", entry)) { + //einzelne Zahlen parsen + int id = Integer.parseInt(entry.split(",")[0]); + int streetId = Integer.parseInt(entry.split(",")[1]); + int desiredVelocity = Integer.parseInt(entry.split(",")[2]); + int acceleration = Integer.parseInt(entry.split(",")[3]); + Car next = new Car(id, desiredVelocity, acceleration, streetId); + cars.add(next); //kann IllegalArgument werfen + } + else { + throw new IllegalArgumentException("wrong car file format"); + } + } + return cars; + } + + private List parseCrossings(List crossingsStrings) throws IllegalArgumentException { + //aus Strings Kreuzungen bekommen + List crossings = new ArrayList<>(); + for (String entry : crossingsStrings) { + if (Pattern.matches("\\d+:\\d+t", entry)) { + //Einzelne Zahlen parsen + int id = Integer.parseInt(entry.split(":")[0]); + int duration = Integer.parseInt(entry.split(":")[1].split("t")[0]); + Crossing next; + if (duration == 0) { + next = new Crossing(id); //wirft IllegalArgument + } + else { + next = new CrossingWithLight(id, duration); //wirft IllegalArgument + } + crossings.add(next); + } + else { + throw new IllegalArgumentException("wrong crossing file format"); + } + } + return crossings; + } + + private List parseStreets(List streetsStrings) throws IllegalArgumentException { + //aus Strings Straßen bekommen + List streets = new ArrayList<>(); + for (String entry : streetsStrings) { + if (Pattern.matches("\\d+-->\\d+:\\d+m,[1,2]x,\\d+max", entry)) { + //einzelne Zahlen parsen + int startNode = Integer.parseInt(entry.split("-")[0]); + int endNode = Integer.parseInt(entry.split(">")[1].split(":")[0]); + int length = Integer.parseInt(entry.split(":")[1].split("m")[0]); + int type = Integer.parseInt(entry.split(",")[1].split("x")[0]); + int maxVelocity = Integer.parseInt(entry.split(",")[2].split("max")[0]); + int id = getNewStreetID(); + Street next; + if (type == 1) { //normale Straße + next = new Street(id, length, maxVelocity, startNode, endNode); //wirft IllegalArgument + } + else if (type == 2) { //mit Überholspur + next = new StreetWithFastLane(id, length, maxVelocity, startNode, endNode); //wirft IllegalArgument + } + else { + throw new IllegalArgumentException("illegal street type"); //falscher Typ + } + streets.add(next); + } + else { + throw new IllegalArgumentException("wrong Street file format"); + } + } + return streets; + } + + private int getNewStreetID() { + int id = lastStreetId + 1; + lastStreetId = id; + return id; + } + + private void simulate(String ticksString) { + if (!fileLoaded ) { //Noch kein Netzwerk geladen + System.err.println(ERROR_DATA_NOT_LOADED); + return; + } + int ticks; + try { + ticks = Integer.parseInt(ticksString); + } + catch (NumberFormatException e) { + System.err.println(ERROR_INVALID_TICK_NUMBER); + return; + } + if (ticks < 0) { + System.err.println(ERROR_INVALID_TICK_NUMBER); + return; + } + for (int i = 0; i < ticks; i++) { + simEngine.doOneTick(); + } + System.out.println(READY); + } + + private void printPosition(String idString) { + if (!fileLoaded ) { + System.err.println(ERROR_DATA_NOT_LOADED); //Noch kein Netzwerk geladen + return; + } + int id; + try { + id = Integer.parseInt(idString); + } + catch (NumberFormatException e) { + System.err.println(ERROR_INVALID_ID); + return; + } + Car car = simEngine.getCar(id); + if (car == null) { + System.err.println(ERROR_INVALID_ID); //Auto gibt es nicht + } + else { + try { + System.out.printf(OUTPUT, id, car.getStreetID(), car.getVelocity(), simEngine.getCarLocation(id)); + System.out.println(); + } + catch (LogicException e) { + System.err.println(ERROR_DEFAULT + e.getMessage()); //Auto gibt es nicht 2 + } + } + + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/kastel/trafficsimulation/io/SimulationFileLoader.java b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/kastel/trafficsimulation/io/SimulationFileLoader.java new file mode 100644 index 0000000000..3518411609 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/complex2/Submission-02/kastel/trafficsimulation/io/SimulationFileLoader.java @@ -0,0 +1,107 @@ +package edu.kit.kastel.trafficsimulation.io; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * File loader for simulation files. + * + * @author Lucas Alber + * @version 1.0 + */ +public final class SimulationFileLoader { + + /** + * The filename for the simulation data representing streets. + */ + public static final String FILENAME_STREETS = "streets.sim"; + /** + * The filename for the simulation data representing crossings. + */ + public static final String FILENAME_CROSSINGS = "crossings.sim"; + /** + * The filename for the simulation data representing cars. + */ + public static final String FILENAME_CARS = "cars.sim"; + + + private final Path folderPath; + + + /** + * Creates a new {@link SimulationFileLoader}. + * + * @param folderPath a path to a folder containing the three simulation files. + * @throws IOException if the folder does not exist or the path is pointing to a normal file. + */ + public SimulationFileLoader(final String folderPath) throws IOException { + this.folderPath = Path.of(folderPath).normalize().toAbsolutePath(); + final File folder = this.folderPath.toFile(); + + if (!folder.exists()) { + throw new IOException(String.format("folder %s does not exist.", this.folderPath.toString())); + } + if (!folder.isDirectory()) { + throw new IOException(String.format("%s is not a directory.", this.folderPath.toString())); + } + } + + + /** + * Loads the simulation file {@value FILENAME_STREETS} and returns the lines as list of String. + * + * The returned value is never {@code null}. An empty list is returned, if the file is empty. + * + * @return the lines of the file as list of String. + * + * @throws IOException if the file does not exist or points to a directory. + */ + public List loadStreets() throws IOException { + return loadSimulationFile(FILENAME_STREETS); + } + + /** + * Loads the simulation file {@value FILENAME_CROSSINGS} and returns the lines as list of String. + * + * The returned value is never {@code null}. An empty list is returned, if the file is empty. + * + * @return the lines of the file as list of String. + * + * @throws IOException if the file does not exist or points to a directory. + */ + public List loadCrossings() throws IOException { + return loadSimulationFile(FILENAME_CROSSINGS); + } + + /** + * Loads the simulation file {@value FILENAME_CARS} and returns the lines as list of String. + * + * The returned value is never {@code null}. An empty list is returned, if the file is empty. + * + * @return the lines of the file as list of String. + * + * @throws IOException if the file does not exist or points to a directory. + */ + public List loadCars() throws IOException { + return loadSimulationFile(FILENAME_CARS); + } + + + private List loadSimulationFile(String fileName) throws IOException { + final Path filePath = this.folderPath.resolve(Path.of(fileName)); + final File file = filePath.toFile(); + + if (!file.exists()) { + throw new IOException(String.format("file %s does not exist.", filePath.toString())); + } + if (!file.isFile()) { + throw new IOException(String.format("file %s is not a normal file.", filePath.toString())); + } + + return Files.readAllLines(filePath); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/conditional/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/conditional/Submission-01/Main.java new file mode 100644 index 0000000000..2478fc055c --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/conditional/Submission-01/Main.java @@ -0,0 +1,23 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + private int result3; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int x = Integer.parseInt(args[0]); + int a = 5; + int b = 10; + + result = a > 3 ? b : a; + result2 = x < 3 ? b : a; + result3 = x == 6 ? a + b : a - b; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/deadCode11/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/deadCode11/Submission-01/Main.java new file mode 100644 index 0000000000..c5178b005f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/deadCode11/Submission-01/Main.java @@ -0,0 +1,32 @@ +package edu.kit.informatik; + +public final class Main { + private static int result; + + public Main(int x) { + if (x > 10) { + result = 1; + } else { + result = 2; + } + } + + public static void main(String[] args) { + Main m = new Main(15); + // result should be 1 + } +} + +class DeadClass { + static { + System.out.println("Static init"); + } + + { + System.out.println("Instance init"); + } + + public DeadClass() { + System.out.println("Constructor"); + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/deadCode2/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/deadCode2/Submission-01/Main.java new file mode 100644 index 0000000000..813b197145 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/deadCode2/Submission-01/Main.java @@ -0,0 +1,11 @@ +package edu.kit.informatik; + +public final class Main { + private static int result; + + public static void main(String[] args) { + result = 1; + return; + result = 2; // Dead code + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/deadCode3/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/deadCode3/Submission-01/Main.java new file mode 100644 index 0000000000..77f59ad626 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/deadCode3/Submission-01/Main.java @@ -0,0 +1,13 @@ +package edu.kit.informatik; + +public final class Main { + private static int result; + + public static void main(String[] args) { + result = 1; + } + + public static void deadMethod() { + result = 2; // Dead method + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/deadCode5/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/deadCode5/Submission-01/Main.java new file mode 100644 index 0000000000..93d7fc87d5 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/deadCode5/Submission-01/Main.java @@ -0,0 +1,15 @@ +package edu.kit.informatik; + +public final class Main { + private static int result; + + public static void main(String[] args) { + result = 1; + } +} + +class DeadClass { + public void deadMethod() { + System.out.println("I am dead"); + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/enum/Submission-01/InputState.java b/languages/java-cpg/src/test/resources/java/ai/enum/Submission-01/InputState.java new file mode 100644 index 0000000000..9cca66f96e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/enum/Submission-01/InputState.java @@ -0,0 +1,10 @@ +package edu.kit.informatik; + +public enum InputState { + PLAYER_COUNT, + PLAYER_NAMES, + START_GOLD, + WIN_GOLD, + SHUFFLE, + READY +} diff --git a/languages/java-cpg/src/test/resources/java/ai/enum/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/enum/Submission-01/Main.java new file mode 100644 index 0000000000..3fdb416a09 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/enum/Submission-01/Main.java @@ -0,0 +1,28 @@ +package edu.kit.informatik; + + +public final class Main { + + private InputState state;// = InputState.PLAYER_NAMES; + private InputState state2 = InputState.PLAYER_NAMES; + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + + state = InputState.PLAYER_COUNT; + + state2 = InputState.PLAYER_COUNT; + + if (state == InputState.PLAYER_COUNT) { + result2 = 100; + } + + result = 400; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/exception/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/exception/Submission-01/Main.java new file mode 100644 index 0000000000..d1f2829e42 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/exception/Submission-01/Main.java @@ -0,0 +1,26 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + + int z = 500; + int y = 100; + + if (z != args.length) { + throw new IllegalArgumentException("Input size mismatch"); + } + + result = z - y; //400 + result2 = y; //100 + + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/forEach/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/forEach/Submission-01/Main.java new file mode 100644 index 0000000000..812105bfcb --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/forEach/Submission-01/Main.java @@ -0,0 +1,24 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + + for (String input : args) { + z++; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/if/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/if/Submission-01/Main.java new file mode 100644 index 0000000000..7295b37036 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/if/Submission-01/Main.java @@ -0,0 +1,37 @@ +package edu.kit.informatik; + +/** + * @author ujiqk + * @version 1.0 + */ +public final class Main { + + private static final String ERROR_ARGUMENTS_NOT_SUPPORTED = "Error: Commandline arguments not supported"; + private static final String ERROR_ARGUMENTS_NOT_SUPPORTED2; + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + /** + * @param args Kommandozeilenparameter mit Wert von x. + */ + public static void main(String[] args) { + + int z = 500; + int y = 100; + + if (y < 100) { + //never run + z = z + 100; + } else { + z = z - 100; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/ifAnd/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/ifAnd/Submission-01/Main.java new file mode 100644 index 0000000000..7d81a18d07 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/ifAnd/Submission-01/Main.java @@ -0,0 +1,27 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + + if ((y < 100 && z > 2000) || y == 101) { + //never run + z = z + 100; + } else { + z = z - 100; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/ifElse/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/ifElse/Submission-01/Main.java new file mode 100644 index 0000000000..190d234107 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/ifElse/Submission-01/Main.java @@ -0,0 +1,30 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + + if (y < 100) { + //never run + z = z + 100; + } else if (z > 200) { + z = z - 100; + } else { + //never run + y = y - 100; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/ifElse2/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/ifElse2/Submission-01/Main.java new file mode 100644 index 0000000000..87add613ac --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/ifElse2/Submission-01/Main.java @@ -0,0 +1,33 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + + if (y < 100) { + //never run + z = z + 100; + } else if (z > 200) { + z = z - 100; + } else if (z > 500) { + //never run + z = z + 200; + } else { + //never run + y = y - 100; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/ifOr/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/ifOr/Submission-01/Main.java new file mode 100644 index 0000000000..317766fdf3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/ifOr/Submission-01/Main.java @@ -0,0 +1,27 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + + if (y < 100 || z > 2000) { + //never run + z = z + 100; + } else { + z = z - 100; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/inheritance/Main.java b/languages/java-cpg/src/test/resources/java/ai/inheritance/Main.java new file mode 100644 index 0000000000..0e1ba673bf --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/inheritance/Main.java @@ -0,0 +1,57 @@ +package edu.kit.informatik; + +import java.util.ArrayList; +import java.util.List; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + List animals = new ArrayList<>(); + animals.add(new Dog()); + animals.add(new Cat()); + + for (Animal animal : animals) { + animal.makeSound(); + } + + } +} + +public class Animal { + public Animal() { + // + } + + public void makeSound() { + System.out.println("Some generic animal sound"); + } +} + +public class Dog extends Animal { + public Dog() { + // + } + + @Override + public void makeSound() { + System.out.println("Bark"); + } +} + +public class Cat extends Animal { + public Cat() { + // + } + + @Override + public void makeSound() { + System.out.println("Meow"); + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/interval/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/interval/Submission-01/Main.java new file mode 100644 index 0000000000..c6a123746f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/interval/Submission-01/Main.java @@ -0,0 +1,161 @@ +package edu.kit.informatik; + +/** + * @author GitHub Copilot (Claude Sonnet 4.5) + */ +public final class Main { + + private static int result = 1; + private static int result2 = 1; + private static int result3 = 1; + private static int result4 = 1; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int x = Integer.parseInt(args[0]); + int y = Integer.parseInt(args[1]); + + // Step 1: Constrain x to [0, Integer.MAX_VALUE] + x = Math.abs(x); + + // Step 2: Constrain y to [10, 50] + y = Math.max(10, Math.min(y, 50)); + + // Step 3: Compute derived values with known intervals + int sum = x + y; // [10, Integer.MAX_VALUE + 50] + int product = y * 2; // [20, 100] + + // Dead code: sum can never be negative + if (sum < 0) { + System.out.print("impossible_negative_sum"); + result = -1; + } + + // Dead code: sum is always >= 10 + if (sum < 5) { + System.out.print("impossible_small_sum"); + result = -2; + } + + // Dead code: product is in [20, 100], cannot be > 150 + if (product > 150) { + System.out.print("impossible_large_product"); + result2 = -3; + } + + // Dead code: product is always >= 20 + if (product < 15) { + System.out.print("impossible_small_product"); + result2 = -4; + } + + // Step 4: Nested constraints with multiple branches + int bounded = Math.max(25, Math.min(x, 75)); // [25, 75] + int scaled = bounded * 3; // [75, 225] + + if (scaled > 300) { + // DEAD: scaled <= 225 + System.out.print("unreachable_scaled_high"); + result3 = -5; + } else if (scaled < 50) { + // DEAD: scaled >= 75 + System.out.print("unreachable_scaled_low"); + result3 = -6; + } else { + // Always taken: scaled in [75, 225] + System.out.print("always_scaled_middle"); + result3 = scaled; + } + + // Step 5: Complex arithmetic with overflow considerations + int clamped = Math.max(0, Math.min(x, 1000)); // [0, 1000] + int doubled = clamped + clamped; // [0, 2000] + int offset = doubled + 500; // [500, 2500] + + if (offset < 400) { + // DEAD: offset >= 500 + System.out.print("impossible_offset_low"); + result4 = -7; + } + + if (offset > 3000) { + // DEAD: offset <= 2500 + System.out.print("impossible_offset_high"); + result4 = -8; + } + + // Step 6: Chained conditionals creating dead branches + int rangeA = Math.max(100, x); // [100, Integer.MAX_VALUE] + int rangeB = Math.min(rangeA, 200); // [100, 200] + + if (rangeB < 90) { + // DEAD: rangeB >= 100 + System.out.print("impossible_rangeB_below_90"); + result = -9; + } else if (rangeB > 250) { + // DEAD: rangeB <= 200 + System.out.print("impossible_rangeB_above_250"); + result = -10; + } else if (rangeB >= 100 && rangeB <= 200) { + // Always taken + System.out.print("always_rangeB_in_range"); + result = rangeB; + } else { + // DEAD: all cases covered above + System.out.print("logically_impossible"); + result = -11; + } + + // Step 7: Multiple dependent intervals + int base = Math.max(50, Math.min(y, 100)); // [50, 50] + int increment = base + 30; // [80, 80] + int multiplied = increment * 2; // [160, 160] + + if (multiplied < 150) { + // DEAD: multiplied >= 160 + System.out.print("impossible_multiplied_low"); + result2 = -12; + } + + if (multiplied > 300) { + // DEAD: multiplied <= 260 + System.out.print("impossible_multiplied_high"); + result2 = -13; + } + + // Step 8: Dead code after proven inequality + int positive = Math.abs(x) + 1; // [1, Integer.MAX_VALUE + 1] + + if (positive <= 0) { + // DEAD: positive is always >= 1 + System.out.print("impossible_non_positive"); + result3 = -14; + } + + // Step 9: Dead branches with division constraints + int divisor = Math.max(5, Math.min(y, 10)); // [10, 10] | y is [10, 50] + int quotient = 100 / divisor; // [10, 10] + + if (quotient > 25) { + // DEAD: quotient <= 20 + System.out.print("impossible_quotient_high"); + result4 = -15; + } + + if (quotient < 8) { + // DEAD: quotient >= 10 + System.out.print("impossible_quotient_low"); + result4 = -16; + } + + // Final result computation + result = result + quotient; //[110, 210] + result2 = result2 + multiplied; + result3 = result3 + scaled; + result4 = result4 + offset; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/intervalDouble/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/intervalDouble/Submission-01/Main.java new file mode 100644 index 0000000000..2ecc592643 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/intervalDouble/Submission-01/Main.java @@ -0,0 +1,161 @@ +package edu.kit.informatik; + +/** + * @author GitHub Copilot (Claude Sonnet 4.5) + */ +public final class Main { + + private static double result = 0.5f; + private static double result2 = 0.5f; + private static double result3 = 0.5f; + private static double result4 = 0.5f; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + double x = Double.parseDouble(args[0]); + double y = Double.parseDouble(args[1]); + + // Step 1: Constrain x to [0.0, Float.MAX_VALUE] + x = Math.abs(x); + + // Step 2: Constrain y to [5.5, 25.5] + y = Math.max(5.5f, Math.min(y, 25.5f)); + + // Step 3: Compute derived values with known intervals + double sum = x + y; // [5.5, Float.MAX_VALUE + 25.5] + double product = y * 0.5f; // [2.75, 12.75] + + // Dead code: sum can never be negative + if (sum < 0.0f) { + System.out.print("impossible_negative_sum"); + result = -0.5f; + } + + // Dead code: sum is always >= 5.5 + if (sum < 2.5f) { + System.out.print("impossible_small_sum"); + result = -1.5f; + } + + // Dead code: product is in [2.75, 12.75], cannot be > 15.0 + if (product > 15.0f) { + System.out.print("impossible_large_product"); + result2 = -2.5f; + } + + // Dead code: product is always >= 2.75 + if (product < 1.5f) { + System.out.print("impossible_small_product"); + result2 = -3.5f; + } + + // Step 4: Nested constraints with multiple branches + double bounded = Math.max(12.5f, Math.min(x, 37.5f)); // [12.5, 37.5] + double scaled = bounded * 1.5f; // [18.75, 56.25] + + if (scaled > 60.0f) { + // DEAD: scaled <= 56.25 + System.out.print("unreachable_scaled_high"); + result3 = -4.5f; + } else if (scaled < 15.0f) { + // DEAD: scaled >= 18.75 + System.out.print("unreachable_scaled_low"); + result3 = -5.5f; + } else { + // Always taken: scaled in [18.75, 56.25] + System.out.print("always_scaled_middle"); + result3 = scaled; + } + + // Step 5: Complex arithmetic with decimal operations + double clamped = Math.max(0.0f, Math.min(x, 500.0f)); // [0.0, 500.0] + double doubled = clamped * 2.0f; // [0.0, 1000.0] + double offset = doubled + 9.8f; // [9.8, 1009.8] + + if (offset < 5.0f) { + // DEAD: offset >= 9.8 + System.out.print("impossible_offset_low"); + result4 = -6.5f; + } + + if (offset > 1500.0f) { + // DEAD: offset <= 1009.8 + System.out.print("impossible_offset_high"); + result4 = -7.5f; + } + + // Step 6: Chained conditionals creating dead branches + double rangeA = Math.max(50.5f, x); // [50.5, Float.MAX_VALUE] + double rangeB = Math.min(rangeA, 100.5f); // [50.5, 100.5] + + if (rangeB < 45.0f) { + // DEAD: rangeB >= 50.5 + System.out.print("impossible_rangeB_below_45"); + result = -8.5f; + } else if (rangeB > 125.0f) { + // DEAD: rangeB <= 100.5 + System.out.print("impossible_rangeB_above_125"); + result = -9.5f; + } else if (rangeB >= 50.5f && rangeB <= 100.5f) { + // Always taken + System.out.print("always_rangeB_in_range"); + result = rangeB; + } else { + // DEAD: all cases covered above + System.out.print("logically_impossible"); + result = -10.5f; + } + + // Step 7: Multiple dependent intervals with division + double base = Math.max(25.0f, Math.min(y, 50.0f)); // [25.0, 25.5] + double increment = base + 15.3f; // [40.3, 40.8] + double divided = increment / 2.0f; // [20.15, 20.4] + + if (divided < 15.0f) { + // DEAD: divided >= 20.4 + System.out.print("impossible_divided_low"); + result2 = -11.5f; + } + + if (divided > 30.0f) { + // DEAD: divided <= 20.4 + System.out.print("impossible_divided_high"); + result2 = -12.5f; + } + + // Step 8: Dead code after proven inequality + double positive = Math.abs(x) + 0.5f; // [0.5, Float.MAX_VALUE] + + if (positive <= 0.0f) { + // DEAD: positive is always >= 0.5 + System.out.print("impossible_non_positive"); + result3 = -13.5f; + } + + // Step 9: Dead branches with modulo and division constraints + double divisor = Math.max(2.5f, Math.min(y, 5.0f)); // [5.0, 5.0] | y is [5.5, 25.5] + double quotient = 50.0f / divisor; // [10.0, 10.0] + + if (quotient > 12.5f) { + // DEAD: quotient <= 9.09 + System.out.print("impossible_quotient_high"); + result4 = -14.5f; + } + + if (quotient < 4.0f) { + // DEAD: quotient >= 9.09 + System.out.print("impossible_quotient_low"); + result4 = -15.5f; + } + + // Final result computation + result = result + quotient; // [10.0, 10.0] + [50.5, 100.5] + result2 = result2 + divided; // [20.65, 20.9] + result3 = result3 + scaled; // [37,5, 112,5] + result4 = result4 + offset; // [10.3, 1010.3] + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/intervalMulti/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/intervalMulti/Submission-01/Main.java new file mode 100644 index 0000000000..01e56520a9 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/intervalMulti/Submission-01/Main.java @@ -0,0 +1,48 @@ +package edu.kit.informatik; + +/** + * @author GitHub Copilot (Claude Sonnet 4.5) + */ +public final class Main { + + private static int result = 1; + private static int result2 = 1; + private static int result3 = 1; + private static int result4 = 1; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int x = Integer.parseInt(args[0]); + int y = Integer.parseInt(args[1]); + + // Step 1: Constrain x to [0, Integer.MAX_VALUE] + x = Math.abs(x); + + // Step 2: Constrain y to [10, 50] or [200, 300] + if (y >= 100) { + y = Math.max(200, Math.min(y, 300)); + } else { + y = Math.max(10, Math.min(y, 50)); + } + + // Step 3: Compute derived values with known intervals + int sum = x + y; //[10, Integer.MAX_VALUE] + int product = y * 2; //[20, 100] or [400, 600] + + // Step 4: Dead code detection + if (y > 3000) { + //Dead Code + product--; + } + + // Final result computation + result = x; + result2 = y; + result3 = result3 + sum; + result4 = product; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/loop/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/loop/Submission-01/Main.java new file mode 100644 index 0000000000..c71fc04593 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/loop/Submission-01/Main.java @@ -0,0 +1,28 @@ +package edu.kit.informatik; + +public final class Main { + + private static int result; + private static int result2; + private static int result3; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int x = Integer.parseInt(args[0]); + int z = 500; + x = Math.abs(x); + int y = 100; + + while (y < z) { + x++; + y++; + } + + result = z; + result2 = y; + result3 = x; + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/loopx2/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/loopx2/Submission-01/Main.java new file mode 100644 index 0000000000..161f5e0ca9 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/loopx2/Submission-01/Main.java @@ -0,0 +1,30 @@ +package edu.kit.informatik; + +public final class Main { + + private static int result; + private static int result2; + private static int result3; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int x = Integer.parseInt(args[0]); + int z = 500; + x = Math.abs(x); + int y = 100; + + while (y < z) { + for (int i = 1; i <= x; i++) { + y++; + } + x++; + } + + result = z; + result2 = y; + result3 = x; + } +} diff --git a/languages/java-cpg/src/test/resources/java/ai/map/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/map/Submission-01/Main.java new file mode 100644 index 0000000000..40cfdd51bc --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/map/Submission-01/Main.java @@ -0,0 +1,26 @@ +package edu.kit.informatik; + +import java.util.HashMap; +import java.util.Map; + +public final class Main { + + Map map2 = new HashMap<>(); + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + Map map = new HashMap<>(); + + map.put("a", 1); + map2.put("b", 2); + + result = map.get("a"); + result2 = map2.get("b"); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/nestedIf/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/nestedIf/Submission-01/Main.java new file mode 100644 index 0000000000..9a84c157cd --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/nestedIf/Submission-01/Main.java @@ -0,0 +1,33 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + + if (y < 100) { + //never run + z = z + 100; + } else { + y = y - 50; //y=50 + if (z > 200) { + z = z - 100; + } else { + //never run + z = z + 200; + } + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/nestedWhile/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/nestedWhile/Submission-01/Main.java new file mode 100644 index 0000000000..2a9625d4a2 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/nestedWhile/Submission-01/Main.java @@ -0,0 +1,28 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int x = 5; + int y = 1; + + while (true) { + + while (x < 10) { + x++; + } + + } + + result = x; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/Main.java new file mode 100644 index 0000000000..e0e385f288 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/Main.java @@ -0,0 +1,35 @@ +package edu.kit.informatik; + +import edu.kit.informatik.ui.StartUserInterface; + +/** + * Main Klasse eines "Queens Farming" Spieles das über die Kommandozeile gespielt werden kann. + * + * @author ujiqk + * @version 1.0 + */ +public final class Main { + + private static final String ERROR_ARGUMENTS_NOT_SUPPORTED = "Error: Commandline arguments not supported"; + + private Main() { + throw new IllegalStateException(); + } + + /** + * Startet das Spiel + * + * @param args Kommandozeilenparameter muss leer sein. + */ + public static void main(String[] args) { + + if (args.length != 0) { + System.err.println(ERROR_ARGUMENTS_NOT_SUPPORTED); + return; + } + + //Spiel starten + StartUserInterface ui1 = new StartUserInterface(); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/ui/InputState.java b/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/ui/InputState.java new file mode 100644 index 0000000000..b6076241ae --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/ui/InputState.java @@ -0,0 +1,27 @@ +package edu.kit.informatik.ui; + +/** Beschreibt die Zustände des Spielstarts + * @author ujiqk + * @version 1.0 + */ +public enum InputState { + /** Einlesen der Spieleranzahl + */ + PLAYER_COUNT, + /** Einlesen der Namen der Spieler + */ + PLAYER_NAMES, + /** einlesen des Startgoldes + */ + START_GOLD, + /** Einlesen des für den Gewinn notwendigen Goldes + */ + WIN_GOLD, + /** Einlesen des Seeds für das Mischeln + */ + SHUFFLE, + /** Alles erfolgreich eingelesen + */ + READY + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/ui/StartUserInterface.java b/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/ui/StartUserInterface.java new file mode 100644 index 0000000000..3386c51439 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/new/Submission-01/ui/StartUserInterface.java @@ -0,0 +1,77 @@ +package edu.kit.informatik.ui; + +import java.util.Scanner; + +/** + * Klasse die den Start eines "Queens Farming" Spiels auf der Kommandozeile implementiert. + * Alle zum Start wichtigen Parameter werden eingelesen und danach das Spiel gestartet. + * + * @author ujiqk + * @version 1.0 + */ +public class StartUserInterface { + private static final String PLAYER_QUESTION = "How many players?"; + private static final String NAME_QUESTION = "Enter the name of player "; + private static final String START_GOLD_QUESTION = "With how much gold should each player start?"; + private static final String WIN_GOLD_QUESTION = "With how much gold should a player win?"; + private static final String SEED_QUESTION = "Please enter the seed used to shuffle the tiles:"; + private static final String COLON = ":"; + private static final String PIXEL_ART = """ + _.-^-._ .--. \s + .-' _ '-. |__| \s + / |_| \\| | \s + / \\ | \s + /| _____ |\\ | \s + | |==|==| | | \s + |---|---|---|---|---| |--|--| | | \s + |---|---|---|---|---| |==|==| | | \s + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ^^^^^^^^^^^^^^^ QUEENS FARMING ^^^^^^^^^^^^^^^ + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"""; + private static final String ERROR_ILLEGAL_NUMBER_OF_PLAYERS = "Error: Illegal number of players"; + private static final String ERROR_ILLEGAL_PLAYER_NAME = "Error: Illegal player name"; + private static final String ERROR_ILLEGAL_GOLD_AMOUNT = "Error: Illegal amount of gold"; + private static final String ERROR_SEED = "Error: Illegal shuffle seed"; + + //Wichtige Sachen um das Spiel zu erzeugen + private InputState state; + private int playerCount; + private int startGold; + private int winGold; + private int seed; + private String[] playerNames; + + /** + * Konstruktor, liest Eingabe ein und übergibt sie gesammelt an das Spiel User Interface + * eingelesene Eingaben: Spieleranzahl, Namen, Startgold, Gold um zu gewinnen, Seed zum Mischeln + */ + public StartUserInterface() { + System.out.println(PIXEL_ART); + + //Status des Einlesens + state = InputState.PLAYER_COUNT; + playerCount = 0; + playerNames = null; + + System.out.println(PLAYER_QUESTION); + + Scanner inputScanner = new Scanner(System.in); + //Schleife um Eingaben einlesen + boolean quit = false; +// while (!quit) { + String input = inputScanner.nextLine(); +// //'quit' befehl befolgen (beendet auch, falls der Name eines Spielers "quit" ist) +// if (Pattern.matches("quit", input)) { +// break; +// //Hiernach darf nichts mehr kommen +// } +// quit = gameInitUi(input); +// } +// if (state == InputState.READY) { +// GameUI gameUI = new GameUI(new QueensFarming(playerNames, winGold, startGold, seed), inputScanner); +// } + inputScanner.close(); + //Ende des Programms + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/returnInIf/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/returnInIf/Submission-01/Main.java new file mode 100644 index 0000000000..3bbb6c6896 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/returnInIf/Submission-01/Main.java @@ -0,0 +1,30 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + private int result3; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + int x = Integer.parseInt(args[0]); + + result = helper(x); + result2 = helper(y); + result3 = z; + } + + private int helper(int x) { + if (x > 0) { + return x * 2; + } + return x / 2; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/returnInIf2/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/returnInIf2/Submission-01/Main.java new file mode 100644 index 0000000000..54c7fb53ab --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/returnInIf2/Submission-01/Main.java @@ -0,0 +1,31 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + private int result3; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + int x = Integer.parseInt(args[0]); + + result = helper(x); + result2 = helper(y); + result3 = z; + } + + private int helper(int x) { + if (x > 0) { + return x * 2; + } else { + return x / 2; + } + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/returnInIf2x/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/returnInIf2x/Submission-01/Main.java new file mode 100644 index 0000000000..50b40a1bb0 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/returnInIf2x/Submission-01/Main.java @@ -0,0 +1,34 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + private int result3; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 500; + int y = 100; + int x = Integer.parseInt(args[0]); + + result = helper(x); + result2 = helper(y); + result3 = z; + } + + private int helper(int x) { + if (x > 0) { + if (x > 90) { + return x + 600; + } + return x * 2; + } else { + return x / 2; + } + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/returnInWhile/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/returnInWhile/Submission-01/Main.java new file mode 100644 index 0000000000..6cc54b024f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/returnInWhile/Submission-01/Main.java @@ -0,0 +1,30 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + private int result3; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int z = 50; + int y = 100; + int x = Integer.parseInt(args[0]); + + result = helper(x); + result2 = helper(y); + result3 = helper(z); + } + + private int helper(int x) { + while (x > 50) { + return x * 2; + } + return x / 2; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/set/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/set/Submission-01/Main.java new file mode 100644 index 0000000000..b972f39e16 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/set/Submission-01/Main.java @@ -0,0 +1,37 @@ +package edu.kit.informatik; + +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +public final class Main { + + private Set set2 = new TreeSet<>(); + private int result; + private boolean result2; + private int result3; + private String result4; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + Set set = new HashSet<>(); + + set.add("a"); + set.add("a"); // duplicate not added + result = set.size(); + result2 = set.contains("a"); + + set2.add("b"); + set2.add("a"); + result3 = set2.size(); + + + Set sortedEntries = new TreeSet<>(Comparator.comparing((String s) -> s.length()).thenComparing(Comparator.naturalOrder())); + sortedEntries.add("apple"); + result4 = sortedEntries.first(); //"apple" + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/simple/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/simple/Submission-01/Main.java new file mode 100644 index 0000000000..dc209c7afa --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/simple/Submission-01/Main.java @@ -0,0 +1,41 @@ +package edu.kit.informatik; + +/** + * @author ujiqk + * @version 1.0 + */ +public final class Main { + + private static final String ERROR_ARGUMENTS_NOT_SUPPORTED = "Error: Commandline arguments not supported"; + private static int result; + private static int result2; + + private Main() { + throw new IllegalStateException(); + } + + /** + * @param args Kommandozeilenparameter mit Wert von x. + */ + public static void main(String[] args) { + + System.out.print("1"); + + int x = Integer.parseInt(args[0]); + int z = 500; + x = Math.abs(x); + int y = 100; + + if (x + y < 100) { + System.out.print("2"); + z = z + 100; + } else { + System.out.print("3"); + z = z - 100; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/simple/Submission-01/daikonRun.txt b/languages/java-cpg/src/test/resources/java/ai/simple/Submission-01/daikonRun.txt new file mode 100644 index 0000000000..f375ec9a15 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/simple/Submission-01/daikonRun.txt @@ -0,0 +1,12 @@ +javac -source 21 -target 21 Main.java +javac Main.java + +java -cp . Main "4" + +export DAIKONDIR=/home/alpaka/git/Daikon/daikon-5.8.22 + +java -cp .:$DAIKONDIR/daikon.jar daikon.DynComp Main "2" + +java -cp .:$DAIKONDIR/daikon.jar daikon.Chicory \ + --comparability-file=Main.decls-DynComp \ + --daikon Main "2" diff --git a/languages/java-cpg/src/test/resources/java/ai/simple2/Submission-02/Main.java b/languages/java-cpg/src/test/resources/java/ai/simple2/Submission-02/Main.java new file mode 100644 index 0000000000..dbce1822da --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/simple2/Submission-02/Main.java @@ -0,0 +1,47 @@ +package edu.kit.informatik; + +/** + * @author ujiqk + * @version 1.0 + */ +public final class Main { + + private static final String ERROR_ARGUMENTS_NOT_SUPPORTED = "Error: Commandline arguments not supported"; + private static final String ERROR_ARGUMENTS_NOT_SUPPORTED2; + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + /** + * @param args Kommandozeilenparameter mit Wert von x. + */ + public static void main(String[] args) { + + int x = Integer.parseInt(args[0]); + int y = 100; + result2 = compute(x, y); + + System.out.print("1"); + int y = 100; + int z = 500; + x = Math.abs(x); + + if (x + y < 100) { + //System.out.print("1"); + z = z + 100; + } else { + //System.out.print("2"); + z = z - 100; + } + + result = z; + } + + private int compute(int x, int y) { + return x + y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/simple3/Submission-03/Main.java b/languages/java-cpg/src/test/resources/java/ai/simple3/Submission-03/Main.java new file mode 100644 index 0000000000..f1d7b45b16 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/simple3/Submission-03/Main.java @@ -0,0 +1,34 @@ +package ai; + +import java.util.Arrays; + +/** + * @author GitHub Copilot (Claude Sonnet 4.5) + */ +public class Main { + + int result2; + private int result; + + public static void main(String[] args) { + double[][] w1 = { + {0.2, -0.1, 0.4}, + {-0.3, 0.8, 0.5} + }; + double[] b1 = {0.1, -0.2}; + double[] w2 = {0.7, -0.6}; + double b2 = 0.05; + + result = 1; + + SimpleNN1 model = new SimpleNN1(w1, b1, w2, b2); + double[] input = {1.0, 0.5, -0.2}; + double output = model.predict(input); + + System.out.println("Input: " + Arrays.toString(input)); + System.out.println("Prediction: " + output); + + result2 = 2; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/simple3/Submission-03/SimpleNN1.java b/languages/java-cpg/src/test/resources/java/ai/simple3/Submission-03/SimpleNN1.java new file mode 100644 index 0000000000..a271da8c55 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/simple3/Submission-03/SimpleNN1.java @@ -0,0 +1,44 @@ +package ai; + +/** + * @author GitHub Copilot (Claude Sonnet 4.5) + */ +public class SimpleNN1 { + + private final double[][] w1; + private final double[] b1; + private final double[] w2; + private final double b2; + + public SimpleNN1(double[][] w1, double[] b1, double[] w2, double b2) { + this.w1 = w1; + this.b1 = b1; + this.w2 = w2; + this.b2 = b2; + } + + private double relu(double x) { + //return x > 0 ? x : 0; + return x; + } + + public double predict(double[] input) { + if (input.length != w1[0].length) { + throw new IllegalArgumentException("Input size mismatch"); + } + double[] hidden = new double[w1.length]; + for (int i = 0; i < w1.length; i++) { + double sum = b1[i]; + for (int j = 0; j < w1[i].length; j++) { + sum += w1[i][j] * input[j]; + } + //hidden[i] = relu(sum); + } + double out = b2; + for (int i = 0; i < hidden.length; i++) { + out += w2[i] * hidden[i]; + } + return out; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/simpleMCEinClassField/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/simpleMCEinClassField/Submission-01/Main.java new file mode 100644 index 0000000000..cc314ca271 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/simpleMCEinClassField/Submission-01/Main.java @@ -0,0 +1,17 @@ +package edu.kit.informatik; + +public final class Main { + + private static int result; + private static int result2; + private int x = Integer.parseInt("42"); + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + x = x + 1; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/stream/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/stream/Submission-01/Main.java new file mode 100644 index 0000000000..9f3b1e6ec3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/stream/Submission-01/Main.java @@ -0,0 +1,31 @@ +package edu.kit.informatik; + +import java.util.ArrayList; +import java.util.List; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + int x = Integer.parseInt(args[0]); + int z = 500; + int y = 100; + + List values = new ArrayList<>(); + + values.add("Test"); + values.add("x: " + x); + + z = values.stream().map(String::length).max(Integer::compareTo).get(); + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/string/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/string/Submission-01/Main.java new file mode 100644 index 0000000000..b4ce565f0d --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/string/Submission-01/Main.java @@ -0,0 +1,28 @@ +package edu.kit.informatik; + +public final class Main { + + private String result; + private String result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + String name = "John Doe"; + String greeting = "Hello"; + + if ((name.length() > 5 && greeting.startsWith("H")) || name.equals("Jane")) { + result = greeting + ", " + name + "!"; + } else { + result = "Welcome, " + name; + } + + result2 = name.toUpperCase(); + + System.out.println(result); + System.out.println(result2); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/stringComplex/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/stringComplex/Submission-01/Main.java new file mode 100644 index 0000000000..e6ad4068e9 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/stringComplex/Submission-01/Main.java @@ -0,0 +1,128 @@ +import java.util.Locale; + +/** + * @author GitHub Copilot (GPT-5 mini) + */ +public final class Main { + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + try { + Options opts = parseArgs(args); + + boolean shout = opts.shout; + int repeat = opts.repeat; + String locale = opts.locale; + boolean formal = opts.formal; + + String greeting = "Hello"; + if (shout) { + greeting = greeting.toUpperCase(Locale.ROOT) + "!!!"; + } + + for (int i = 0; i < Math.max(1, repeat); i++) { + System.out.println(greeting); + } + + System.out.println("Uppercase name: "); + + } catch (Exception e) { + System.err.println("Usage examples:"); + System.err.println(" java Main --name=\"Jane Doe\" --title=Dr --locale=de --formal --shout --repeat=2"); + } + } + + /** + * Simple parser supporting: + * --key=value and flags like --shout (treated as true) + * first positional argument is treated as name if present + */ + private static Options parseArgs(String[] args) { + Options opts = new Options(); + for (String a : args) { + if (a == null || a.isBlank()) return; + if (a.startsWith("--")) { + int eq = a.indexOf('='); + if (eq > 0) { + String k = a.substring(2, eq); + String v = a.substring(eq + 1); + switch (k) { + case "name": + opts.name = v; + break; + case "title": + opts.title = v; + break; + case "repeat": + try { + opts.repeat = Integer.parseInt(v); + } catch (NumberFormatException ignored) { + opts.repeat = 1; + } + break; + case "locale": + opts.locale = v; + break; + case "formal": + opts.formal = Boolean.parseBoolean(v); + break; + case "shout": + opts.shout = Boolean.parseBoolean(v); + break; + default: + // unknown option: ignore + break; + } + } else { + String flag = a.substring(2); + switch (flag) { + case "shout": + opts.shout = true; + break; + case "formal": + opts.formal = true; + break; + default: + // unknown flag: ignore + break; + } + } + } else if (opts.name == null) { + opts.name = a; + } + } + if (opts.name == null || opts.name.isBlank()) { + opts.name = "John Doe"; + } + if (opts.locale == null || opts.locale.isBlank()) { + opts.locale = "en"; + } + opts.repeat = Math.max(1, opts.repeat); + return opts; + } + + /** + * Simple options holder instead of a map. + */ + private static final class Options { + String name; + String title; + boolean shout; + int repeat; + String locale; + boolean formal; + + Options() { + this.name = null; + this.title = null; + this.shout = false; + this.repeat = 1; + this.locale = "en"; + this.formal = false; + } + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/switch/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/switch/Submission-01/Main.java new file mode 100644 index 0000000000..dab1cc3732 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/switch/Submission-01/Main.java @@ -0,0 +1,30 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + + int x = Integer.parseInt(args[0]); + int z = 500; + int y = 100; + + switch (x + x < 100) { + case true: + z++; + break; + default: + z--; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/switch2/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/switch2/Submission-01/Main.java new file mode 100644 index 0000000000..6576beebc2 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/switch2/Submission-01/Main.java @@ -0,0 +1,34 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + + int x = Integer.parseInt(args[0]); + int z = 500; + int y = 100; + + switch (x + x) { + case 100 -> { + z++; + } + case 200 -> { + z += 2; + } + default -> { + z--; + } + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/try/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/try/Submission-01/Main.java new file mode 100644 index 0000000000..647b1a8ecb --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/try/Submission-01/Main.java @@ -0,0 +1,31 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + + int z = 500; + int y = 100; + + try { + z = z - 100; + if (z == 400) { + throw new Exception(); + } + y = y + 100; + } catch (Exception e) { + y++; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/try2/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/try2/Submission-01/Main.java new file mode 100644 index 0000000000..8e3109f4a9 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/try2/Submission-01/Main.java @@ -0,0 +1,36 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + + int z = 500; + int y = 100; + + try { + z = parsePositiveNumber("250"); + y = y + 100; + } catch (Exception e) { + y++; + } + + result = z; + result2 = y; + } + + private int parsePositiveNumber(String input) throws NumberFormatException, ParseException { + int number = Integer.parseInt(input); + if (number < 0) { + throw new ParseException(); + } + return number; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/try3/Submission-01/Main.java b/languages/java-cpg/src/test/resources/java/ai/try3/Submission-01/Main.java new file mode 100644 index 0000000000..9fe83ed1d5 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/try3/Submission-01/Main.java @@ -0,0 +1,31 @@ +package edu.kit.informatik; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + + int z = 500; + int y = 100; + + try { + z = z - 100; + if (z != 400) { + throw new Exception(); + } + y = y + 100; + } catch (Exception e) { + y++; + } + + result = z; + result2 = y; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/ai/whileAssign/Main.java b/languages/java-cpg/src/test/resources/java/ai/whileAssign/Main.java new file mode 100644 index 0000000000..2a4f8e03fc --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/ai/whileAssign/Main.java @@ -0,0 +1,27 @@ +package edu.kit.informatik; + +import java.util.Scanner; + +public final class Main { + + private int result; + private int result2; + + private Main() { + throw new IllegalStateException(); + } + + public static void main(String[] args) { + Scanner s = new Scanner(System.in); + int ponto = 0; + int x = 1; + + while ((ponto = s.nextInt()) != 0) { + x++; + } + + result = x; + result2 = ponto; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/.gitignore b/languages/java-cpg/src/test/resources/java/aiGenerated/.gitignore new file mode 100644 index 0000000000..da0fb023d3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/.gitignore @@ -0,0 +1,4 @@ +test/ +out/ +aiGenerated.iml +.idea/ diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/annotation.md b/languages/java-cpg/src/test/resources/java/aiGenerated/annotation.md new file mode 100644 index 0000000000..a2fbee777e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/annotation.md @@ -0,0 +1,5 @@ +Dead Code is annotated with: +//DeadCodeStart +//DeadCodeEnd + +Plagiarized pairs are described in the EvaluationEngineTest.java diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project1.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project1.java new file mode 100644 index 0000000000..195a0fbabd --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project1.java @@ -0,0 +1,43 @@ +package com.example.calculator; + +public class Main { + public static void main(String[] args) { + Calculator calc = new Calculator(); + + System.out.println("Addition: " + calc.add(10, 5)); + System.out.println("Subtraction: " + calc.subtract(10, 5)); + System.out.println("Multiplication: " + calc.multiply(10, 5)); + System.out.println("Division: " + calc.divide(10, 0)); + } +} + +class Calculator { + public int add(int a, int b) { + return a + b; + } + + public int subtract(int a, int b) { + return a - b; + } + + public int multiply(int a, int b) { + return a * b; + } + + public double divide(double a, double b) { + if (b == 0) { + throw new ArithmeticException("Cannot divide by zero"); + } + return a / b; + } + + //DeadCodeStart + public int modulo(int a, int b) { + return a % b; + } + + public double power(double base, double exponent) { + return Math.pow(base, exponent); + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project10.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project10.java new file mode 100644 index 0000000000..7fd7321284 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project10.java @@ -0,0 +1,805 @@ +// Main.java + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class Main { + public static void main(String[] args) { + University university = new University("Tech State University", "456 University Ave"); + + // PLAGIARIZED: Same structure as Hospital departments + Department computerScience = new Department("Computer Science", "CS001", 50); + Department mathematics = new Department("Mathematics", "MATH001", 40); + Department physics = new Department("Physics", "PHYS001", 35); + + university.addDepartment(computerScience); + university.addDepartment(mathematics); + university.addDepartment(physics); + + // PLAGIARIZED: Similar to Hospital doctors + Professor prof1 = new Professor("Dr. Robert Anderson", "PROF001", "Computer Science", 12); + Professor prof2 = new Professor("Dr. Lisa Martinez", "PROF002", "Mathematics", 8); + Professor prof3 = new Professor("Dr. David Wilson", "PROF003", "Physics", 15); + + university.registerProfessor(prof1, computerScience); + university.registerProfessor(prof2, mathematics); + university.registerProfessor(prof3, physics); + + // PLAGIARIZED: Similar to Hospital patients + Student student1 = new Student("Alex Thompson", "S001", 20, "Computer Science"); + Student student2 = new Student("Emma Davis", "S002", 21, "Mathematics"); + Student student3 = new Student("Ryan Miller", "S003", 19, "Physics"); + + university.registerStudent(student1); + university.registerStudent(student2); + university.registerStudent(student3); + + // PLAGIARIZED: Similar appointment scheduling pattern + System.out.println("=== University Management System ==="); + System.out.println("University: " + university.getName()); + System.out.println(); + + Enrollment enr1 = university.scheduleEnrollment(student1, prof1, "Data Structures"); + Enrollment enr2 = university.scheduleEnrollment(student2, prof2, "Linear Algebra"); + Enrollment enr3 = university.scheduleEnrollment(student3, prof3, "Quantum Mechanics"); + + System.out.println("\n--- Scheduled Enrollments ---"); + university.displayEnrollments(); + + System.out.println("\n--- Processing Enrollments ---"); + university.processEnrollment(enr1, "Student performing well. Midterm grade: A-"); + university.processEnrollment(enr2, "Needs additional tutoring. Current grade: B+"); + + System.out.println("\n--- Professor Workload ---"); + university.displayProfessorWorkload(); + + System.out.println("\n--- Student Academic History ---"); + student1.displayAcademicHistory(); + + //DeadCodeStart + // PLAGIARIZED: Same pattern as Hospital billing + TuitionSystem tuition = new TuitionSystem(university); + double cost = tuition.calculateCourseCost(enr1); + Receipt receipt = tuition.generateReceipt(student1, enr1); + //DeadCodeEnd + + //DeadCodeStart + // PLAGIARIZED: Same pattern as Hospital analytics + UniversityAnalytics analytics = new UniversityAnalytics(university); + analytics.generateEnrollmentReport(); + double avgStudentAge = analytics.calculateAverageStudentAge(); + //DeadCodeEnd + } +} + +// Department.java - HEAVILY PLAGIARIZED from Hospital's Department + +class Department { + private String name; + private String deptId; + private int capacity; + private List professors; // Changed from doctors to professors + private int currentStudents; // Changed from currentPatients + + // PLAGIARIZED: Exact same constructor pattern + public Department(String name, String deptId, int capacity) { + this.name = name; + this.deptId = deptId; + this.capacity = capacity; + this.professors = new ArrayList<>(); + this.currentStudents = 0; + } + + public String getName() { + return name; + } + + public String getDeptId() { + return deptId; + } + + // PLAGIARIZED: Renamed from addDoctor + public void addProfessor(Professor professor) { + professors.add(professor); + } + + //DeadCodeStart + public List getProfessors() { + return professors; + } + //DeadCodeEnd + + // PLAGIARIZED: Exact same logic as Hospital + public boolean hasCapacity() { + return currentStudents < capacity; + } + + // PLAGIARIZED: Same increment pattern + public void incrementStudentCount() { + currentStudents++; + } + + // PLAGIARIZED: Same decrement pattern + public void decrementStudentCount() { + if (currentStudents > 0) currentStudents--; + } + + //DeadCodeStart + public int getCapacity() { + return capacity; + } + + // PLAGIARIZED from Hospital.Department.setCapacity() - in dead code + public void setCapacity(int newCapacity) { + // Never called + this.capacity = newCapacity; + System.out.println("Capacity updated to: " + newCapacity); + } + + public int getCurrentStudents() { + return currentStudents; + } + + // PLAGIARIZED: Exact same calculation as getUtilizationRate() + public double getEnrollmentRate() { + // Unused calculation method + if (capacity == 0) return 0.0; + return (double) currentStudents / capacity * 100; + } + + // PLAGIARIZED from getAvailableDoctors() + public List getAvailableProfessors() { + // Complex dead code for finding available professors + List available = new ArrayList<>(); + for (Professor prof : professors) { + if (prof.getCourseCount() < 10) { + available.add(prof); + } + } + return available; + } + + // PLAGIARIZED from reorganizeDoctors() + private void reorganizeProfessors() { + // Incomplete optimization logic + System.out.println("Reorganizing department: " + name); + } + //DeadCodeEnd + + // PLAGIARIZED: Same toString format as Hospital + @Override + public String toString() { + return String.format("Department: %s [%s] - %d professors, %d/%d students", + name, deptId, professors.size(), currentStudents, capacity); + } +} + +// Professor.java - PLAGIARIZED from Doctor.java + +class Professor { + private String name; + private String professorId; + private String specialization; + private int yearsOfExperience; + private List enrollments; // Changed from appointments + private int courseCount; // Changed from appointmentCount + private Department assignedDepartment; + + // PLAGIARIZED: Exact same constructor as Doctor + public Professor(String name, String professorId, String specialization, int yearsOfExperience) { + this.name = name; + this.professorId = professorId; + this.specialization = specialization; + this.yearsOfExperience = yearsOfExperience; + this.enrollments = new ArrayList<>(); + this.courseCount = 0; + } + + public String getName() { + return name; + } + + public String getProfessorId() { + return professorId; + } + + //DeadCodeStart + public String getSpecialization() { + return specialization; + } + //DeadCodeEnd + + // PLAGIARIZED: Same as Doctor.setDepartment() + public void setDepartment(Department dept) { + this.assignedDepartment = dept; + } + + // PLAGIARIZED from Doctor.addAppointment() + public void addEnrollment(Enrollment enrollment) { + enrollments.add(enrollment); + courseCount++; + } + + public int getCourseCount() { + return courseCount; + } + + //DeadCodeStart + public List getEnrollments() { + return enrollments; + } + + public int getYearsOfExperience() { + return yearsOfExperience; + } + + public Department getAssignedDepartment() { + return assignedDepartment; + } + + // PLAGIARIZED from Doctor.updateSpecialization() - hidden in dead code + public void updateSpecialization(String newSpec) { + // Never used + this.specialization = newSpec; + System.out.println("Specialization updated for " + name); + } + + // PLAGIARIZED from Doctor.isSeniorDoctor() + public boolean isTenuredProfessor() { + // Unused classification method - same logic + return yearsOfExperience >= 10; + } + + // PLAGIARIZED from Doctor.calculateSalary() + public double calculateSalary() { + // Dead code for salary calculation - exact same formula + double baseSalary = 100000; + double experienceBonus = yearsOfExperience * 5000; + return baseSalary + experienceBonus; + } + + // PLAGIARIZED from Doctor.getPatientList() + public List getStudentList() { + // Dead code that extracts students from enrollments + List students = new ArrayList<>(); + for (Enrollment enr : enrollments) { + students.add(enr.getStudent()); + } + return students; + } + + // PLAGIARIZED from Doctor.sendReminders() + private void sendNotifications() { + // Incomplete notification system + System.out.println("Sending course notifications..."); + } + + public void assignGrade(Enrollment enrollment, String grade) { + // Additional dead code + System.out.println("Assigning grade " + grade + " to " + enrollment.getStudent().getName()); + } + //DeadCodeEnd + + // PLAGIARIZED: Same toString format as Doctor + @Override + public String toString() { + return String.format("%s (%s) - %s specialist, %d courses", + name, professorId, specialization, courseCount); + } +} + +// Student.java - PLAGIARIZED from Patient.java + +class Student { + private String name; + private String studentId; + private int age; + private String major; // Changed from bloodType + private List academicHistory; // Changed from medicalHistory + private List enrollments; // Changed from appointments + private String currentStatus; + + // PLAGIARIZED: Exact same constructor pattern as Patient + public Student(String name, String studentId, int age, String major) { + this.name = name; + this.studentId = studentId; + this.age = age; + this.major = major; + this.academicHistory = new ArrayList<>(); + this.enrollments = new ArrayList<>(); + this.currentStatus = "Enrolled"; + } + + public String getName() { + return name; + } + + public String getStudentId() { + return studentId; + } + + public int getAge() { + return age; + } + + // PLAGIARIZED from Patient.addMedicalRecord() + public void addAcademicRecord(String record) { + academicHistory.add(record); + } + + // PLAGIARIZED from Patient.addAppointment() + public void addEnrollment(Enrollment enrollment) { + enrollments.add(enrollment); + } + + public String getStatus() { + return currentStatus; + } + + //DeadCodeStart + public void setStatus(String status) { + this.currentStatus = status; + } + //DeadCodeEnd + + // PLAGIARIZED: Exact same display pattern as Patient.displayMedicalHistory() + public void displayAcademicHistory() { + System.out.println("Academic history for " + name + ":"); + if (academicHistory.isEmpty()) { + System.out.println(" No records yet"); + } else { + for (String record : academicHistory) { + System.out.println(" - " + record); + } + } + } + + //DeadCodeStart + public String getMajor() { + return major; + } + + public List getEnrollments() { + return enrollments; + } + + // PLAGIARIZED from Patient.updateAge() + public void updateAge(int newAge) { + // Never called + this.age = newAge; + } + + // PLAGIARIZED from Patient.updateBloodType() + public void updateMajor(String newMajor) { + // Dead code for updating major + this.major = newMajor; + System.out.println("Major updated for " + name); + } + + // PLAGIARIZED from Patient.isHighRiskPatient() + public boolean isAtRiskStudent() { + // Unused risk assessment - same logic pattern + return age > 65 || academicHistory.size() > 5; + } + + // PLAGIARIZED from Patient.getAllergies() + public List getExtracurriculars() { + // Returns empty list - never properly implemented + return new ArrayList<>(); + } + + // PLAGIARIZED from Patient.getInsuranceInfo() + public String getScholarshipInfo() { + // Dead code returning placeholder + return "Scholarship ID: SCH-" + studentId; + } + + // PLAGIARIZED from Patient.notifyEmergencyContact() + private void notifyAdvisor() { + // Incomplete notification system + System.out.println("Notifying academic advisor for " + name); + } + + public double calculateGPA() { + // Additional dead code + return 3.5; // Hardcoded placeholder + } + //DeadCodeEnd + + // PLAGIARIZED: Same toString format as Patient + @Override + public String toString() { + return String.format("Student: %s [%s] - Age: %d, Major: %s, Status: %s", + name, studentId, age, major, currentStatus); + } +} + +// Enrollment.java - PLAGIARIZED from Appointment.java + +class Enrollment { + private static int nextId = 1; + private String enrollmentId; + private Student student; // Changed from patient + private Professor professor; // Changed from doctor + private LocalDateTime enrollmentTime; // Changed from scheduledTime + private String courseName; // Changed from reason + private String status; + private String feedback; // Changed from diagnosis + + // PLAGIARIZED: Exact same constructor pattern as Appointment + public Enrollment(Student student, Professor professor, String courseName) { + this.enrollmentId = "ENR" + String.format("%04d", nextId++); + this.student = student; + this.professor = professor; + this.enrollmentTime = LocalDateTime.now(); + this.courseName = courseName; + this.status = "Active"; + this.feedback = ""; + } + + public String getEnrollmentId() { + return enrollmentId; + } + + public Student getStudent() { + return student; + } + + public Professor getProfessor() { + return professor; + } + + public String getStatus() { + return status; + } + //DeadCodeEnd + + //DeadCodeStart + public void setStatus(String status) { + this.status = status; + } + + //DeadCodeStart + public String getFeedback() { + return feedback; + } + + // PLAGIARIZED from Appointment.setDiagnosis() + public void setFeedback(String feedback) { + this.feedback = feedback; + this.status = "Graded"; + } + + public LocalDateTime getEnrollmentTime() { + return enrollmentTime; + } + + public String getCourseName() { + return courseName; + } + + // PLAGIARIZED from Appointment.reschedule() + public void changeSection(LocalDateTime newTime) { + // Never called rescheduling method + this.enrollmentTime = newTime; + System.out.println("Section changed to: " + + newTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + } + + // PLAGIARIZED from Appointment.cancel() + public void drop() { + // Dead code for dropping course + this.status = "Dropped"; + System.out.println("Enrollment " + enrollmentId + " dropped"); + } + + // PLAGIARIZED from Appointment.getDurationMinutes() + public int getCreditHours() { + // Returns fixed value - never used + return 3; + } + + // PLAGIARIZED from Appointment.isUrgent() + private boolean isHonorsCourse() { + // Unused priority check + return courseName.toLowerCase().contains("honors"); + } + + public String calculateLetterGrade() { + // Additional dead code + return "A"; // Placeholder + } + //DeadCodeEnd + + // PLAGIARIZED: Same toString format as Appointment + @Override + public String toString() { + return String.format("[%s] %s with %s - %s (%s)", + enrollmentId, student.getName(), professor.getName(), courseName, status); + } +} + +// University.java - HEAVILY PLAGIARIZED from Hospital.java + +class University { + private String name; + private String address; + private List departments; + private List professors; // Changed from doctors + private List students; // Changed from patients + private List enrollments; // Changed from appointments + + // PLAGIARIZED: Exact same constructor as Hospital + public University(String name, String address) { + this.name = name; + this.address = address; + this.departments = new ArrayList<>(); + this.professors = new ArrayList<>(); + this.students = new ArrayList<>(); + this.enrollments = new ArrayList<>(); + } + + public String getName() { + return name; + } + + // PLAGIARIZED: Exact copy of Hospital.addDepartment() + public void addDepartment(Department dept) { + departments.add(dept); + System.out.println("Added department: " + dept.getName()); + } + + // PLAGIARIZED from Hospital.registerDoctor() + public void registerProfessor(Professor professor, Department dept) { + professors.add(professor); + professor.setDepartment(dept); + dept.addProfessor(professor); + System.out.println("Registered professor: " + professor.getName() + " to " + dept.getName()); + } + + // PLAGIARIZED from Hospital.registerPatient() + public void registerStudent(Student student) { + students.add(student); + System.out.println("Registered student: " + student.getName()); + } + + // PLAGIARIZED: Same logic as Hospital.scheduleAppointment() + public Enrollment scheduleEnrollment(Student student, Professor professor, String courseName) { + Enrollment enr = new Enrollment(student, professor, courseName); + enrollments.add(enr); + student.addEnrollment(enr); + professor.addEnrollment(enr); + System.out.println("Scheduled: " + enr); + return enr; + } + + // PLAGIARIZED from Hospital.processAppointment() + public void processEnrollment(Enrollment enr, String feedback) { + enr.setFeedback(feedback); + enr.getStudent().addAcademicRecord(feedback); + System.out.println("Processed enrollment " + enr.getEnrollmentId() + + " - Status: " + enr.getStatus()); + } + + // PLAGIARIZED from Hospital.displayAppointments() + public void displayEnrollments() { + for (Enrollment enr : enrollments) { + System.out.println(" " + enr); + } + } + + // PLAGIARIZED from Hospital.displayDoctorWorkload() + public void displayProfessorWorkload() { + for (Professor prof : professors) { + System.out.println(" " + prof); + } + } + + public List getStudents() { + return students; + } + + public List getProfessors() { + return professors; + } + + public List getDepartments() { + return departments; + } + + //DeadCodeStart + public String getAddress() { + return address; + } + + public List getEnrollments() { + return enrollments; + } + + // PLAGIARIZED from Hospital.findDepartmentById() - in dead code + public Department findDepartmentById(String deptId) { + // Never used search method + for (Department dept : departments) { + if (dept.getDeptId().equals(deptId)) { + return dept; + } + } + return null; + } + + // PLAGIARIZED from Hospital.findDoctorById() + public Professor findProfessorById(String professorId) { + // Dead code for finding professors + for (Professor prof : professors) { + if (prof.getProfessorId().equals(professorId)) { + return prof; + } + } + return null; + } + + // PLAGIARIZED from Hospital.findPatientById() + public Student findStudentById(String studentId) { + // Dead code for finding students + for (Student s : students) { + if (s.getStudentId().equals(studentId)) { + return s; + } + } + return null; + } + + // PLAGIARIZED from Hospital.getAppointmentsByStatus() + public List getEnrollmentsByStatus(String status) { + // Complex filtering that's never used + List filtered = new ArrayList<>(); + for (Enrollment enr : enrollments) { + if (enr.getStatus().equals(status)) { + filtered.add(enr); + } + } + return filtered; + } + + // PLAGIARIZED from Hospital.generateDailyReport() + private void generateSemesterReport() { + // Incomplete reporting logic + System.out.println("Generating semester report for " + name); + System.out.println("Total enrollments: " + enrollments.size()); + System.out.println("Total students: " + students.size()); + } + + // PLAGIARIZED from Hospital.transferPatient() + public void transferStudent(Student student, Department from, Department to) { + // Never called transfer logic + if (to.hasCapacity()) { + from.decrementStudentCount(); + to.incrementStudentCount(); + System.out.println("Transferred " + student.getName() + + " from " + from.getName() + " to " + to.getName()); + } + } + + public void accreditDepartment(Department dept) { + // Additional dead code + System.out.println("Department " + dept.getName() + " has been accredited"); + } + //DeadCodeEnd +} + +// TuitionSystem.java - PLAGIARIZED from BillingSystem.java +//DeadCodeStart +class TuitionSystem { + private static final double BASE_COURSE_FEE = 150.0; // Same value as BASE_CONSULTATION_FEE + private University university; + + // PLAGIARIZED: Exact same constructor pattern as BillingSystem + public TuitionSystem(University university) { + this.university = university; + } + + // PLAGIARIZED from BillingSystem.calculateAppointmentCost() + public double calculateCourseCost(Enrollment enr) { + // Entire class is dead code - exact same logic + double cost = BASE_COURSE_FEE; + Professor prof = enr.getProfessor(); + if (prof.getYearsOfExperience() > 10) { + cost *= 1.5; + } + return cost; + } + + // PLAGIARIZED from BillingSystem.generateInvoice() + public Receipt generateReceipt(Student student, Enrollment enr) { + double amount = calculateCourseCost(enr); + return new Receipt(student, enr, amount); + } + + // PLAGIARIZED from BillingSystem.processPayment() + public void processPayment(Receipt receipt) { + System.out.println("Processing payment for receipt: " + receipt.getReceiptId()); + } +} + +// PLAGIARIZED: Entire class copied from Invoice +class Receipt { + private static int nextId = 1; + private String receiptId; + private Student student; // Changed from patient + private Enrollment enrollment; // Changed from appointment + private double amount; + private boolean paid; + + public Receipt(Student student, Enrollment enrollment, double amount) { + this.receiptId = "REC" + String.format("%05d", nextId++); // Changed prefix + this.student = student; + this.enrollment = enrollment; + this.amount = amount; + this.paid = false; + } + + public String getReceiptId() { + return receiptId; + } + + public void markAsPaid() { + this.paid = true; + } +} +//DeadCodeEnd + +// UniversityAnalytics.java - PLAGIARIZED from HospitalAnalytics.java +//DeadCodeStart +class UniversityAnalytics { + private University university; + + // PLAGIARIZED: Exact same constructor + public UniversityAnalytics(University university) { + this.university = university; + } + + // PLAGIARIZED from HospitalAnalytics.generateOccupancyReport() + public void generateEnrollmentReport() { + // Never actually used - dead analytics code + System.out.println("=== Enrollment Report ==="); + for (Department dept : university.getDepartments()) { + System.out.println(dept.getName() + ": " + + dept.getCurrentStudents() + "/" + dept.getCapacity()); + } + } + + // PLAGIARIZED: Exact same calculation logic as calculateAveragePatientAge() + public double calculateAverageStudentAge() { + // Dead calculation method + List students = university.getStudents(); + if (students.isEmpty()) return 0.0; + + int sum = 0; + for (Student s : students) { + sum += s.getAge(); + } + return (double) sum / students.size(); + } + + // PLAGIARIZED from HospitalAnalytics.getTopDoctors() + public List getTopProfessors(int count) { + // Complex sorting logic that's never used + List sorted = new ArrayList<>(university.getProfessors()); + // Incomplete sorting implementation + return sorted.subList(0, Math.min(count, sorted.size())); + } + + // PLAGIARIZED from HospitalAnalytics.analyzeTrends() + private void analyzeAcademicTrends() { + // Incomplete trend analysis + System.out.println("Analyzing academic trends..."); + } + + public void calculateRetentionRate() { + // Additional dead code + System.out.println("Calculating student retention rate..."); + } +} +//DeadCodeEnd diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project2.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project2.java new file mode 100644 index 0000000000..195c0c2b8d --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project2.java @@ -0,0 +1,44 @@ +package com.example.students; + +public class Main { + public static void main(String[] args) { + StudentGrades grades = new StudentGrades(); + + // This looks different but uses plagiarized calculator logic + System.out.println("Total score: " + grades.calculateTotal(85, 92)); + System.out.println("Score difference: " + grades.calculateDifference(92, 85)); + + grades.displayStudent("John Doe", 85); + } +} + +class StudentGrades { + // Plagiarized from Calculator.add() + public int calculateTotal(int score1, int score2) { + return score1 + score2; + } + + // Plagiarized from Calculator.subtract() + public int calculateDifference(int score1, int score2) { + return score1 - score2; + } + + //DeadCodeStart + // Hidden plagiarism - multiply method copied but never used + public int calculateProduct(int score1, int score2) { + return score1 * score2; + } + + // More plagiarized code hidden in dead code section + public double calculateRatio(double score1, double score2) { + if (score2 == 0) { + throw new ArithmeticException("Cannot divide by zero"); + } + return score1 / score2; + } + //DeadCodeEnd + + public void displayStudent(String name, int grade) { + System.out.println("Student: " + name + ", Grade: " + grade); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project3.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project3.java new file mode 100644 index 0000000000..a0d6493819 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project3.java @@ -0,0 +1,65 @@ +package com.example.banking; + +public class Main { + public static void main(String[] args) { + BankAccount account = new BankAccount("ACC001", 1000.0); + + account.deposit(500.0); + account.withdraw(200.0); + account.displayBalance(); + } +} + +class BankAccount { + private String accountId; + private double balance; + + public BankAccount(String accountId, double initialBalance) { + this.accountId = accountId; + this.balance = initialBalance; + } + + public void deposit(double amount) { + balance += amount; + System.out.println("Deposited: $" + amount); + } + + public void withdraw(double amount) { + if (balance >= amount) { + balance -= amount; + System.out.println("Withdrawn: $" + amount); + } else { + System.out.println("Insufficient funds"); + } + } + + public void displayBalance() { + System.out.println("Current balance: $" + balance); + } + + //DeadCodeStart + public double calculateInterest(double rate) { + double interest = balance * rate; + balance += interest; + return interest; + } + + public void applyPenalty(double amount) { + balance -= amount; + System.out.println("Penalty applied: $" + amount); + } + + public boolean transferTo(BankAccount target, double amount) { + if (balance >= amount) { + this.balance -= amount; + target.balance += amount; + return true; + } + return false; + } + + public String getAccountStatement() { + return "Account: " + accountId + ", Balance: $" + balance; + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project4.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project4.java new file mode 100644 index 0000000000..dbc7b15f88 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project4.java @@ -0,0 +1,58 @@ +package com.example.shopping; + +public class Main { + public static void main(String[] args) { + ShoppingCart cart = new ShoppingCart(); + + cart.addItem("Laptop", 999.99, 1); + cart.addItem("Mouse", 29.99, 2); + cart.displayTotal(); + } +} + +class ShoppingCart { + private double total = 0.0; + + public void addItem(String name, double price, int quantity) { + // Plagiarized from Calculator (Project 1) multiply method + double itemTotal = price * quantity; + + // Plagiarized from Calculator add method + total = total + itemTotal; + + System.out.println("Added: " + name + " x" + quantity + " = $" + itemTotal); + } + + public void displayTotal() { + System.out.println("Cart Total: $" + total); + } + + //DeadCodeStart + // Hidden plagiarism from BankAccount (Project 3) + public void applyDiscount(double percentage) { + double discount = total * percentage; + total -= discount; + System.out.println("Discount applied: $" + discount); + } + + // More plagiarized calculator logic hidden as dead code + public double calculateTax(double taxRate) { + return total * taxRate; + } + + public void removeItem(String name, double price, int quantity) { + double itemTotal = price * quantity; + total = total - itemTotal; + System.out.println("Removed: " + name); + } + + public boolean validatePayment(double payment) { + // Plagiarized comparison logic from BankAccount.withdraw + if (payment >= total) { + return true; + } else { + return false; + } + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project5.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project5.java new file mode 100644 index 0000000000..5bfab525a0 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project5.java @@ -0,0 +1,112 @@ +import java.util.ArrayList; +import java.util.List; + +// Main.java +public class Main { + public static void main(String[] args) { + StudentManager manager = new StudentManager(); + + Student s1 = new Student("Alice", 20, "CS101"); + Student s2 = new Student("Bob", 22, "CS102"); + + manager.addStudent(s1); + manager.addStudent(s2); + + System.out.println("Students enrolled:"); + manager.displayStudents(); + + double avg = manager.calculateAverageAge(); + System.out.println("\nAverage age: " + avg); + } +} + +// Student.java +class Student { + private String name; + private int age; + private String courseId; + + public Student(String name, int age, String courseId) { + this.name = name; + this.age = age; + this.courseId = courseId; + } + + //DeadCodeStart + public String getName() { + return name; + } + //DeadCodeEnd + + public int getAge() { + return age; + } + + //DeadCodeStart + public String getCourseId() { + return courseId; + } + + public void setGrade(char grade) { + // This method is never called + System.out.println("Grade set to: " + grade); + } + + private boolean isHonorsStudent() { + // Unused helper method + return age > 21; + } + //DeadCodeEnd + + @Override + public String toString() { + return "Student{name='" + name + "', age=" + age + ", course='" + courseId + "'}"; + } +} + +// StudentManager.java + +class StudentManager { + private List students; + + public StudentManager() { + students = new ArrayList<>(); + } + + public void addStudent(Student student) { + students.add(student); + } + + public void displayStudents() { + for (Student s : students) { + System.out.println(s); + } + } + + public double calculateAverageAge() { + if (students.isEmpty()) return 0.0; + + int sum = 0; + for (Student s : students) { + sum += s.getAge(); + } + return (double) sum / students.size(); + } + + //DeadCodeStart + public Student findStudentByName(String name) { + // Never used in the application + for (Student s : students) { + if (s.getName().equals(name)) { + return s; + } + } + return null; + } + + private void sortStudentsByAge() { + // Incomplete dead code + System.out.println("Sorting students..."); + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project6.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project6.java new file mode 100644 index 0000000000..e3796b3c79 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project6.java @@ -0,0 +1,138 @@ +import java.util.ArrayList; +import java.util.List; + +// Main.java +public class Main { + public static void main(String[] args) { + EmployeeManager manager = new EmployeeManager(); + + Employee e1 = new Employee("Charlie", 28, "ENG001"); + Employee e2 = new Employee("Diana", 35, "MKT002"); + + manager.addEmployee(e1); + manager.addEmployee(e2); + + System.out.println("Employees registered:"); + manager.displayEmployees(); + + double avg = manager.calculateAverageAge(); + System.out.println("\nAverage age: " + avg); + + //DeadCodeStart + // This code tries to hide the plagiarism with unused functionality + System.out.println("\nCalculating bonuses..."); + double bonus = calculateBonus(e1); + // But bonus is never actually used + //DeadCodeEnd + } + + //DeadCodeStart + private static double calculateBonus(Employee e) { + // Dead code that was copied from nowhere + return e.getAge() * 100.0; + } + //DeadCodeEnd +} + +// Employee.java +class Employee { + private String name; + private int age; + private String departmentId; + + // PLAGIARIZED: Constructor logic copied from Student class + public Employee(String name, int age, String departmentId) { + this.name = name; + this.age = age; + this.departmentId = departmentId; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public String getDepartmentId() { + return departmentId; + } + + //DeadCodeStart + // PLAGIARIZED from Student.setGrade() but modified slightly + public void setPerformanceRating(char rating) { + // This method is never called - hiding plagiarism in dead code + System.out.println("Rating set to: " + rating); + } + + private boolean isSeniorEmployee() { + // PLAGIARIZED from Student.isHonorsStudent() - same logic + return age > 21; + } + //DeadCodeEnd + + @Override + public String toString() { + return "Employee{name='" + name + "', age=" + age + ", dept='" + departmentId + "'}"; + } +} + +// EmployeeManager.java +class EmployeeManager { + private List employees; + + public EmployeeManager() { + employees = new ArrayList<>(); + } + + public void addEmployee(Employee employee) { + employees.add(employee); + } + + public void displayEmployees() { + for (Employee e : employees) { + System.out.println(e); + } + } + + // PLAGIARIZED: Entire method copied from StudentManager.calculateAverageAge() + public double calculateAverageAge() { + if (employees.isEmpty()) return 0.0; + + int sum = 0; + for (Employee e : employees) { + sum += e.getAge(); + } + return (double) sum / employees.size(); + } + + //DeadCodeStart + // PLAGIARIZED: Copied from StudentManager.findStudentByName() and renamed + public Employee findEmployeeByName(String name) { + // Never used in the application - plagiarism hidden in dead code + for (Employee e : employees) { + if (e.getName().equals(name)) { + return e; + } + } + return null; + } + + private void sortEmployeesByAge() { + // PLAGIARIZED dead code from StudentManager + System.out.println("Sorting employees..."); + } + + public List getHighPerformers() { + // Additional dead code that's never called + List result = new ArrayList<>(); + for (Employee e : employees) { + if (e.getAge() > 30) { + result.add(e); + } + } + return result; + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project7.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project7.java new file mode 100644 index 0000000000..54f42bd587 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project7.java @@ -0,0 +1,326 @@ +import java.util.ArrayList; +import java.util.List; + +public class Main { + public static void main(String[] args) { + Library library = new Library("Central Library"); + + // Create books + Book book1 = new Book("1984", "George Orwell", "978-0451524935", 1949); + Book book2 = new Book("To Kill a Mockingbird", "Harper Lee", "978-0061120084", 1960); + Book book3 = new Book("The Great Gatsby", "F. Scott Fitzgerald", "978-0743273565", 1925); + + // Create members + Member member1 = new Member("John Doe", "M001", "john@email.com"); + Member member2 = new Member("Jane Smith", "M002", "jane@email.com"); + + // Add to library + library.addBook(book1); + library.addBook(book2); + library.addBook(book3); + library.registerMember(member1); + library.registerMember(member2); + + // Perform operations + System.out.println("=== Library System ==="); + library.displayAvailableBooks(); + + System.out.println("\n--- Borrowing books ---"); + library.borrowBook(member1, book1); + library.borrowBook(member2, book2); + + System.out.println("\n--- Available books after borrowing ---"); + library.displayAvailableBooks(); + + System.out.println("\n--- Member borrowing history ---"); + member1.displayBorrowingHistory(); + + //DeadCodeStart + // Initialize analytics but never use them + LibraryAnalytics analytics = new LibraryAnalytics(library); + analytics.calculatePopularityScore(book1); + //DeadCodeEnd + } +} + +// Book.java +class Book { + private String title; + private String author; + private String isbn; + private int publicationYear; + private boolean isAvailable; + private int borrowCount; + + public Book(String title, String author, String isbn, int publicationYear) { + this.title = title; + this.author = author; + this.isbn = isbn; + this.publicationYear = publicationYear; + this.isAvailable = true; + this.borrowCount = 0; + } + + public String getTitle() { + return title; + } + + //DeadCodeStart + public String getAuthor() { + return author; + } + + public String getIsbn() { + return isbn; + } + //DeadCodeEnd + + public boolean isAvailable() { + return isAvailable; + } + + public void setAvailable(boolean available) { + isAvailable = available; + } + + public void incrementBorrowCount() { + borrowCount++; + } + + //DeadCodeStart + public int getBorrowCount() { + return borrowCount; + } + + public int getPublicationYear() { + return publicationYear; + } + + public void updateTitle(String newTitle) { + // Never used - dead code + this.title = newTitle; + System.out.println("Title updated to: " + newTitle); + } + + private boolean isClassic() { + // Unused helper method + return publicationYear < 1950; + } + + public String getGenre() { + // Returns hardcoded value, never called + return "Fiction"; + } + //DeadCodeEnd + + @Override + public String toString() { + return String.format("'%s' by %s [%s] - %s", + title, author, isbn, isAvailable ? "Available" : "Borrowed"); + } +} + +// Member.java +class Member { + private String name; + private String memberId; + private String email; + private List borrowedBooks; + private List borrowingHistory; + private int totalBorrows; + + public Member(String name, String memberId, String email) { + this.name = name; + this.memberId = memberId; + this.email = email; + this.borrowedBooks = new ArrayList<>(); + this.borrowingHistory = new ArrayList<>(); + this.totalBorrows = 0; + } + + public String getName() { + return name; + } + + public String getMemberId() { + return memberId; + } + + public List getBorrowedBooks() { + return borrowedBooks; + } + + public void borrowBook(Book book) { + borrowedBooks.add(book); + borrowingHistory.add(book.getTitle()); + totalBorrows++; + } + + public void returnBook(Book book) { + borrowedBooks.remove(book); + } + + public void displayBorrowingHistory() { + System.out.println("Borrowing history for " + name + ":"); + for (String bookTitle : borrowingHistory) { + System.out.println(" - " + bookTitle); + } + } + + //DeadCodeStart + public String getEmail() { + return email; + } + + public void updateEmail(String newEmail) { + // Never called + this.email = newEmail; + System.out.println("Email updated for " + name); + } + + public int calculateMembershipLevel() { + // Complex dead code that's never used + if (totalBorrows > 50) return 3; + else if (totalBorrows > 20) return 2; + else return 1; + } + + private boolean canBorrowMore() { + // Unused validation method + return borrowedBooks.size() < 5; + } + + public List getRecommendations() { + // Dead code that returns dummy data + List recommendations = new ArrayList<>(); + recommendations.add("Recommended Book 1"); + recommendations.add("Recommended Book 2"); + return recommendations; + } + //DeadCodeEnd +} + +// Library.java +class Library { + private String name; + private List books; + private List members; + + public Library(String name) { + this.name = name; + this.books = new ArrayList<>(); + this.members = new ArrayList<>(); + } + + public void addBook(Book book) { + books.add(book); + System.out.println("Added book: " + book.getTitle()); + } + + public void registerMember(Member member) { + members.add(member); + System.out.println("Registered member: " + member.getName()); + } + + public void borrowBook(Member member, Book book) { + if (book.isAvailable()) { + book.setAvailable(false); + book.incrementBorrowCount(); + member.borrowBook(book); + System.out.println(member.getName() + " borrowed " + book.getTitle()); + } else { + System.out.println("Book is not available: " + book.getTitle()); + } + } + + public void returnBook(Member member, Book book) { + book.setAvailable(true); + member.returnBook(book); + System.out.println(member.getName() + " returned " + book.getTitle()); + } + + public void displayAvailableBooks() { + System.out.println("Available books in " + name + ":"); + for (Book book : books) { + if (book.isAvailable()) { + System.out.println(" " + book); + } + } + } + + public List getBooks() { + return books; + } + + //DeadCodeStart + public List getMembers() { + return members; + } + + public Book findBookByIsbn(String isbn) { + // Never used search functionality + for (Book book : books) { + if (book.getIsbn().equals(isbn)) { + return book; + } + } + return null; + } + + public Member findMemberById(String memberId) { + // Dead code for finding members + for (Member member : members) { + if (member.getMemberId().equals(memberId)) { + return member; + } + } + return null; + } + + private void generateMonthlyReport() { + // Incomplete dead code + System.out.println("Generating monthly report for " + name); + int totalBooks = books.size(); + int availableBooks = 0; + for (Book book : books) { + if (book.isAvailable()) availableBooks++; + } + } + + public void removeBook(String isbn) { + // Never called removal method + Book toRemove = findBookByIsbn(isbn); + if (toRemove != null) { + books.remove(toRemove); + System.out.println("Removed book: " + toRemove.getTitle()); + } + } + //DeadCodeEnd +} + +// LibraryAnalytics.java +//DeadCodeStart +class LibraryAnalytics { + private Library library; + + public LibraryAnalytics(Library library) { + this.library = library; + } + + public double calculatePopularityScore(Book book) { + // Entire class is dead code - never actually used + return book.getBorrowCount() * 1.5; + } + + public List getMostPopularBooks(int count) { + List allBooks = library.getBooks(); + // Incomplete sorting logic + return allBooks.subList(0, Math.min(count, allBooks.size())); + } + + public void printStatistics() { + System.out.println("Library Statistics:"); + System.out.println("Total books: " + library.getBooks().size()); + } +} +//DeadCodeEnd diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project8.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project8.java new file mode 100644 index 0000000000..f7a3d403a0 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project8.java @@ -0,0 +1,398 @@ +import java.util.ArrayList; +import java.util.List; + +public class Main { + public static void main(String[] args) { + Warehouse warehouse = new Warehouse("Main Warehouse"); + + // Create products + Product product1 = new Product("Laptop", "Manufacturer A", "SKU-001", 2020); + Product product2 = new Product("Monitor", "Manufacturer B", "SKU-002", 2021); + Product product3 = new Product("Keyboard", "Manufacturer C", "SKU-003", 2019); + + // Create customers + Customer customer1 = new Customer("Alice Johnson", "C001", "alice@company.com"); + Customer customer2 = new Customer("Bob Williams", "C002", "bob@company.com"); + + // Add to warehouse + warehouse.addProduct(product1); + warehouse.addProduct(product2); + warehouse.addProduct(product3); + warehouse.registerCustomer(customer1); + warehouse.registerCustomer(customer2); + + // Perform operations + System.out.println("=== Inventory System ==="); + warehouse.displayAvailableProducts(); + + System.out.println("\n--- Processing orders ---"); + warehouse.processOrder(customer1, product1); + warehouse.processOrder(customer2, product2); + + System.out.println("\n--- Available products after orders ---"); + warehouse.displayAvailableProducts(); + + System.out.println("\n--- Customer order history ---"); + customer1.displayOrderHistory(); + + //DeadCodeStart + // PLAGIARIZED: Similar pattern from Library's Main class + InventoryAnalytics analytics = new InventoryAnalytics(warehouse); + analytics.calculateDemandScore(product1); + //DeadCodeEnd + + // Additional dead code to obscure plagiarism + System.out.println("\n--- Checking warehouse capacity ---"); + //DeadCodeStart + int capacity = warehouse.getTotalCapacity(); + // capacity is calculated but never used + //DeadCodeEnd + } +} + +// Product.java +class Product { + private String name; + private String manufacturer; + private String sku; + private int manufactureYear; + private boolean inStock; + private int orderCount; + + // PLAGIARIZED: Constructor pattern directly from Book class + public Product(String name, String manufacturer, String sku, int manufactureYear) { + this.name = name; + this.manufacturer = manufacturer; + this.sku = sku; + this.manufactureYear = manufactureYear; + this.inStock = true; + this.orderCount = 0; + } + + public String getName() { + return name; + } + + //DeadCodeStart + public String getManufacturer() { + return manufacturer; + } + + public String getSku() { + return sku; + } + //DeadCodeEnd + + // PLAGIARIZED: Same logic as Book.isAvailable() + public boolean inStock() { + return inStock; + } + + // PLAGIARIZED: Same as Book.setAvailable() + public void setInStock(boolean stock) { + inStock = stock; + } + + // PLAGIARIZED: Exact copy of Book.incrementBorrowCount() + public void incrementOrderCount() { + orderCount++; + } + + //DeadCodeStart + public int getOrderCount() { + return orderCount; + } + + public int getManufactureYear() { + return manufactureYear; + } + + // PLAGIARIZED from Book.updateTitle() - hidden in dead code + public void updateName(String newName) { + // Never used - dead code + this.name = newName; + System.out.println("Name updated to: " + newName); + } + + // PLAGIARIZED: Same logic as Book.isClassic() + private boolean isLegacyProduct() { + // Unused helper method - plagiarized and hidden + return manufactureYear < 1950; + } + + // PLAGIARIZED from Book.getGenre() + public String getCategory() { + // Returns hardcoded value, never called + return "Electronics"; + } + + public double calculateDepreciation() { + // Additional dead code + int age = 2024 - manufactureYear; + return age * 0.1; + } + //DeadCodeEnd + + // PLAGIARIZED: toString format similar to Book + @Override + public String toString() { + return String.format("'%s' by %s [%s] - %s", + name, manufacturer, sku, inStock ? "In Stock" : "Out of Stock"); + } +} + +// Customer.java + +class Customer { + private String name; + private String customerId; + private String email; + private List orderedProducts; + private List orderHistory; + private int totalOrders; + + // PLAGIARIZED: Exact same structure as Member constructor + public Customer(String name, String customerId, String email) { + this.name = name; + this.customerId = customerId; + this.email = email; + this.orderedProducts = new ArrayList<>(); + this.orderHistory = new ArrayList<>(); + this.totalOrders = 0; + } + + public String getName() { + return name; + } + + //DeadCodeStart + public String getCustomerId() { + return customerId; + } + + public List getOrderedProducts() { + return orderedProducts; + } + //DeadCodeEnd + + // PLAGIARIZED: Same logic as Member.borrowBook() + public void orderProduct(Product product) { + orderedProducts.add(product); + orderHistory.add(product.getName()); + totalOrders++; + } + + // PLAGIARIZED: Copy of Member.returnBook() + public void cancelOrder(Product product) { + orderedProducts.remove(product); + } + + // PLAGIARIZED: Near-exact copy of Member.displayBorrowingHistory() + public void displayOrderHistory() { + System.out.println("Order history for " + name + ":"); + for (String productName : orderHistory) { + System.out.println(" - " + productName); + } + } + + //DeadCodeStart + public String getEmail() { + return email; + } + + // PLAGIARIZED from Member.updateEmail() - in dead code + public void updateEmail(String newEmail) { + // Never called + this.email = newEmail; + System.out.println("Email updated for " + name); + } + + // PLAGIARIZED: Exact same logic as Member.calculateMembershipLevel() + public int calculateCustomerTier() { + // Complex dead code that's never used - plagiarized + if (totalOrders > 50) return 3; + else if (totalOrders > 20) return 2; + else return 1; + } + + // PLAGIARIZED from Member.canBorrowMore() + private boolean canOrderMore() { + // Unused validation method - plagiarized logic + return orderedProducts.size() < 5; + } + + // PLAGIARIZED from Member.getRecommendations() + public List getSuggestedProducts() { + // Dead code that returns dummy data + List suggestions = new ArrayList<>(); + suggestions.add("Suggested Product 1"); + suggestions.add("Suggested Product 2"); + return suggestions; + } + + public double calculateLifetimeValue() { + // Extra dead code + return totalOrders * 150.0; + } + //DeadCodeEnd +} + +// Warehouse.java + +class Warehouse { + private String name; + private List products; + private List customers; + + public Warehouse(String name) { + this.name = name; + this.products = new ArrayList<>(); + this.customers = new ArrayList<>(); + } + + // PLAGIARIZED: Exact copy of Library.addBook() + public void addProduct(Product product) { + products.add(product); + System.out.println("Added product: " + product.getName()); + } + + // PLAGIARIZED: Copy of Library.registerMember() + public void registerCustomer(Customer customer) { + customers.add(customer); + System.out.println("Registered customer: " + customer.getName()); + } + + // PLAGIARIZED: Same logic structure as Library.borrowBook() + public void processOrder(Customer customer, Product product) { + if (product.inStock()) { + product.setInStock(false); + product.incrementOrderCount(); + customer.orderProduct(product); + System.out.println(customer.getName() + " ordered " + product.getName()); + } else { + System.out.println("Product is not in stock: " + product.getName()); + } + } + + // PLAGIARIZED: Copy of Library.returnBook() + public void restockProduct(Customer customer, Product product) { + product.setInStock(true); + customer.cancelOrder(product); + System.out.println(customer.getName() + " cancelled order for " + product.getName()); + } + + // PLAGIARIZED: Near-identical to Library.displayAvailableBooks() + public void displayAvailableProducts() { + System.out.println("Available products in " + name + ":"); + for (Product product : products) { + if (product.inStock()) { + System.out.println(" " + product); + } + } + } + + public List getProducts() { + return products; + } + + //DeadCodeStart + public List getCustomers() { + return customers; + } + + // PLAGIARIZED from Library.findBookByIsbn() - hidden in dead code + public Product findProductBySku(String sku) { + // Never used search functionality - plagiarized + for (Product product : products) { + if (product.getSku().equals(sku)) { + return product; + } + } + return null; + } + + // PLAGIARIZED from Library.findMemberById() + public Customer findCustomerById(String customerId) { + // Dead code for finding customers - plagiarized + for (Customer customer : customers) { + if (customer.getCustomerId().equals(customerId)) { + return customer; + } + } + return null; + } + + // PLAGIARIZED from Library.generateMonthlyReport() + private void generateInventoryReport() { + // Incomplete dead code - plagiarized structure + System.out.println("Generating inventory report for " + name); + int totalProducts = products.size(); + int availableProducts = 0; + for (Product product : products) { + if (product.inStock()) availableProducts++; + } + } + + // PLAGIARIZED from Library.removeBook() + public void removeProduct(String sku) { + // Never called removal method - plagiarized + Product toRemove = findProductBySku(sku); + if (toRemove != null) { + products.remove(toRemove); + System.out.println("Removed product: " + toRemove.getName()); + } + } + + public int getTotalCapacity() { + // Dead code that calculates but result is never used + return products.size() * 100; + } + + private void optimizeStorage() { + // Dead code with complex logic + List inStockProducts = new ArrayList<>(); + for (Product p : products) { + if (p.inStock()) { + inStockProducts.add(p); + } + } + // Do nothing with the list + } + //DeadCodeEnd +} + +// InventoryAnalytics.java +//DeadCodeStart +// PLAGIARIZED: Entire class structure copied from LibraryAnalytics +class InventoryAnalytics { + private Warehouse warehouse; + + public InventoryAnalytics(Warehouse warehouse) { + this.warehouse = warehouse; + } + + // PLAGIARIZED from LibraryAnalytics.calculatePopularityScore() + public double calculateDemandScore(Product product) { + // Entire class is dead code - never actually used, but plagiarized + return product.getOrderCount() * 1.5; + } + + // PLAGIARIZED from LibraryAnalytics.getMostPopularBooks() + public List getMostOrderedProducts(int count) { + List allProducts = warehouse.getProducts(); + // Incomplete sorting logic - plagiarized + return allProducts.subList(0, Math.min(count, allProducts.size())); + } + + // PLAGIARIZED from LibraryAnalytics.printStatistics() + public void printStatistics() { + System.out.println("Warehouse Statistics:"); + System.out.println("Total products: " + warehouse.getProducts().size()); + } + + public void forecastDemand() { + // Additional dead code method + System.out.println("Forecasting future demand..."); + } +} +//DeadCodeEnd diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project9.java b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project9.java new file mode 100644 index 0000000000..393581318e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project9.java @@ -0,0 +1,713 @@ +// Main.java + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class Main { + public static void main(String[] args) { + Hospital hospital = new Hospital("St. Mary's Hospital", "123 Medical Center Dr"); + + // Create departments + Department cardiology = new Department("Cardiology", "D001", 20); + Department neurology = new Department("Neurology", "D002", 15); + Department emergency = new Department("Emergency", "D003", 30); + + hospital.addDepartment(cardiology); + hospital.addDepartment(neurology); + hospital.addDepartment(emergency); + + // Create doctors + Doctor dr1 = new Doctor("Dr. Sarah Johnson", "DOC001", "Cardiology", 15); + Doctor dr2 = new Doctor("Dr. Michael Chen", "DOC002", "Neurology", 10); + Doctor dr3 = new Doctor("Dr. Emily Davis", "DOC003", "Emergency", 8); + + hospital.registerDoctor(dr1, cardiology); + hospital.registerDoctor(dr2, neurology); + hospital.registerDoctor(dr3, emergency); + + // Create patients + Patient patient1 = new Patient("John Smith", "P001", 45, "O+"); + Patient patient2 = new Patient("Mary Williams", "P002", 62, "A+"); + Patient patient3 = new Patient("James Brown", "P003", 38, "B-"); + + hospital.registerPatient(patient1); + hospital.registerPatient(patient2); + hospital.registerPatient(patient3); + + // Schedule appointments + System.out.println("=== Hospital Management System ==="); + System.out.println("Hospital: " + hospital.getName()); + System.out.println(); + + Appointment apt1 = hospital.scheduleAppointment(patient1, dr1, "Routine checkup"); + Appointment apt2 = hospital.scheduleAppointment(patient2, dr2, "Follow-up consultation"); + Appointment apt3 = hospital.scheduleAppointment(patient3, dr3, "Emergency visit"); + + System.out.println("\n--- Scheduled Appointments ---"); + hospital.displayAppointments(); + + System.out.println("\n--- Processing Appointments ---"); + hospital.processAppointment(apt1, "Patient shows stable heart rate. Prescribed medication."); + hospital.processAppointment(apt2, "Brain scan results normal. Continue current treatment."); + + System.out.println("\n--- Doctor Workload ---"); + hospital.displayDoctorWorkload(); + + System.out.println("\n--- Patient Medical History ---"); + patient1.displayMedicalHistory(); + + //DeadCodeStart + // Create billing system but never use it + BillingSystem billing = new BillingSystem(hospital); + double cost = billing.calculateAppointmentCost(apt1); + Invoice invoice = billing.generateInvoice(patient1, apt1); + //DeadCodeEnd + + //DeadCodeStart + // Analytics initialization + HospitalAnalytics analytics = new HospitalAnalytics(hospital); + analytics.generateOccupancyReport(); + double avgPatientAge = analytics.calculateAveragePatientAge(); + //DeadCodeEnd + } +} + +// Department.java +class Department { + private String name; + private String deptId; + private int capacity; + private List doctors; + private int currentPatients; + + public Department(String name, String deptId, int capacity) { + this.name = name; + this.deptId = deptId; + this.capacity = capacity; + this.doctors = new ArrayList<>(); + this.currentPatients = 0; + } + + public String getName() { + return name; + } + + public String getDeptId() { + return deptId; + } + + public void addDoctor(Doctor doctor) { + doctors.add(doctor); + } + + //DeadCodeStart + public List getDoctors() { + return doctors; + } + //DeadCodeEnd + + public boolean hasCapacity() { + return currentPatients < capacity; + } + + public void incrementPatientCount() { + currentPatients++; + } + + public void decrementPatientCount() { + if (currentPatients > 0) currentPatients--; + } + + //DeadCodeStart + public int getCapacity() { + return capacity; + } + + public void setCapacity(int newCapacity) { + // Never called + this.capacity = newCapacity; + System.out.println("Capacity updated to: " + newCapacity); + } + + public int getCurrentPatients() { + return currentPatients; + } + + public double getUtilizationRate() { + // Unused calculation method + if (capacity == 0) return 0.0; + return (double) currentPatients / capacity * 100; + } + + public List getAvailableDoctors() { + // Complex dead code for finding available doctors + List available = new ArrayList<>(); + for (Doctor doc : doctors) { + if (doc.getAppointmentCount() < 10) { + available.add(doc); + } + } + return available; + } + + private void reorganizeDoctors() { + // Incomplete optimization logic + System.out.println("Reorganizing department: " + name); + } + //DeadCodeEnd + + @Override + public String toString() { + return String.format("Department: %s [%s] - %d doctors, %d/%d patients", + name, deptId, doctors.size(), currentPatients, capacity); + } +} + +// Doctor.java + +class Doctor { + private String name; + private String doctorId; + private String specialization; + private int yearsOfExperience; + private List appointments; + private int appointmentCount; + private Department assignedDepartment; + + public Doctor(String name, String doctorId, String specialization, int yearsOfExperience) { + this.name = name; + this.doctorId = doctorId; + this.specialization = specialization; + this.yearsOfExperience = yearsOfExperience; + this.appointments = new ArrayList<>(); + this.appointmentCount = 0; + } + + public String getName() { + return name; + } + + public String getDoctorId() { + return doctorId; + } + + public String getSpecialization() { + return specialization; + } + + public void setDepartment(Department dept) { + this.assignedDepartment = dept; + } + + public void addAppointment(Appointment appointment) { + appointments.add(appointment); + appointmentCount++; + } + + public int getAppointmentCount() { + return appointmentCount; + } + + public List getAppointments() { + return appointments; + } + + //DeadCodeStart + public int getYearsOfExperience() { + return yearsOfExperience; + } + + public Department getAssignedDepartment() { + return assignedDepartment; + } + + public void updateSpecialization(String newSpec) { + // Never used + this.specialization = newSpec; + System.out.println("Specialization updated for " + name); + } + + public boolean isSeniorDoctor() { + // Unused classification method + return yearsOfExperience >= 10; + } + + public double calculateSalary() { + // Dead code for salary calculation + double baseSalary = 100000; + double experienceBonus = yearsOfExperience * 5000; + return baseSalary + experienceBonus; + } + + public List getPatientList() { + // Dead code that extracts patients from appointments + List patients = new ArrayList<>(); + for (Appointment apt : appointments) { + patients.add(apt.getPatient()); + } + return patients; + } + + private void sendReminders() { + // Incomplete notification system + System.out.println("Sending appointment reminders..."); + } + //DeadCodeEnd + + @Override + public String toString() { + return String.format("%s (%s) - %s specialist, %d appointments", + name, doctorId, specialization, appointmentCount); + } +} + +// Patient.java +class Patient { + private String name; + private String patientId; + private int age; + private String bloodType; + private List medicalHistory; + private List appointments; + private String currentStatus; + + public Patient(String name, String patientId, int age, String bloodType) { + this.name = name; + this.patientId = patientId; + this.age = age; + this.bloodType = bloodType; + this.medicalHistory = new ArrayList<>(); + this.appointments = new ArrayList<>(); + this.currentStatus = "Registered"; + } + + public String getName() { + return name; + } + + public String getPatientId() { + return patientId; + } + + public int getAge() { + return age; + } + + public void addMedicalRecord(String record) { + medicalHistory.add(record); + } + + public void addAppointment(Appointment appointment) { + appointments.add(appointment); + } + + public String getStatus() { + return currentStatus; + } + + public void setStatus(String status) { + this.currentStatus = status; + } + + public void displayMedicalHistory() { + System.out.println("Medical history for " + name + ":"); + if (medicalHistory.isEmpty()) { + System.out.println(" No records yet"); + } else { + for (String record : medicalHistory) { + System.out.println(" - " + record); + } + } + } + + //DeadCodeStart + public String getBloodType() { + return bloodType; + } + + public List getAppointments() { + return appointments; + } + + public void updateAge(int newAge) { + // Never called + this.age = newAge; + } + + public void updateBloodType(String newBloodType) { + // Dead code for updating blood type + this.bloodType = newBloodType; + System.out.println("Blood type updated for " + name); + } + + public boolean isHighRiskPatient() { + // Unused risk assessment + return age > 65 || medicalHistory.size() > 5; + } + + public List getAllergies() { + // Returns empty list - never properly implemented + return new ArrayList<>(); + } + + public String getInsuranceInfo() { + // Dead code returning placeholder + return "Insurance ID: INS-" + patientId; + } + + private void notifyEmergencyContact() { + // Incomplete notification system + System.out.println("Notifying emergency contact for " + name); + } + //DeadCodeEnd + + @Override + public String toString() { + return String.format("Patient: %s [%s] - Age: %d, Blood Type: %s, Status: %s", + name, patientId, age, bloodType, currentStatus); + } +} + +// Appointment.java + +class Appointment { + private static int nextId = 1; + private String appointmentId; + private Patient patient; + private Doctor doctor; + private LocalDateTime scheduledTime; + private String reason; + private String status; + private String diagnosis; + + public Appointment(Patient patient, Doctor doctor, String reason) { + this.appointmentId = "APT" + String.format("%04d", nextId++); + this.patient = patient; + this.doctor = doctor; + this.scheduledTime = LocalDateTime.now(); + this.reason = reason; + this.status = "Scheduled"; + this.diagnosis = ""; + } + + public String getAppointmentId() { + return appointmentId; + } + + public Patient getPatient() { + return patient; + } + + public Doctor getDoctor() { + return doctor; + } + + public String getStatus() { + return status; + } + + //DeadCodeStart + public void setStatus(String status) { + this.status = status; + } + + public String getDiagnosis() { + return diagnosis; + } + //DeadCodeEnd + + public void setDiagnosis(String diagnosis) { + this.diagnosis = diagnosis; + this.status = "Completed"; + } + + //DeadCodeStart + public LocalDateTime getScheduledTime() { + return scheduledTime; + } + + public String getReason() { + return reason; + } + + public void reschedule(LocalDateTime newTime) { + // Never called rescheduling method + this.scheduledTime = newTime; + System.out.println("Appointment rescheduled to: " + + newTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + } + + public void cancel() { + // Dead code for cancellation + this.status = "Cancelled"; + System.out.println("Appointment " + appointmentId + " cancelled"); + } + + public int getDurationMinutes() { + // Returns fixed duration - never used + return 30; + } + + private boolean isUrgent() { + // Unused priority check + return reason.toLowerCase().contains("emergency"); + } + //DeadCodeEnd + + @Override + public String toString() { + return String.format("[%s] %s with %s - %s (%s)", + appointmentId, patient.getName(), doctor.getName(), reason, status); + } +} + +// Hospital.java + +class Hospital { + private String name; + private String address; + private List departments; + private List doctors; + private List patients; + private List appointments; + + public Hospital(String name, String address) { + this.name = name; + this.address = address; + this.departments = new ArrayList<>(); + this.doctors = new ArrayList<>(); + this.patients = new ArrayList<>(); + this.appointments = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void addDepartment(Department dept) { + departments.add(dept); + System.out.println("Added department: " + dept.getName()); + } + + public void registerDoctor(Doctor doctor, Department dept) { + doctors.add(doctor); + doctor.setDepartment(dept); + dept.addDoctor(doctor); + System.out.println("Registered doctor: " + doctor.getName() + " to " + dept.getName()); + } + + public void registerPatient(Patient patient) { + patients.add(patient); + System.out.println("Registered patient: " + patient.getName()); + } + + public Appointment scheduleAppointment(Patient patient, Doctor doctor, String reason) { + Appointment apt = new Appointment(patient, doctor, reason); + appointments.add(apt); + patient.addAppointment(apt); + doctor.addAppointment(apt); + System.out.println("Scheduled: " + apt); + return apt; + } + + public void processAppointment(Appointment apt, String diagnosis) { + apt.setDiagnosis(diagnosis); + apt.getPatient().addMedicalRecord(diagnosis); + System.out.println("Processed appointment " + apt.getAppointmentId() + + " - Status: " + apt.getStatus()); + } + + public void displayAppointments() { + for (Appointment apt : appointments) { + System.out.println(" " + apt); + } + } + + public void displayDoctorWorkload() { + for (Doctor doc : doctors) { + System.out.println(" " + doc); + } + } + + public List getPatients() { + return patients; + } + + public List getDoctors() { + return doctors; + } + + public List getDepartments() { + return departments; + } + + //DeadCodeStart + public String getAddress() { + return address; + } + + public List getAppointments() { + return appointments; + } + + public Department findDepartmentById(String deptId) { + // Never used search method + for (Department dept : departments) { + if (dept.getDeptId().equals(deptId)) { + return dept; + } + } + return null; + } + + public Doctor findDoctorById(String doctorId) { + // Dead code for finding doctors + for (Doctor doc : doctors) { + if (doc.getDoctorId().equals(doctorId)) { + return doc; + } + } + return null; + } + + public Patient findPatientById(String patientId) { + // Dead code for finding patients + for (Patient p : patients) { + if (p.getPatientId().equals(patientId)) { + return p; + } + } + return null; + } + + public List getAppointmentsByStatus(String status) { + // Complex filtering that's never used + List filtered = new ArrayList<>(); + for (Appointment apt : appointments) { + if (apt.getStatus().equals(status)) { + filtered.add(apt); + } + } + return filtered; + } + + private void generateDailyReport() { + // Incomplete reporting logic + System.out.println("Generating daily report for " + name); + System.out.println("Total appointments: " + appointments.size()); + System.out.println("Total patients: " + patients.size()); + } + + public void transferPatient(Patient patient, Department from, Department to) { + // Never called transfer logic + if (to.hasCapacity()) { + from.decrementPatientCount(); + to.incrementPatientCount(); + System.out.println("Transferred " + patient.getName() + + " from " + from.getName() + " to " + to.getName()); + } + } + //DeadCodeEnd +} + +// BillingSystem.java +//DeadCodeStart +class BillingSystem { + private static final double BASE_CONSULTATION_FEE = 150.0; + private Hospital hospital; + + public BillingSystem(Hospital hospital) { + this.hospital = hospital; + } + + public double calculateAppointmentCost(Appointment apt) { + // Entire class is dead code + double cost = BASE_CONSULTATION_FEE; + Doctor doc = apt.getDoctor(); + if (doc.getYearsOfExperience() > 10) { + cost *= 1.5; + } + return cost; + } + + public Invoice generateInvoice(Patient patient, Appointment apt) { + double amount = calculateAppointmentCost(apt); + return new Invoice(patient, apt, amount); + } + + public void processPayment(Invoice invoice) { + System.out.println("Processing payment for invoice: " + invoice.getInvoiceId()); + } +} + +class Invoice { + private static int nextId = 1; + private String invoiceId; + private Patient patient; + private Appointment appointment; + private double amount; + private boolean paid; + + public Invoice(Patient patient, Appointment appointment, double amount) { + this.invoiceId = "INV" + String.format("%05d", nextId++); + this.patient = patient; + this.appointment = appointment; + this.amount = amount; + this.paid = false; + } + + public String getInvoiceId() { + return invoiceId; + } + + public void markAsPaid() { + this.paid = true; + } +} +//DeadCodeEnd + +// HospitalAnalytics.java +//DeadCodeStart +class HospitalAnalytics { + private Hospital hospital; + + public HospitalAnalytics(Hospital hospital) { + this.hospital = hospital; + } + + public void generateOccupancyReport() { + // Never actually used - dead analytics code + System.out.println("=== Occupancy Report ==="); + for (Department dept : hospital.getDepartments()) { + System.out.println(dept.getName() + ": " + + dept.getCurrentPatients() + "/" + dept.getCapacity()); + } + } + + public double calculateAveragePatientAge() { + // Dead calculation method + List patients = hospital.getPatients(); + if (patients.isEmpty()) return 0.0; + + int sum = 0; + for (Patient p : patients) { + sum += p.getAge(); + } + return (double) sum / patients.size(); + } + + public List getTopDoctors(int count) { + // Complex sorting logic that's never used + List sorted = new ArrayList<>(hospital.getDoctors()); + // Incomplete sorting implementation + return sorted.subList(0, Math.min(count, sorted.size())); + } + + private void analyzeTrends() { + // Incomplete trend analysis + System.out.println("Analyzing hospital trends..."); + } +} +//DeadCodeEnd diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/claude/source.txt b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/source.txt new file mode 100644 index 0000000000..f258a67faf --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/source.txt @@ -0,0 +1,10 @@ +All Code in this directory is generated with Anthropic Claude Sonnet 4.5 +Initial Prompt: " write one or more java example projects that must contain: + + - a Main class with a main method as a starting point + + - sometimes dead code in all variants + + - sometimes one project should plagiarize from another + + - plagiarisms and dead code can be combined, and dead code can be used to hide plagiarisms " diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project1.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project1.java new file mode 100644 index 0000000000..dbb3d7e053 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project1.java @@ -0,0 +1,61 @@ +import java.util.Arrays; + +public class ArraySorter { + + public static void main(String[] args) { + int[] data = { 8, 7, 2, 1, 0, 9, 6 }; + System.out.println("Unsorted: " + Arrays.toString(data)); + + sort(data, 0, data.length - 1); + + System.out.println("Sorted: " + Arrays.toString(data)); + + //DeadCodeStart + if (false) { + System.out.println("Debug: Sorting complete."); + printDebugStats(data); + } + //DeadCodeEnd + } + + // Standard QuickSort implementation + public static void sort(int[] arr, int low, int high) { + if (low < high) { + int pi = partition(arr, low, high); + + sort(arr, low, pi - 1); + sort(arr, pi + 1, high); + } + } + + private static int partition(int[] arr, int low, int high) { + int pivot = arr[high]; + int i = (low - 1); + + for (int j = low; j < high; j++) { + if (arr[j] <= pivot) { + i++; + swap(arr, i, j); + } + } + swap(arr, i + 1, high); + return i + 1; + } + + private static void swap(int[] arr, int i, int j) { + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + + //DeadCodeStart + // This method is defined but never called in the execution flow + private static void printDebugStats(int[] arr) { + int sum = 0; + for (int i : arr) { + sum += i; + } + System.out.println("Checksum: " + sum); + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project2.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project2.java new file mode 100644 index 0000000000..237e83d35b --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project2.java @@ -0,0 +1,64 @@ +import java.util.Arrays; + +public class LogAnalyzer { + + public static void main(String[] args) { + // Simulating timestamps (longs) instead of ints, but the logic is identical + long[] timestamps = { 162000L, 161000L, 163000L, 160500L, 162500L }; + + System.out.println("Raw Logs: " + Arrays.toString(timestamps)); + + analyzeSequence(timestamps, 0, timestamps.length - 1); + + System.out.println("Ordered: " + Arrays.toString(timestamps)); + } + + // Plagiarism: Renamed 'sort' to 'analyzeSequence' + // Logic is identical to QuickSort + public static void analyzeSequence(long[] logs, int start, int end) { + if (start < end) { + // Plagiarism: Renamed 'partition' to 'segmentize' + int index = segmentize(logs, start, end); + + analyzeSequence(logs, start, index - 1); + analyzeSequence(logs, index + 1, end); + } + } + + private static int segmentize(long[] logs, int start, int end) { + long ref = logs[end]; // Renamed 'pivot' to 'ref' + int marker = (start - 1); // Renamed 'i' to 'marker' + + //DeadCodeStart + // This loop looks like it calculates a hash for security, + // but the result is local and discarded immediately. + // It serves to distract from the copied logic below. + long securityHash = 0; + for (int k = start; k < end; k++) { + securityHash = (securityHash * 31 + logs[k]) % 100000; + if (securityHash < 0) { + securityHash = 0; // Unreachable for positive inputs + } + } + //DeadCodeEnd + + // Plagiarism: Identical logic to 'partition' loop in Project 1 + for (int scanner = start; scanner < end; scanner++) { // Renamed 'j' to 'scanner' + if (logs[scanner] <= ref) { + marker++; + + // Plagiarism: Inline swap logic instead of helper function to look different + long temp = logs[marker]; + logs[marker] = logs[scanner]; + logs[scanner] = temp; + } + } + + // Final swap of the reference element + long tempRef = logs[marker + 1]; + logs[marker + 1] = logs[end]; + logs[end] = tempRef; + + return marker + 1; + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project3.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project3.java new file mode 100644 index 0000000000..b3124a02cb --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project3.java @@ -0,0 +1,45 @@ +public class LegacyBankSystem { + + public static void main(String[] args) { + System.out.println("Bank System initialized v4.2"); + System.out.println("Performing daily audit..."); + + double[] accounts = { 100.50, 50.25, 200.00 }; + performAudit(accounts); + + System.out.println("Audit complete."); + } + + public static void performAudit(double[] accounts) { + double total = 0; + for (double d : accounts) { + total += d; + } + System.out.println("Total holdings: " + total); + } + + //DeadCodeStart + // This entire method is never called. + // It contains a plagiarized version of the sorting algorithm from Project 1. + // It might represent "dead" legacy code that was copied and pasted + // but forgotten, effectively hiding the plagiarism in an unused method. + private static void legacy_sorter_v1(int[] arr, int low, int high) { + // Exact logic from Project 1, just compacted + if (low < high) { + int pivot = arr[high]; + int i = (low - 1); + for (int j = low; j < high; j++) { + if (arr[j] <= pivot) { + i++; + int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; + } + } + int temp = arr[i+1]; arr[i+1] = arr[high]; arr[high] = temp; + int pi = i + 1; + + legacy_sorter_v1(arr, low, pi - 1); + legacy_sorter_v1(arr, pi + 1, high); + } + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project4.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project4.java new file mode 100644 index 0000000000..ed621b50ec --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project4.java @@ -0,0 +1,84 @@ +import java.util.Arrays; + +public class NetworkRouter { + + static final int V = 5; // Number of vertices + + public static void main(String[] args) { + // Adjacency matrix representation of the network + // 0 means no direct link, non-zero is the latency cost + int graph[][] = new int[][] { + { 0, 4, 0, 0, 0 }, + { 4, 0, 8, 0, 0 }, + { 0, 8, 0, 7, 0 }, + { 0, 0, 7, 0, 9 }, + { 0, 0, 0, 9, 0 } + }; + + System.out.println("Calculating shortest paths from Node 0..."); + dijkstra(graph, 0); + + //DeadCodeStart + // Opaque predicate: checks a mathematical impossibility to hide dead code + // The JVM might optimize this away, but source-wise it looks like logic + int check = (V * 2) % 2; + if (check != 0) { + System.err.println("Critical Error: Topology unstable."); + runDiagnostics(graph); + } + //DeadCodeEnd + } + + static void dijkstra(int graph[][], int src) { + int dist[] = new int[V]; + Boolean sptSet[] = new Boolean[V]; + + Arrays.fill(dist, Integer.MAX_VALUE); + Arrays.fill(sptSet, false); + + dist[src] = 0; + + for (int count = 0; count < V - 1; count++) { + int u = minDistance(dist, sptSet); + sptSet[u] = true; + + for (int v = 0; v < V; v++) + if (!sptSet[v] && graph[u][v] != 0 && + dist[u] != Integer.MAX_VALUE && + dist[u] + graph[u][v] < dist[v]) { + dist[v] = dist[u] + graph[u][v]; + } + } + printSolution(dist); + } + + static int minDistance(int dist[], Boolean sptSet[]) { + int min = Integer.MAX_VALUE, min_index = -1; + + for (int v = 0; v < V; v++) + if (sptSet[v] == false && dist[v] <= min) { + min = dist[v]; + min_index = v; + } + + return min_index; + } + + static void printSolution(int dist[]) { + System.out.println("Vertex \t Distance from Source"); + for (int i = 0; i < V; i++) + System.out.println(i + " \t " + dist[i]); + } + + //DeadCodeStart + static void runDiagnostics(int graph[][]) { + // Complex looking loop that effectively does nothing useful + for(int i=0; i 'impactCost' + // 'sptSet' -> 'analyzed' + static void calculateInfluence(int[][] relations, int influencerId) { + int[] impactCost = new int[USER_COUNT]; + Boolean[] analyzed = new Boolean[USER_COUNT]; + + Arrays.fill(impactCost, Integer.MAX_VALUE); + Arrays.fill(analyzed, false); + + impactCost[influencerId] = 0; + + for (int i = 0; i < USER_COUNT - 1; i++) { + int currentTarget = findMostSusceptible(impactCost, analyzed); + + //DeadCodeStart + // This block executes but is effectively dead because 'cache' is local + // and reset every iteration. It disguises the plagiarism flow. + int[] cache = new int[10]; + for(int k=0; k<5; k++) { + cache[k] = currentTarget * k; + } + //DeadCodeEnd + + analyzed[currentTarget] = true; + + // Plagiarism: The relaxation logic is identical to Project 4 + for (int v = 0; v < USER_COUNT; v++) { + // Formatting changed to hide structure slightly + boolean unvisited = !analyzed[v]; + boolean connected = relations[currentTarget][v] != 0; + boolean reachable = impactCost[currentTarget] != Integer.MAX_VALUE; + + if (unvisited && connected && reachable) { + int potentialNewImpact = impactCost[currentTarget] + relations[currentTarget][v]; + if (potentialNewImpact < impactCost[v]) { + impactCost[v] = potentialNewImpact; + } + } + } + } + + displayMetrics(impactCost); + } + + static int findMostSusceptible(int[] costs, Boolean[] mask) { + int minVal = Integer.MAX_VALUE; + int targetIndex = -1; + + for (int v = 0; v < USER_COUNT; v++) { + if (!mask[v] && costs[v] <= minVal) { + minVal = costs[v]; + targetIndex = v; + } + } + return targetIndex; + } + + static void displayMetrics(int[] data) { + System.out.println("User \t Resistance Score"); + for (int i = 0; i < USER_COUNT; i++) + System.out.println(i + " \t " + data[i]); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project7.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project7.java new file mode 100644 index 0000000000..5fe28f71d9 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project7.java @@ -0,0 +1,93 @@ +import java.util.Arrays; + +public class SmartGridEnergy { + + // Simulates a 5-node substation network + static final int NODES = 5; + + public static void main(String[] args) { + // Resistance (Ohms) between substations. + // Logic is identical to the graph weights in previous projects. + int[][] gridTopology = new int[][] { + { 0, 4, 0, 0, 0 }, + { 4, 0, 8, 0, 0 }, + { 0, 8, 0, 7, 0 }, + { 0, 0, 7, 0, 9 }, + { 0, 0, 0, 9, 0 } + }; + + System.out.println("Initializing Energy Grid Optimization..."); + + // Active usage of the plagiarized logic + int[] losses = calculateVoltageDrop(gridTopology, 0); + + System.out.println("Optimization Complete. Loss metrics per node:"); + System.out.println(Arrays.toString(losses)); + + //DeadCodeStart + if (losses.length > 100) { + // This block is unreachable for NODES = 5 + System.out.println("Emergency: Grid overload detected."); + performEmergencyShutdown(); + } + //DeadCodeEnd + } + + // Plagiarism: This is the Dijkstra implementation from Project 4, + // actively used here to calculate voltage drops. + public static int[] calculateVoltageDrop(int[][] resistance, int sourceNode) { + int[] drops = new int[NODES]; + Boolean[] locked = new Boolean[NODES]; + + Arrays.fill(drops, Integer.MAX_VALUE); + Arrays.fill(locked, false); + + drops[sourceNode] = 0; + + for (int i = 0; i < NODES - 1; i++) { + // Logic copied but extracted to a slightly different looking helper interaction + int u = getLowestResistanceNode(drops, locked); + locked[u] = true; + + //DeadCodeStart + // Phantom Calculation: disguised as a "noise filter" + // The result 'noise' is calculated but never applied to the 'drops' array + double noise = 0; + for(int k=0; k<5; k++) { + noise += Math.sin(k) * resistance[u][u]; + } + //DeadCodeEnd + + for (int v = 0; v < NODES; v++) { + // Standard relaxation logic + if (!locked[v] && resistance[u][v] != 0 && + drops[u] != Integer.MAX_VALUE && + drops[u] + resistance[u][v] < drops[v]) { + drops[v] = drops[u] + resistance[u][v]; + } + } + } + return drops; + } + + private static int getLowestResistanceNode(int[] drops, Boolean[] locked) { + int min = Integer.MAX_VALUE, min_index = -1; + for (int v = 0; v < NODES; v++) + if (!locked[v] && drops[v] <= min) { + min = drops[v]; + min_index = v; + } + return min_index; + } + + //DeadCodeStart + private static void performEmergencyShutdown() { + System.out.println("Shutting down core reactors..."); + for(int i = 0; i < 10; i++) { + // Simulating delay + long start = System.currentTimeMillis(); + while(System.currentTimeMillis() - start < 10) {}; + } + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project8.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project8.java new file mode 100644 index 0000000000..e84f7a92fb --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/Project8.java @@ -0,0 +1,94 @@ +import java.util.Arrays; + +public class SocialGraphMiner { + + private static final int MEMBERS = 5; + + public static void main(String[] args) { + // Represents "Closeness" (inverse of distance) or "Hops" + // Same data structure as previous graph examples + int[][] socialNetwork = { + {0, 4, 0, 0, 0}, + {4, 0, 8, 0, 0}, + {0, 8, 0, 7, 0}, + {0, 0, 7, 0, 9}, + {0, 0, 0, 9, 0} + }; + + System.out.println("Mapping social degrees of separation..."); + + // Active usage of the logic + mapConnections(socialNetwork, 0); + + //DeadCodeStart + // This method call looks legitimate but the method implementation does nothing effective + verifyPrivacyCompliance(socialNetwork); + //DeadCodeEnd + } + + // Plagiarism: Logic is identical to Project 4/7 + // Renamed variables to fit "Social Media" context + static void mapConnections(int[][] graph, int startUser) { + int[] separationDegrees = new int[MEMBERS]; + Boolean[] visited = new Boolean[MEMBERS]; + + Arrays.fill(separationDegrees, Integer.MAX_VALUE); + Arrays.fill(visited, false); + + separationDegrees[startUser] = 0; + + for (int count = 0; count < MEMBERS - 1; count++) { + int u = findNextUser(separationDegrees, visited); + visited[u] = true; + + for (int v = 0; v < MEMBERS; v++) { + //DeadCodeStart + // Redundant check: graph[u][v] >= 0 is always true for this data + if (graph[u][v] < 0) { + System.out.println("Negative edge detected"); + } + //DeadCodeEnd + + if (!visited[v] && graph[u][v] != 0 && + separationDegrees[u] != Integer.MAX_VALUE && + separationDegrees[u] + graph[u][v] < separationDegrees[v]) { + + separationDegrees[v] = separationDegrees[u] + graph[u][v]; + } + } + } + + printDegrees(separationDegrees); + } + + static int findNextUser(int[] deg, Boolean[] vis) { + int min = Integer.MAX_VALUE, idx = -1; + + for (int v = 0; v < MEMBERS; v++) + if (!vis[v] && deg[v] <= min) { + min = deg[v]; + idx = v; + } + return idx; + } + + static void printDegrees(int[] deg) { + System.out.println("User ID \t Separation Cost"); + for (int i = 0; i < MEMBERS; i++) + System.out.println(i + " \t\t " + deg[i]); + } + + //DeadCodeStart + // This looks like a complex verification algorithm, but it just loops + // and modifies local variables that are discarded. + static void verifyPrivacyCompliance(int[][] data) { + int complianceScore = 0; + for (int[] row : data) { + for (int val : row) { + if (val > 0) complianceScore++; + } + } + // Compliance score is calculated but never returned or printed + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectA.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectA.java new file mode 100644 index 0000000000..a20e56b414 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectA.java @@ -0,0 +1,150 @@ +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * PROJECT A: The Original + * A simple banking system to manage accounts. + * Contains minor dead code (debug flag). + */ +public class BankingSystem { + + private static final Map accounts = new HashMap<>(); + private static final boolean DEBUG_MODE = false; + + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + System.out.println("--- Simple Banking System 1.0 ---"); + + boolean running = true; + while (running) { + printMenu(); + String command = scanner.nextLine(); + +// switch (command) { //Does not work with java-cpg graph normalization +// case "1": +// createAccount(scanner); +// break; +// case "2": +// deposit(scanner); +// break; +// case "3": +// withdraw(scanner); +// break; +// case "4": +// checkBalance(scanner); +// break; +// case "5": +// running = false; +// System.out.println("Exiting..."); +// break; +// default: +// System.out.println("Invalid option."); +// } + + if (command.equals("1")) { + createAccount(scanner); + } else if (command.equals("2")) { + deposit(scanner); + } else if (command.equals("3")) { + withdraw(scanner); + } else if (command.equals("4")) { + checkBalance(scanner); + } else if (command.equals("5")) { + running = false; + System.out.println("Exiting..."); + } else { + System.out.println("Invalid option."); + } + + + //DeadCodeStart + if (DEBUG_MODE) { + System.out.println("[DEBUG] Current memory usage: " + Runtime.getRuntime().totalMemory()); + } + //DeadCodeEnd + } + scanner.close(); + } + + private static void printMenu() { + System.out.println("\n1. Create Account"); + System.out.println("2. Deposit"); + System.out.println("3. Withdraw"); + System.out.println("4. Check Balance"); + System.out.println("5. Exit"); + System.out.print("Choose: "); + } + + private static void createAccount(Scanner sc) { + System.out.print("Enter account holder name: "); + String name = sc.nextLine(); + if (accounts.containsKey(name)) { + System.out.println("Account already exists."); + } else { + accounts.put(name, 0.0); + System.out.println("Account created for " + name); + } + } + + private static void deposit(Scanner sc) { + System.out.print("Enter name: "); + String name = sc.nextLine(); + if (accounts.containsKey(name)) { + System.out.print("Amount to deposit: "); + try { + double amount = Double.parseDouble(sc.nextLine()); + if (amount > 0) { + accounts.put(name, accounts.get(name) + amount); + System.out.println("Deposited " + amount); + } else { + System.out.println("Amount must be positive."); + } + } catch (NumberFormatException e) { + System.out.println("Invalid number."); + } + } else { + System.out.println("Account not found."); + } + } + + private static void withdraw(Scanner sc) { + System.out.print("Enter name: "); + String name = sc.nextLine(); + if (accounts.containsKey(name)) { + System.out.print("Amount to withdraw: "); + try { + double amount = Double.parseDouble(sc.nextLine()); + double current = accounts.get(name); + if (amount > 0 && current >= amount) { + accounts.put(name, current - amount); + System.out.println("Withdrawn " + amount); + } else { + System.out.println("Invalid amount or insufficient funds."); + } + } catch (NumberFormatException e) { + System.out.println("Invalid number."); + } + } else { + System.out.println("Account not found."); + } + } + + private static void checkBalance(Scanner sc) { + System.out.print("Enter name: "); + String name = sc.nextLine(); + if (accounts.containsKey(name)) { + System.out.println("Balance: " + accounts.get(name)); + } else { + System.out.println("Account not found."); + } + } + + //DeadCodeStart + private static void transfer(String from, String to, double amount) { + // TODO: Implement transfer logic + System.out.println("Transfer feature coming soon."); + } + //DeadCodeEnd + +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectB.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectB.java new file mode 100644 index 0000000000..260a9a4e59 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectB.java @@ -0,0 +1,159 @@ +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * PROJECT B: The "Dead Code" Variant + * A mock game handler that illustrates extensive dead code, + * deprecated logic, unreachable branches, and hidden plagiarism. + */ +public class LegacyGameHandler { + + private static int score = 0; + + public static void main(String[] args) { + System.out.println("Starting Game Handler..."); + + // Active logic + playLevel(1); + + // Dead Code: Logic gated by a hardcoded false check + //DeadCodeStart + if (false) { + playLevel(99); // God mode - never reachable + unlockAllAchievements(); + startMultiplayer(); // Unreachable call + } + //DeadCodeEnd + + // Dead Code: Variable assigned but never used + //DeadCodeStart + String unusedVersionString = "v2.0.1-beta"; + //DeadCodeEnd + + System.out.println("Game Over. Final Score: " + score); + } + + private static void playLevel(int level) { + System.out.println("Playing Level " + level + "..."); + score += 100; + + // Dead Code: Loop that does nothing effective + // Compiler might optimize this away, but it sits in source + //DeadCodeStart + for (int i = 0; i < 50; i++) { + int unused = i * 2; + } + //DeadCodeEnd + } + + // Dead Code: Missing method from previous version, added here to compile + // but remains unreachable in main. + //DeadCodeStart + private static void unlockAllAchievements() { + System.out.println("ALL UNLOCKED!"); + } + //DeadCodeEnd + + // Dead Code: Entire method is never called in main + //DeadCodeStart + private static void oldRenderingEngine() { + System.out.println("Rendering with OpenGL 1.0..."); + // This was replaced by playLevel but left in the codebase + int x = 10; + int y = 20; + System.out.println("Coords: " + x + "," + y); + } + //DeadCodeEnd + + // Dead Code: Unreachable 'else' because variable is hardcoded + //DeadCodeStart + private static void checkDifficulty() { + int difficulty = 1; // Hardcoded + + if (difficulty == 1) { + System.out.println("Easy mode"); + } else if (difficulty == 2) { + System.out.println("Hard mode"); + } else { + // Dead Code: Impossible branch + System.out.println("Impossible mode"); + } + } + //DeadCodeEnd + + // Dead Code: Method overloading that isn't used + //DeadCodeStart + private static void playLevel(int level, boolean cheatMode) { + if (cheatMode) { + score += 9999; + } + playLevel(level); + } + //DeadCodeEnd + + // Dead Code: Vestigial network code that was never finished + //DeadCodeStart + private static void startMultiplayer() { + try { + // Simulating a connection that never happens + String host = "127.0.0.1"; + if (host == null) { + throw new Exception("No host"); + } + } catch (Exception e) { + // Empty catch block - bad practice and dead logic + } + } + //DeadCodeEnd + + /** + * PLAGIARISM & DEAD CODE COMBO + * This entire inner class is a copy of the BankingSystem logic + * (renamed slightly) intended for an "In-Game Economy" feature + * that was never hooked up to the main game loop. + */ + //DeadCodeStart + private static class InGameEconomy { + // Stolen directly from BankingSystem logic + private static final Map wallets = new HashMap<>(); + + // This method is never called + public void manageWallets() { + Scanner sc = new Scanner(System.in); // Resource leak (never closed) + boolean economyRunning = true; + + // This loop is unreachable because the class is never instantiated + while (economyRunning) { + System.out.println("1. Create Wallet"); + System.out.println("2. Add Gold"); + System.out.println("3. Spend Gold"); + + String cmd = "5"; // Hardcoded to exit immediately if it were run + + switch (cmd) { + case "1": + // Plagiarized 'createAccount' logic + String name = "Player1"; + if (wallets.containsKey(name)) { + System.out.println("Wallet exists."); + } else { + wallets.put(name, 0.0); + } + break; + case "2": + // Plagiarized 'deposit' logic + // Dead code: 'amount' is hardcoded + double amount = 100.0; + if (amount > 0) { + // wallets.put(...) + } + break; + default: + economyRunning = false; + } + } + } + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectC.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectC.java new file mode 100644 index 0000000000..bb0ee8e919 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectC.java @@ -0,0 +1,166 @@ +import java.util.HashMap; +import java.util.Scanner; + +/** + * PROJECT C: The Plagiarist + * Functionally identical to BankingSystem.java (Project A). + * Uses dead code and variable renaming to hide the plagiarism. + */ +public class AssetManager { + + // PLAGIARISM: Same data structure as Project A, just renamed 'accounts' to 'clientAssets' + static HashMap clientAssets = new HashMap<>(); + + public static void main(String[] args) { + Scanner inputProcessor = new Scanner(System.in); + System.out.println("=== Corporate Asset Vault ==="); // Changed string to look different + + int status = 1; + while (status == 1) { // PLAGIARISM: Same loop structure as Project A + showOptions(); + String selection = inputProcessor.nextLine(); + + // DEAD CODE / OBFUSCATION: + // A useless conditional to break the visual flow of the switch statement + //DeadCodeStart + if (1 == 2) { + System.out.println("System failure."); + break; + } + //DeadCodeEnd + + // PLAGIARISM: Logic mapping is identical to BankingSystem (1=create, 2=dep, 3=with, 4=bal) + if (selection.equals("1")) { + registerClient(inputProcessor); + } else if (selection.equals("2")) { + addFunds(inputProcessor); + } else if (selection.equals("3")) { + removeFunds(inputProcessor); + } else if (selection.equals("4")) { + viewAsset(inputProcessor); + } else if (selection.equals("5")) { + status = 0; // Exiting + System.out.println("Shutting down vault..."); + } else { + System.out.println("Unknown command."); + } + } + + // DEAD CODE: Method call that looks important but does nothing + //DeadCodeStart + cleanupMemory(); + //DeadCodeEnd + } + + // PLAGIARISM: Exact copy of 'printMenu' from Project A, just different text + private static void showOptions() { + System.out.println("\n[1] Register Client"); + System.out.println("[2] Add Assets"); + System.out.println("[3] Liquidate Assets"); + System.out.println("[4] Audit Client"); + System.out.println("[5] Quit"); + System.out.print("Action: "); + } + + // PLAGIARISM: 'createAccount' from Project A + private static void registerClient(Scanner s) { + System.out.print("Client ID: "); + String id = s.nextLine(); + + // OBFUSCATION: Wrapping the check in a redundant 'true' block + ///DeadCodeStart + if (true) { + ///DeadCodeEnd + if (clientAssets.containsKey(id)) { + System.out.println("Client ID conflict."); + } else { + clientAssets.put(id, 0.00); + System.out.println("Client registered: " + id); + } + ///DeadCodeStart + } + ///DeadCodeEnd + } + + // PLAGIARISM: 'deposit' from Project A + private static void addFunds(Scanner s) { + System.out.print("Client ID: "); + String id = s.nextLine(); + + // OBFUSCATION: Added a useless loop that runs once to hide the logic nesting + //DeadCodeStart + for (int k = 0; k < 1; k++) { + //DeadCodeEnd + if (clientAssets.containsKey(id)) { + System.out.print("Value to inject: "); + try { + double val = Double.parseDouble(s.nextLine()); + if (val > 0) { + // PLAGIARISM: Core logic stolen + double oldVal = clientAssets.get(id); + clientAssets.put(id, oldVal + val); + System.out.println("Injected: " + val); + } else { + System.out.println("Positive values only."); + } + } catch (Exception e) { + System.out.println("Data error."); + } + } else { + System.out.println("ID not found."); + } + //DeadCodeStart + } + //DeadCodeEnd + } + + // PLAGIARISM: 'withdraw' from Project A + private static void removeFunds(Scanner s) { + System.out.print("Client ID: "); + String id = s.nextLine(); + + if (clientAssets.containsKey(id)) { + System.out.print("Liquidation amount: "); + try { + double val = Double.parseDouble(s.nextLine()); + + // DEAD CODE: Variable that is calculated but never affects the outcome + ///DeadCodeStart + double taxEstimate = val * 0.05; + ///DeadCodeEnd + + double current = clientAssets.get(id); + if (val > 0 && current >= val) { + clientAssets.put(id, current - val); + System.out.println("Liquidated: " + val); + } else { + System.out.println("Insufficient assets or invalid amount."); + } + } catch (Exception e) { + System.out.println("Data error."); + } + } else { + System.out.println("ID not found."); + } + } + + // PLAGIARISM: 'checkBalance' from Project A + private static void viewAsset(Scanner s) { + System.out.print("Client ID: "); + String id = s.nextLine(); + if (clientAssets.containsKey(id)) { + System.out.println("Current Asset Value: " + clientAssets.get(id)); + } else { + System.out.println("ID not found."); + } + } + + // DEAD CODE: A method added purely to make the file size different from Project A + //DeadCodeStart + private static void cleanupMemory() { + int x = 0; + x++; + // Does nothing meaningful + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectD.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectD.java new file mode 100644 index 0000000000..4181f4b235 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectD.java @@ -0,0 +1,73 @@ +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.Scanner; + +/** + * PROJECT D: The Hider + * Functionally, this is a Math Tutor app. + * However, it uses Dead Code to hide a plagiarized version of the BankingSystem. + */ +public class MathTutor { + + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + Random rand = new Random(); + System.out.println("--- Math Tutor 1.0 ---"); + + int correct = 0; + for (int i = 0; i < 3; i++) { + int a = rand.nextInt(10); + int b = rand.nextInt(10); + System.out.print("What is " + a + " + " + b + "? "); + try { + int ans = Integer.parseInt(scanner.nextLine()); + if (ans == (a + b)) { + System.out.println("Correct!"); + correct++; + } else { + System.out.println("Wrong."); + } + } catch (NumberFormatException e) { + System.out.println("Invalid input."); + } + } + System.out.println("Score: " + correct + "/3"); + + // DEAD CODE CALL + // This method is called, but the logic inside is gated by 'if (false)' + // effectively hiding the plagiarized code within the executable. + //DeadCodeStart + executeHiddenLogic(); + //DeadCodeEnd + } + + private static void executeHiddenLogic() { + // DEAD CODE & PLAGIARISM COMBO + // The compiler may ignore this, but it exists in the source code. + // It is a direct copy of BankingSystem.java logic, hidden here. + ///DeadCodeStart + if (false) { + Map hiddenAccounts = new HashMap<>(); + Scanner sc = new Scanner(System.in); + boolean running = true; + + // Stolen Banking Logic + while (running) { + System.out.println("1. Create (Stolen)"); + System.out.println("2. Deposit (Stolen)"); + String cmd = sc.nextLine(); + + if (cmd.equals("1")) { + String name = sc.nextLine(); + hiddenAccounts.put(name, 0.0); + } else if (cmd.equals("2")) { + String name = sc.nextLine(); + double amount = Double.parseDouble(sc.nextLine()); + hiddenAccounts.put(name, hiddenAccounts.get(name) + amount); + } + } + } + ///DeadCodeEnd + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectE.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectE.java new file mode 100644 index 0000000000..3991fc07be --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectE.java @@ -0,0 +1,73 @@ +/** + * PROJECT E: The Structural Copy + * This project plagiarizes the *structure* of LegacyGameHandler.java. + * It maps "Levels" to "Paragraphs" and "Score" to "WordCount". + */ +public class SimpleTextEditor { + + // Plagiarism: 'score' in GameHandler becomes 'totalWordCount' here + private static int totalWordCount = 0; + + public static void main(String[] args) { + System.out.println("Starting Text Editor..."); + + // Plagiarism: 'playLevel(1)' becomes 'processParagraph(1)' + processParagraph(1); + + // Dead Code: Logic gated by hardcoded false check, just like LegacyGameHandler + ///DeadCodeStart + if (false) { + processParagraph(99); + forceAutoSave(); // Unreachable call + } + ///DeadCodeEnd + + System.out.println("Editing Finished. Total Words: " + totalWordCount); + } + + // Plagiarism: The structure of this method is identical to 'playLevel' + private static void processParagraph(int paragraphId) { + System.out.println("Processing Paragraph " + paragraphId + "..."); + totalWordCount += 100; // Arbitrary addition just like the game score + + // Dead Code: Useless loop copied from LegacyGameHandler + //DeadCodeStart + for (int i = 0; i < 50; i++) { + String unused = "char " + i; + } + //DeadCodeEnd + } + + // Dead Code: Method defined but never called (Vestigial feature) + //DeadCodeStart + private static void legacySpellChecker() { + System.out.println("Checking spelling..."); + // This represents the 'oldRenderingEngine' from the GameHandler + int errors = 0; + System.out.println("Errors found: " + errors); + } + //DeadCodeEnd + + // Dead Code: Unreachable 'else' + //DeadCodeStart + private static void checkFontSupport() { + int fontVersion = 1; // Hardcoded + + if (fontVersion == 1) { + System.out.println("Arial Supported"); + } else if (fontVersion == 2) { + System.out.println("Times New Roman Supported"); + } else { + // Dead Code: Impossible branch + System.out.println("Wingdings Supported"); + } + } + //DeadCodeEnd + + // Dead Code: Unreachable hidden feature + //DeadCodeStart + private static void forceAutoSave() { + System.out.println("Saving to cloud..."); + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectF.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectF.java new file mode 100644 index 0000000000..0656291109 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectF.java @@ -0,0 +1,110 @@ +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * PROJECT F: Library Catalog + * A standard system to manage book availability. + */ +public class LibraryCatalog { + + // Map: ISBN -> Is Available (true = in library, false = checked out) + private static final Map catalog = new HashMap<>(); + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + System.out.println("--- City Library System ---"); + + // Seed some data + catalog.put("978-0134685991", true); // Effective Java + catalog.put("978-0132350884", true); // Clean Code + + boolean active = true; + while (active) { + System.out.println("\n1. Borrow Book"); + System.out.println("2. Return Book"); + System.out.println("3. List Available"); + System.out.println("4. Exit"); + System.out.print("Select: "); + + String choice = sc.nextLine(); + + switch (choice) { + case "1": + borrowBook(sc); + break; + case "2": + returnBook(sc); + break; + case "3": + listBooks(); + break; + case "4": + active = false; + break; + case "99": + // Hidden admin menu that is never advertised and practically dead + System.out.println("Admin Mode: Resetting all books..."); + for (String key : catalog.keySet()) { + catalog.put(key, true); + } + break; + default: + System.out.println("Invalid."); + } + } + } + + private static void borrowBook(Scanner sc) { + System.out.print("Enter ISBN: "); + String isbn = sc.nextLine(); + if (catalog.containsKey(isbn)) { + if (catalog.get(isbn)) { + catalog.put(isbn, false); + System.out.println("Book borrowed successfully."); + } else { + System.out.println("Book is currently out."); + } + } else { + System.out.println("Book not found in catalog."); + } + } + + private static void returnBook(Scanner sc) { + System.out.print("Enter ISBN: "); + String isbn = sc.nextLine(); + if (catalog.containsKey(isbn)) { + catalog.put(isbn, true); + System.out.println("Book returned."); + } else { + System.out.println("This book does not belong to our library."); + } + } + + private static void listBooks() { + System.out.println("--- Available Books ---"); + for (Map.Entry entry : catalog.entrySet()) { + if (entry.getValue()) { + System.out.println("ISBN: " + entry.getKey()); + } + } + } + + /** + * Deprecated Search Method. + * Replaced by direct ISBN lookup in v2.0. + * Kept for archival purposes but never called. + */ + //DeadCodeStart + private static void legacySearch(String title) { + System.out.println("Searching legacy database for: " + title); + if (title == null) return; + + // Simulating a slow search + for (int i = 0; i < 1000; i++) { + // busy wait + } + System.out.println("No results found in legacy DB."); + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectG.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectG.java new file mode 100644 index 0000000000..f0f438c1a7 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectG.java @@ -0,0 +1,124 @@ +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * PROJECT G: Inventory Tracker + * PLAGIARISM: Steals logic from LibraryCatalog.java. + * - 'catalog' becomes 'inventory' + * - 'borrowBook' becomes 'checkoutItem' + * - 'returnBook' becomes 'restockItem' + * * DEAD CODE: Contains "Ghost" code - leftovers from the plagiarism + * that were commented out or gated behind false flags. + */ +public class InventoryTracker { + + // PLAGIARISM: Same structure as LibraryCatalog + private static final Map inventory = new HashMap<>(); + + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + System.out.println("--- Warehouse Inventory 1.0 ---"); + + inventory.put("SKU-001", true); + inventory.put("SKU-002", true); + + //DeadCodeStart + // This variable is a leftover from the copy-paste job + // It is defined but never used in the active logic. + boolean libraryIsOpen = true; + if (!libraryIsOpen) { + System.out.println("Library is closed."); // The word "Library" reveals the theft + } + //DeadCodeEnd + + boolean systemRunning = true; + while (systemRunning) { + System.out.println("\n1. Checkout Item"); + System.out.println("2. Restock Item"); + System.out.println("3. List Stock"); + System.out.println("4. Shutdown"); + System.out.print("Command: "); + + String input = scanner.nextLine(); + + // PLAGIARISM: Same switch logic as LibraryCatalog + if (input.equals("1")) { + checkoutItem(scanner); + } else if (input.equals("2")) { + restockItem(scanner); + } else if (input.equals("3")) { + listStock(); + } else if (input.equals("4")) { + systemRunning = false; + } else { + System.out.println("Unknown command."); + } + + //DeadCodeStart + if (false) { + // Vestigial logic from the original file + // attempting to call methods that don't exist in this version + // checkOverdueBooks(); + } + //DeadCodeEnd + } + } + + private static void checkoutItem(Scanner s) { + System.out.print("Enter SKU: "); + String sku = s.nextLine(); + + //DeadCodeStart + // Useless check to obfuscate the stolen logic below + if (sku.equals("MAGIC_STRING")) { + return; + } + //DeadCodeEnd + + if (inventory.containsKey(sku)) { + if (inventory.get(sku)) { + inventory.put(sku, false); + System.out.println("Item checked out."); + } else { + System.out.println("Item out of stock."); + } + } else { + System.out.println("SKU not found."); + } + } + + private static void restockItem(Scanner s) { + System.out.print("Enter SKU: "); + String sku = s.nextLine(); + if (inventory.containsKey(sku)) { + inventory.put(sku, true); + System.out.println("Item restocked."); + } else { + System.out.println("Item not part of inventory."); + } + } + + private static void listStock() { + System.out.println("--- Current Stock ---"); + for (Map.Entry entry : inventory.entrySet()) { + if (entry.getValue()) { + System.out.println("SKU: " + entry.getKey()); + } + } + } + + //DeadCodeStart + /** + * UNUSED METHOD + * This was copied accidentally from the Library project + * but the method body was emptied to hide the evidence. + */ + private static void calculateLateFees() { + // TODO: Remove this method before production + double fee = 0.0; + int daysLate = 5; + fee = daysLate * 0.50; // Logic executed but result is void + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectH.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectH.java new file mode 100644 index 0000000000..b0be7a520c --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectH.java @@ -0,0 +1,101 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * PROJECT H: Smart Home Hub + * Advanced Example: Uses abstract classes and polymorphism. + * Simulates a central hub managing various IOT devices. + */ +public class SmartHomeHub { + + private static final List devices = new ArrayList<>(); + + public static void main(String[] args) { + System.out.println("--- Smart Home Hub v3.0 ---"); + + devices.add(new SmartBulb("Living Room Light")); + devices.add(new SmartThermostat("Hallway AC")); + + simulateDayCycle(); + + //DeadCodeStart + // Legacy voice activation module that was disabled due to privacy concerns + boolean voiceEnabled = false; + if (voiceEnabled) { + listenForCommands(); + } + //DeadCodeEnd + } + + private static void simulateDayCycle() { + Random rand = new Random(); + for (int hour = 0; hour < 24; hour++) { + System.out.println("Hour: " + hour + ":00"); + + // Polymerphism in action + for (SmartDevice device : devices) { + if (rand.nextBoolean()) { + device.toggle(); + } + if (device.isOn) { + device.operate(); + } + } + + //DeadCodeStart + // Unreachable code intended for a demo mode that forces all devices on + if (hour == 99) { + for (SmartDevice d : devices) d.isOn = true; + } + //DeadCodeEnd + } + } + + //DeadCodeStart + private static void listenForCommands() { + System.out.println("Listening for 'Hey Java'..."); + // Logic incomplete + } + //DeadCodeEnd + + // --- Inner Classes for Device Hierarchy --- + + abstract static class SmartDevice { + String id; + boolean isOn = false; + + SmartDevice(String id) { + this.id = id; + } + + void toggle() { + isOn = !isOn; + System.out.println(id + " is now " + (isOn + "OFF")); + } + + abstract void operate(); + } + + static class SmartBulb extends SmartDevice { + SmartBulb(String id) { + super(id); + } + + @Override + void operate() { + System.out.println(" -> " + id + " is illuminating at 800 lumens."); + } + } + + static class SmartThermostat extends SmartDevice { + SmartThermostat(String id) { + super(id); + } + + @Override + void operate() { + System.out.println(" -> " + id + " is regulating temp to 22C."); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectI.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectI.java new file mode 100644 index 0000000000..d701b5bcfa --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectI.java @@ -0,0 +1,133 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * PROJECT I: Factory Floor System + * PLAGIARISM: Steals the OOP structure and logic from SmartHomeHub.java. + * - 'SmartDevice' becomes 'IndustrialUnit' + * - 'SmartBulb' becomes 'HydraulicPress' + * - 'SmartThermostat' becomes 'ConveyorBelt' + * * DEAD CODE: Contains leftover logic from the SmartHome source code + * that was barely scrubbed, hiding in the dead regions. + */ +public class FactoryFloorSystem { + + // PLAGIARISM: Identical List structure to SmartHomeHub + private static final List units = new ArrayList<>(); + + public static void main(String[] args) { + System.out.println("--- Industrial Control System ---"); + + units.add(new HydraulicPress("Press #1")); + units.add(new ConveyorBelt("Belt #4")); + + runShiftSimulation(); + + //DeadCodeStart + // "Ghost" Code: This is the 'voiceEnabled' check from SmartHomeHub + // The variable name was changed, but the logic structure remains. + boolean remoteAccess = false; + if (remoteAccess) { + // "listenForCommands" was renamed but body is empty/dead + connectToCloud(); + } + //DeadCodeEnd + } + + private static void runShiftSimulation() { + Random rand = new Random(); + // PLAGIARISM: Same loop structure (0 to 24) representing hours/shifts + for (int shiftBlock = 0; shiftBlock < 24; shiftBlock++) { + System.out.println("Shift Block: " + shiftBlock); + + for (IndustrialUnit unit : units) { + if (rand.nextBoolean()) { + unit.switchPower(); // Renamed from 'toggle' + } + if (unit.isActive) { // Renamed from 'isOn' + unit.performTask(); // Renamed from 'operate' + } + } + + //DeadCodeStart + // Obvious Plagiarism Leftover: + // The logic checks for 'hour == 99' (from SmartHomeHub) + // even though this loop uses 'shiftBlock'. + if (shiftBlock == 99) { + System.out.println("Demo Mode: Lights On"); // "Lights" reveals the source + } + //DeadCodeEnd + } + } + + //DeadCodeStart + private static void connectToCloud() { + // Unused method + // System.out.println("Listening for 'Hey Java'..."); // Original string commented out + } + //DeadCodeEnd + + // --- Inner Classes (Stolen Hierarchy) --- + + // PLAGIARISM: Direct copy of 'SmartDevice' abstract class + abstract static class IndustrialUnit { + String assetId; + boolean isActive = false; + + IndustrialUnit(String id) { + this.assetId = id; + } + + void switchPower() { + isActive = !isActive; + System.out.println(assetId + " state: " + (isActive + "HALTED")); + } + + abstract void performTask(); + } + + // PLAGIARISM: Direct copy of 'SmartBulb' logic + static class HydraulicPress extends IndustrialUnit { + HydraulicPress(String id) { + super(id); + } + + @Override + void performTask() { + // Logic is identical to bulb illumination, just different text + System.out.println(" -> " + assetId + " is applying 5000psi pressure."); + } + } + + // PLAGIARISM: Direct copy of 'SmartThermostat' logic + static class ConveyorBelt extends IndustrialUnit { + ConveyorBelt(String id) { + super(id); + } + + @Override + void performTask() { + System.out.println(" -> " + assetId + " is moving at 2.5 m/s."); + } + } + + //DeadCodeStart + + /** + * Vestigial class from the stolen project. + * This class 'SmartLock' was copied over but never renamed + * or used in the Factory system. + */ + static class SmartLock extends IndustrialUnit { + SmartLock(String id) { + super(id); + } + + @Override + void performTask() { + System.out.println("Locking doors."); + } + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectJ.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectJ.java new file mode 100644 index 0000000000..d8b8096a6a --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectJ.java @@ -0,0 +1,105 @@ +import java.util.Scanner; + +/** + * PROJECT J: Flight Booking System + * A system to manage seat assignments on a small aircraft. + */ +public class FlightBookingSystem { + + // 5 seats, null = empty, String = passenger name + private static String[] seats = new String[5]; + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + System.out.println("--- SkyHigh Airlines Reservation ---"); + + boolean flying = true; + while (flying) { + System.out.println("\n1. Book Seat"); + System.out.println("2. Cancel Reservation"); + System.out.println("3. View Manifest"); + System.out.println("4. Land (Exit)"); + System.out.print("Option: "); + + String choice = sc.nextLine(); + switch (choice) { + case "1": + bookSeat(sc); + break; + case "2": + cancelSeat(sc); + break; + case "3": + viewManifest(); + break; + case "4": + flying = false; + break; + default: + System.out.println("Invalid."); + } + + //DeadCodeStart + // Safety check that was disabled after beta testing + if (false) { + checkAltimeter(); + } + //DeadCodeEnd + } + } + + private static void bookSeat(Scanner sc) { + System.out.print("Enter Seat # (0-4): "); + try { + int seat = Integer.parseInt(sc.nextLine()); + if (seat >= 0 && seat < seats.length) { + if (seats[seat] == null) { + System.out.print("Passenger Name: "); + seats[seat] = sc.nextLine(); + System.out.println("Boarding Pass Issued."); + } else { + System.out.println("Seat Occupied."); + } + } else { + System.out.println("Invalid Seat."); + } + } catch (NumberFormatException e) { + System.out.println("Error reading input."); + } + } + + private static void cancelSeat(Scanner sc) { + System.out.print("Enter Seat # (0-4): "); + try { + int seat = Integer.parseInt(sc.nextLine()); + if (seat >= 0 && seat < seats.length) { + if (seats[seat] != null) { + System.out.println("Removed " + seats[seat]); + seats[seat] = null; + } else { + System.out.println("Seat is already empty."); + } + } + } catch (NumberFormatException e) { + System.out.println("Error."); + } + } + + private static void viewManifest() { + System.out.println("--- Passenger Manifest ---"); + for (int i = 0; i < seats.length; i++) { + System.out.println("Seat " + i + ": " + seats[i]); + } + } + + //DeadCodeStart + private static void checkAltimeter() { + System.out.println("Altitude: 30,000ft"); + // Dead logic intended to trigger oxygen masks if pressure drops + double pressure = 100.0; + if (pressure < 50) { + System.out.println("DEPLOY MASKS"); + } + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectK.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectK.java new file mode 100644 index 0000000000..60b94a1e0d --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectK.java @@ -0,0 +1,111 @@ +import java.util.Scanner; + +/** + * PROJECT K: Cinema Ticket Booth + * PLAGIARISM: Steals logic from FlightBookingSystem. + * - 'seats' array is structurally identical. + * - 'bookSeat' becomes 'sellTicket'. + * - 'cancelSeat' becomes 'refundTicket'. + * * DEAD CODE: Contains "Ghost" aviation logic that wasn't fully cleaned up. + */ +public class CinemaTicketBooth { + + // PLAGIARISM: Identical array structure (size 5) used for the movie theater row + private static String[] armchairs = new String[5]; + + public static void main(String[] args) { + Scanner input = new Scanner(System.in); + System.out.println("--- Grand Cinema Box Office ---"); + + boolean showRunning = true; + while (showRunning) { + System.out.println("\n1. Sell Ticket"); + System.out.println("2. Refund Ticket"); + System.out.println("3. View Audience"); + System.out.println("4. Close Booth"); + System.out.print("Selection: "); + + String cmd = input.nextLine(); + + // PLAGIARISM: Identical Switch-Case logic flow + if (cmd.equals("1")) { + sellTicket(input); + } else if (cmd.equals("2")) { + refundTicket(input); + } else if (cmd.equals("3")) { + viewAudience(); + } else if (cmd.equals("4")) { + showRunning = false; + } else { + System.out.println("Unknown."); + } + + //DeadCodeStart + // PLAGIARISM GHOST: + // This is the 'checkAltimeter' logic from the flight system. + // It is unreachable (gated by false), but the strings reveal the theft. + if (false) { + System.out.println("Altitude: 0ft (Ground Level)"); + // Why would a cinema have oxygen masks? + // Because this code was stolen from an airplane system. + System.out.println("DEPLOY OXYGEN MASKS"); + } + //DeadCodeEnd + } + } + + // PLAGIARISM: 'bookSeat' logic copied exactly + private static void sellTicket(Scanner s) { + System.out.print("Chair # (0-4): "); + try { + int chair = Integer.parseInt(s.nextLine()); + if (chair >= 0 && chair < armchairs.length) { + if (armchairs[chair] == null) { + System.out.print("Viewer Name: "); + armchairs[chair] = s.nextLine(); + System.out.println("Ticket Printed."); + } else { + System.out.println("Chair Taken."); + } + } else { + System.out.println("Invalid Chair."); + } + } catch (NumberFormatException e) { + System.out.println("Input Error."); + } + } + + // PLAGIARISM: 'cancelSeat' logic copied exactly + private static void refundTicket(Scanner s) { + System.out.print("Chair # (0-4): "); + try { + int chair = Integer.parseInt(s.nextLine()); + //DeadCodeStart + // Useless variable check left over from flight logic + // "turbulence" has no meaning in a cinema + boolean turbulence = false; + if (turbulence) return; + //DeadCodeEnd + + if (chair >= 0 && chair < armchairs.length) { + if (armchairs[chair] != null) { + System.out.println("Refunded " + armchairs[chair]); + armchairs[chair] = null; + } else { + System.out.println("Chair is already free."); + } + } + } catch (NumberFormatException e) { + System.out.println("Error."); + } + } + + // PLAGIARISM: 'viewManifest' logic copied exactly + private static void viewAudience() { + System.out.println("--- Current Audience ---"); + for (int i = 0; i < armchairs.length; i++) { + // Logic: (val == null ? empty : val) is identical to source + System.out.println("Chair " + i + ": " + armchairs[i]); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectL.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectL.java new file mode 100644 index 0000000000..f97c1438fc --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectL.java @@ -0,0 +1,115 @@ +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Random; +import java.util.stream.Collectors; + +/** + * PROJECT L: Stock Market Engine + * COMPLEXITY: Uses PriorityQueues for order matching, + * Java Streams for analytics, and internal static classes. + */ +public class StockMarketEngine { + + private static final String[] TICKERS = {"AAPL", "GOOG", "TSLA", "AMZN"}; + private static final Random rand = new Random(); + + // Order Books: Buyers want low prices (max heap not needed, just high bids first), + // Sellers want high prices (min heap for lowest ask). + // Actually standard matching: + // Buy Orders: Sort Descending (Highest bid gets priority) + // Sell Orders: Sort Ascending (Lowest ask gets priority) + private static PriorityQueue buySide = new PriorityQueue<>(Comparator.comparingDouble(Order::getPrice).reversed()); + private static PriorityQueue sellSide = new PriorityQueue<>(Comparator.comparingDouble(Order::getPrice)); + + public static void main(String[] args) { + System.out.println("--- NYSE Trading System v9.2 ---"); + + // Simulate trading day + for (int i = 0; i < 100; i++) { + generateRandomOrder(); + matchOrders(); + } + + generateReport(); + + //DeadCodeStart + // Old circuit breaker logic disabled for high-volatility testing + boolean panicMode = false; + if (panicMode) { + haltTrading("MARKET CRASH DETECTED"); + flushAllOrders(); + } + //DeadCodeEnd + } + + private static void generateRandomOrder() { + String ticker = TICKERS[rand.nextInt(TICKERS.length)]; + double price = 100 + (rand.nextDouble() * 50); + boolean isBuy = rand.nextBoolean(); + + Order o = new Order(ticker, price, isBuy); + if (isBuy) buySide.add(o); + else sellSide.add(o); + } + + private static void matchOrders() { + while (!buySide.isEmpty() && !sellSide.isEmpty()) { + Order topBuy = buySide.peek(); + Order topSell = sellSide.peek(); + + // Spread check: Trade happens if Bid >= Ask + if (topBuy.price >= topSell.price) { + // Execute Trade + buySide.poll(); + sellSide.poll(); + System.out.println(String.format("TRADE EXECUTED: %s @ %.2f", topBuy.ticker, topSell.price)); + } else { + break; // No more matching prices + } + } + } + + private static void generateReport() { + System.out.println("\n--- EOD Analytics ---"); + List openOrders = new ArrayList<>(buySide); + openOrders.addAll(sellSide); + + // Advanced Stream Usage + Map volumeByTicker = openOrders.stream() + .collect(Collectors.groupingBy(o -> o.ticker, Collectors.counting())); + + volumeByTicker.forEach((k, v) -> System.out.println(k + " Open Orders: " + v)); + } + + //DeadCodeStart + private static void haltTrading(String reason) { + System.err.println("HALT: " + reason); + // This logic is dead because panicMode is hardcoded to false + buySide.clear(); + sellSide.clear(); + } + + private static void flushAllOrders() { + // Recursive method that was never implemented correctly + // flushAllOrders(); // Would cause StackOverflow + } + //DeadCodeEnd + + // --- Inner Classes --- + + static class Order { + String ticker; + double price; + boolean isBuy; + + Order(String t, double p, boolean b) { + this.ticker = t; + this.price = p; + this.isBuy = b; + } + + public double getPrice() { return price; } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectM.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectM.java new file mode 100644 index 0000000000..7c1b26d523 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectM.java @@ -0,0 +1,119 @@ +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.Map; + +/** + * PROJECT M: Auction House Server + * PLAGIARISM: Steals the PriorityQueue matching algorithm from StockMarketEngine. + * - 'Order' becomes 'BidRequest' + * - 'buySide' becomes 'bidQueue' + * - 'sellSide' becomes 'listingQueue' + * * DEAD CODE: Contains logic for "Market Crashes" inside an antique auction app. + */ +public class AuctionHouseServer { + + private static final String[] CATEGORIES = {"Painting", "Vase", "Statue", "Coin"}; + private static final Random rng = new Random(); + + // PLAGIARISM: Identical PriorityQueue Logic + // Bidders want to pay money (sorted Descending to find highest bidder) + // Sellers want to sell items (sorted Ascending to find lowest reserve price) + private static PriorityQueue bidQueue = new PriorityQueue<>(Comparator.comparingDouble(BidRequest::getAmount).reversed()); + private static PriorityQueue listingQueue = new PriorityQueue<>(Comparator.comparingDouble(BidRequest::getAmount)); + + public static void main(String[] args) { + System.out.println("--- Sotheby's Digital Auction v2.0 ---"); + + // Simulate auction rounds + for (int i = 0; i < 100; i++) { + createTraffic(); + resolveAuctions(); + } + + printManifest(); + + //DeadCodeStart + // "Ghost" Code: This is the 'haltTrading' logic from StockMarketEngine. + // It refers to "SEC Regulations" which makes no sense for an art auction. + boolean regulatoryFreeze = false; + if (regulatoryFreeze) { + System.err.println("SEC HALT: Insider Trading Detected"); + bidQueue.clear(); + } + //DeadCodeEnd + } + + private static void createTraffic() { + String cat = CATEGORIES[rng.nextInt(CATEGORIES.length)]; + double val = 100 + (rng.nextDouble() * 50); + boolean isBid = rng.nextBoolean(); + + BidRequest req = new BidRequest(cat, val, isBid); + if (isBid) bidQueue.add(req); + else listingQueue.add(req); + } + + // PLAGIARISM: This is 'matchOrders' renamed + private static void resolveAuctions() { + while (!bidQueue.isEmpty() && !listingQueue.isEmpty()) { + BidRequest highestBidder = bidQueue.peek(); + BidRequest lowestSeller = listingQueue.peek(); + + // Spread check: Deal happens if Bid >= Reserve + if (highestBidder.amount >= lowestSeller.amount) { + // Execute Sale + bidQueue.poll(); + listingQueue.poll(); + System.out.println(String.format("SOLD: %s for $%.2f", highestBidder.itemType, lowestSeller.amount)); + } else { + break; + } + } + } + + private static void printManifest() { + System.out.println("\n--- Unsold Inventory ---"); + List unmatched = new ArrayList<>(bidQueue); + unmatched.addAll(listingQueue); + + // PLAGIARISM: Identical Stream usage + Map countByType = unmatched.stream() + .collect(Collectors.groupingBy(o -> o.itemType, Collectors.counting())); + + countByType.forEach((k, v) -> System.out.println(k + " Pending: " + v)); + } + + //DeadCodeStart + /** + * UNUSED ALGORITHM + * This method contains variable names 'ticker' and 'stockSplit' + * which were forgotten during the copy-paste process. + */ + private static void calculateDividends(double stockSplit) { + String ticker = "AAPL"; // Why is Apple stock in an antique auction? + double dividend = stockSplit * 0.05; + // Logic ends here + } + //DeadCodeEnd + + // --- Inner Classes --- + + // PLAGIARISM: 'Order' renamed to 'BidRequest' + static class BidRequest { + String itemType; // Was 'ticker' + double amount; // Was 'price' + boolean isBid; // Was 'isBuy' + + BidRequest(String t, double p, boolean b) { + this.itemType = t; + this.amount = p; + this.isBid = b; + } + + public double getAmount() { return amount; } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectN.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectN.java new file mode 100644 index 0000000000..c444b778d8 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectN.java @@ -0,0 +1,106 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * PROJECT N: RPG Battle System + * COMPLEXITY: Uses Interfaces, Abstract Classes, and Polymorphism. + */ +public class RPGBattleSystem { + + public static void main(String[] args) { + System.out.println("--- Dungeon Brawl v1.0 ---"); + + List arena = new ArrayList<>(); + arena.add(new Paladin("Sir Java", 100)); + arena.add(new Necromancer("Lord Null", 80)); + + // Simulate rounds + for (int i = 1; i <= 3; i++) { + System.out.println("\nRound " + i); + for (Combatant c : arena) { + if (c.isAlive()) { + c.performAction(); + } + } + } + + //DeadCodeStart + // Old loot drop table that was deprecated in favor of JSON configs + boolean dropLoot = false; + if (dropLoot) { + String[] loot = {"Sword", "Shield", "Potion"}; + System.out.println("Dropped: " + loot[0]); + } + //DeadCodeEnd + } + + // --- Interfaces & Hierarchy --- + + interface Combatant { + void performAction(); + + boolean isAlive(); + + //DeadCodeStart + void takeDamage(int amount); + //DeadCodeEnd + } + + abstract static class Character implements Combatant { + String name; + int hp; + Random rng = new Random(); + + Character(String n, int h) { + this.name = n; + this.hp = h; + } + + public boolean isAlive() { + return hp > 0; + } + + //DeadCodeStart + public void takeDamage(int amount) { + this.hp -= amount; + System.out.println(name + " takes " + amount + " damage. HP: " + hp); + } + //DeadCodeEnd + } + + static class Paladin extends Character { + Paladin(String n, int h) { + super(n, h); + System.out.println("Test"); + } + + @Override + public void performAction() { + // Paladin logic: High defense, physical attack + int dmg = rng.nextInt(15) + 5; + System.out.println(name + " swings Warhammer for " + dmg); + + //DeadCodeStart + // Unused 'Holy Light' ability logic + if (false) { + this.hp += 50; + System.out.println("Healed by Holy Light!"); + } + //DeadCodeEnd + } + } + + static class Necromancer extends Character { + Necromancer(String n, int h) { + super(n, h); + } + + @Override + public void performAction() { + // Necromancer logic: Magic attack + int dmg = rng.nextInt(25); + System.out.println(name + " casts Shadow Bolt for " + dmg); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectO.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectO.java new file mode 100644 index 0000000000..baccc22bb2 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectO.java @@ -0,0 +1,110 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * PROJECT O: Corporate Payroll + * PLAGIARISM: Steals the interface/abstract class structure from RPGBattleSystem. + * - 'Combatant' becomes 'Payable' + * - 'Character' becomes 'Employee' + * - 'Paladin' becomes 'Executive' (High Salary) + * - 'Necromancer' becomes 'Contractor' (Variable Pay) + * * DEAD CODE: Checks for "Mana" and "HP" inside the payroll logic. + */ +public class CorporatePayroll { + + public static void main(String[] args) { + System.out.println("--- HR Payroll System 2024 ---"); + + // PLAGIARISM: Same List structure + List staff = new ArrayList<>(); + staff.add(new Executive("CEO Smith", 100000)); + staff.add(new Contractor("Dev Jones", 50)); // Hourly rate + + // Simulate pay cycles (was 'Rounds') + for (int i = 1; i <= 3; i++) { + System.out.println("\nMonth " + i); + for (Payable p : staff) { + if (p.isActive()) { // Was 'isAlive' + p.processPayment(); // Was 'performAction' + } + } + } + + //DeadCodeStart + // PLAGIARISM GHOST: + // This is the 'dropLoot' logic from the RPG. + // Why would a payroll system have a "Sword" or "Shield"? + boolean bonusDrop = false; + if (bonusDrop) { + String[] items = {"Sword of Layoffs", "Shield of Tax Evasion"}; + System.out.println("Looted: " + items[0]); + } + //DeadCodeEnd + } + + // --- Interfaces & Hierarchy (Stolen) --- + + interface Payable { + void processPayment(); // Was 'performAction' + boolean isActive(); // Was 'isAlive' + void deductTax(int amount); // Was 'takeDamage' + } + + // PLAGIARISM: Direct copy of 'Character' abstract class + abstract static class Employee implements Payable { + String name; + int balance; // Was 'hp' + Random rng = new Random(); + + Employee(String n, int b) { this.name = n; this.balance = b; } + + public boolean isActive() { return balance > 0; } // Logic is identical + + public void deductTax(int amount) { + this.balance -= amount; + System.out.println(name + " pays " + amount + " tax. Balance: " + balance); + } + } + + // PLAGIARISM: 'Paladin' Logic (Steady, high numbers) + static class Executive extends Employee { + Executive(String n, int b) { super(n, b); } + + @Override + public void processPayment() { + // Executive logic: High Bonus + int pay = rng.nextInt(1500) + 5000; + System.out.println(name + " authorizes bonus of $" + pay); + + //DeadCodeStart + // GHOST CODE: The 'Holy Light' healing logic + // Checks for a 'mana' variable that doesn't exist in this scope anymore + if (false) { + // System.out.println("Casting Healing Spell..."); + // this.mana -= 10; + } + //DeadCodeEnd + } + } + + // PLAGIARISM: 'Necromancer' Logic (High variance) + static class Contractor extends Employee { + Contractor(String n, int b) { super(n, b); } + + @Override + public void processPayment() { + // Contractor logic: Variable hours + int pay = rng.nextInt(2500); + System.out.println(name + " bills hours for $" + pay); + + //DeadCodeStart + // Vestigial logic checking for magic resistance + int magicResist = 0; + if (magicResist > 10) { + System.out.println("Spell resisted!"); + } + //DeadCodeEnd + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectP.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectP.java new file mode 100644 index 0000000000..7f20d74aee --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectP.java @@ -0,0 +1,103 @@ +import java.util.Random; + +/** + * PROJECT P: BioLife Simulation + * A standard implementation of Conway's Game of Life. + * Uses a 2D grid and standard nested loops. + */ +public class BioLifeSim { + + private static final int ROWS = 10; + private static final int COLS = 10; + private static boolean[][] grid = new boolean[ROWS][COLS]; + + public static void main(String[] args) { + System.out.println("--- Micro-Organism Lab v1.4 ---"); + initializeCulture(); + + // Simulate 3 Generations + for (int gen = 0; gen < 3; gen++) { + System.out.println("Generation: " + gen); + displayGrid(); + evolve(); + } + + //DeadCodeStart + // Deprecated 3D rendering pipeline + // This code is unreachable but was part of the original engine + if (false) { + int zAxis = 5; + System.out.println("Rendering Volumetric Cube at Z=" + zAxis); + // Simulated heavy calculation + double vertex = Math.pow(zAxis, 3); + } + //DeadCodeEnd + } + + private static void initializeCulture() { + Random rng = new Random(); + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + grid[r][c] = rng.nextBoolean(); + } + } + } + + private static void evolve() { + boolean[][] nextGen = new boolean[ROWS][COLS]; + + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + int neighbors = countNeighbors(r, c); + + if (grid[r][c]) { + // Rule 1: Underpopulation (<2 dies) + // Rule 2: Overcrowding (>3 dies) + // Rule 3: Survival (2 or 3 lives) + if (neighbors < 2 || neighbors > 3) { + nextGen[r][c] = false; + } else { + nextGen[r][c] = true; + } + } else { + // Rule 4: Reproduction (3 becomes alive) + if (neighbors == 3) { + nextGen[r][c] = true; + } + } + } + } + grid = nextGen; + } + + private static int countNeighbors(int row, int col) { + int count = 0; + // Check 3x3 grid around cell + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + if (i == 0 && j == 0) { + //continue + } else { + int r = row + i; + int c = col + j; + + // Boundary checks + if (r >= 0 && r < ROWS && c >= 0 && c < COLS) { + if (grid[r][c]) count++; + } + } + } + } + return count; + } + + private static void displayGrid() { + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + System.out.print(grid[r][c] + ". "); + } + System.out.println(); + } + System.out.println(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectQ.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectQ.java new file mode 100644 index 0000000000..9fe924164a --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectQ.java @@ -0,0 +1,126 @@ +import java.util.Random; + +/** + * PROJECT Q: Seat Reservation System + * PLAGIARISM: Steals the 'Game of Life' logic from BioLifeSim. + * OBFUSCATION: + * 1. Flattens the 2D grid into a 1D array to hide loop structure. + * 2. Renames "Neighbors" to "SocialProximity". + * 3. Justifies the 23/3 survival rules as "Corporate Booking Policies". + * DEAD CODE: Contains simulation-specific terms (FPS, Toroidal Wrap) + * that have no place in a booking system. + */ +public class SeatReservationSystem { + + private static final int WIDTH = 10; + private static final int HEIGHT = 10; + // OBFUSCATION: Using 1D array instead of 2D to look different + private static boolean[] seatMap = new boolean[WIDTH * HEIGHT]; + + public static void main(String[] args) { + System.out.println("--- Smart Cinema Booking Logic ---"); + randomizeBookings(); + + // Simulate "Fiscal Quarters" (Generations) + for (int q = 0; q < 3; q++) { + System.out.println("Quarter Audit: " + q); + printLayout(); + optimizeOccupancy(); + } + + //DeadCodeStart + // EVIDENCE OF THEFT: + // A variable named 'targetFPS' (Frames Per Second) is meaningless + // for a seat booking system. This is leftover from the simulation code. + int targetFPS = 60; + if (targetFPS < 30) { + System.out.println("Lag detected."); + } + //DeadCodeEnd + } + + private static void randomizeBookings() { + Random rng = new Random(); + for (int i = 0; i < seatMap.length; i++) { + seatMap[i] = rng.nextBoolean(); + } + } + + private static void optimizeOccupancy() { + boolean[] nextMap = new boolean[seatMap.length]; + + // OBFUSCATION: Single loop iterating over 1D array + for (int i = 0; i < seatMap.length; i++) { + int density = checkSocialDistancing(i); + + if (seatMap[i]) { + // PLAGIARISM: The logic is exactly Game of Life ( <2 or >3 cancels) + // But comments claim it's "Profit/Safety" rules. + + if (density < 2) { + // "Under-utilized seat. Cancel to save cleaning costs." + nextMap[i] = false; + } else if (density > 3) { + // "Fire Hazard violation. Too many people nearby. Cancel." + nextMap[i] = false; + } else { + // "Optimal crowd density. Keep reservation." + nextMap[i] = true; + } + } else { + // PLAGIARISM: Reproduction rule (==3 creates life) + // "High demand area. Auto-book empty seat." + if (density == 3) { + nextMap[i] = true; + } + } + } + seatMap = nextMap; + } + + // OBFUSCATION: Calculating 2D neighbors from a 1D index + private static int checkSocialDistancing(int index) { + int interactions = 0; + int row = index / WIDTH; + int col = index % WIDTH; + + // Same 3x3 check, but math is adapted for 1D array + for (int yOff = -1; yOff <= 1; yOff++) { + for (int xOff = -1; xOff <= 1; xOff++) { + if (yOff == 0 && xOff == 0) { + //continue; + } else { + int checkR = row + yOff; + int checkC = col + xOff; + + //DeadCodeStart + // EVIDENCE OF THEFT: + // "Toroidal Wrap" means connecting the left edge to the right edge. + // Cinemas don't wrap around. This is Game of Life topology logic. + boolean enableToroidalWrap = false; + if (enableToroidalWrap) { + if (checkC < 0) checkC = WIDTH - 1; + if (checkC >= WIDTH) checkC = 0; + } + //DeadCodeEnd + + if (checkR >= 0 && checkR < HEIGHT && checkC >= 0 && checkC < WIDTH) { + // Map 2D back to 1D index + if (seatMap[checkR * WIDTH + checkC]) { + interactions++; + } + } + } + } + } + return interactions; + } + + private static void printLayout() { + for (int i = 0; i < seatMap.length; i++) { + System.out.print(seatMap[i] + "[ ]"); + if ((i + 1) % WIDTH == 0) System.out.println(); + } + System.out.println(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectR.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectR.java new file mode 100644 index 0000000000..ea87445463 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectR.java @@ -0,0 +1,94 @@ +import java.util.ArrayList; +import java.util.List; + +/** + * PROJECT R: Elevator Controller + * CONCEPT: Event-Driven Programming with Interfaces. + * Simulates a physical elevator moving between floors. + */ +public class ElevatorController { + + private int currentFloor = 0; + private boolean doorsOpen = false; + private List listeners = new ArrayList<>(); + + public static void main(String[] args) { + System.out.println("--- Building A: Main Lift ---"); + ElevatorController lift = new ElevatorController(); + + // Register an anonymous listener for events + lift.addListener(new ElevatorListener() { + @Override + public void onFloorChanged(int newFloor) { + System.out.println("[Display] " + newFloor); + } + + @Override + public void onDoors(boolean open) { + System.out.println("[Mechanism] Doors " + open); + } + }); + + lift.call(5); + lift.call(2); + + //DeadCodeStart + // Legacy Fire Service Mode + // This logic overrides all calls and sends lift to ground + boolean fireAlarm = false; + if (fireAlarm) { + System.out.println("FIRE DETECTED. DESCENDING."); + lift.currentFloor = 0; + } + //DeadCodeEnd + } + + public void addListener(ElevatorListener l) { + listeners.add(l); + } + + public void call(int targetFloor) { + if (doorsOpen) closeDoors(); + + System.out.println("Moving to " + targetFloor + "..."); + // Simulate movement + while (currentFloor != targetFloor) { + if (currentFloor < targetFloor) currentFloor++; + else currentFloor--; + + notifyFloors(); + } + openDoors(); + } + + private void openDoors() { + this.doorsOpen = true; + for (ElevatorListener l : listeners) l.onDoors(true); + } + + private void closeDoors() { + //DeadCodeStart + // Safety Sensor Logic + // Checks if an object is blocking the door + int obstructionSensorVal = 0; + if (obstructionSensorVal > 5) { + System.out.println("Object detected. Re-opening."); + return; + } + //DeadCodeEnd + + this.doorsOpen = false; + for (ElevatorListener l : listeners) l.onDoors(false); + } + + private void notifyFloors() { + for (ElevatorListener l : listeners) l.onFloorChanged(currentFloor); + } + + // --- Inner Interface --- + interface ElevatorListener { + void onFloorChanged(int newFloor); + + void onDoors(boolean open); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectS.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectS.java new file mode 100644 index 0000000000..d7d2488f8e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectS.java @@ -0,0 +1,106 @@ +import java.util.ArrayList; +import java.util.List; + +/** + * PROJECT S: Podcast Streamer + * PLAGIARISM: Steals the Event-Driven logic from ElevatorController. + * - 'ElevatorListener' becomes 'PlaybackListener' + * - 'call(floor)' becomes 'skipTo(episode)' + * - 'doorsOpen' becomes 'isBuffering' + * * DEAD CODE: Contains hardware-specific checks (Door Obstruction, Fire Alarm) + * inside a software media player. + */ +public class PodcastStreamer { + + private int currentEpisode = 0; + // PLAGIARISM: 'doorsOpen' logic mapped to 'isBuffering' + // Logic: You can't move while doors are open -> You can't play while buffering + private boolean isBuffering = false; + + // PLAGIARISM: Identical Listener list structure + private List subscribers = new ArrayList<>(); + + public static void main(String[] args) { + System.out.println("--- AudioFly Player v2.1 ---"); + PodcastStreamer player = new PodcastStreamer(); + + // PLAGIARISM: Anonymous inner class usage identical to Project R + player.subscribe(new PlaybackListener() { + @Override + public void onTrackChange(int trackId) { + System.out.println("Now Playing: Episode " + trackId); + } + + @Override + public void onBufferState(boolean buffering) { + System.out.println("Status: " + buffering); + } + }); + + player.skipTo(5); + player.skipTo(2); + + //DeadCodeStart + // EVIDENCE OF THEFT: + // Why would a podcast player check for a Fire Alarm? + // Because it was copied from the ElevatorController. + boolean emergencyOverride = false; + if (emergencyOverride) { + System.out.println("EMERGENCY DESCENT"); // Dead giveaway term "Descent" + player.currentEpisode = 0; + } + //DeadCodeEnd + } + + public void subscribe(PlaybackListener l) { + subscribers.add(l); + } + + public void skipTo(int targetEpisode) { + if (isBuffering) stopBuffering(); + + System.out.println("Skipping to " + targetEpisode + "..."); + + // PLAGIARISM: Identical 'while' loop movement logic + // Elevators move 1 floor at a time; Podcasts don't usually 'scroll' tracks like this + while (currentEpisode != targetEpisode) { + if (currentEpisode < targetEpisode) currentEpisode++; + else currentEpisode--; + + notifyTracks(); + } + startBuffering(); + } + + private void startBuffering() { + this.isBuffering = true; + for (PlaybackListener l : subscribers) l.onBufferState(true); + } + + private void stopBuffering() { + //DeadCodeStart + // EVIDENCE OF THEFT: + // This is the 'closeDoors' safety check from the Elevator. + // It checks for "Physical Obstruction" in a digital buffer. + int opticalSensor = 0; + if (opticalSensor > 5) { + System.out.println("Door blocked."); // "Door" in a podcast app? + return; + } + //DeadCodeEnd + + this.isBuffering = false; + for (PlaybackListener l : subscribers) l.onBufferState(false); + } + + private void notifyTracks() { + for (PlaybackListener l : subscribers) l.onTrackChange(currentEpisode); + } + + // --- Inner Interface --- + interface PlaybackListener { + void onTrackChange(int trackId); // Was 'onFloorChanged' + + void onBufferState(boolean buffering); // Was 'onDoors' + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectT.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectT.java new file mode 100644 index 0000000000..392aa16ed8 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectT.java @@ -0,0 +1,88 @@ +import java.util.ArrayList; +import java.util.List; + +/** + * PROJECT T: Secure Data Vault + * CONCEPTS: Generics with Bounded Types, Custom Exceptions. + * A system for storing sensitive data objects safely. + */ +public class SecureDataVault { + + public static void main(String[] args) { + System.out.println("--- DoD Secure Storage v4.0 ---"); + + try { + // Create a Vault for TopSecret documents + Vault documentVault = new Vault<>("1234"); + documentVault.store(new TopSecretDoc("NuclearLaunchCodes.txt")); + + // Attempt retrieval + TopSecretDoc doc = documentVault.retrieve("1234"); + System.out.println("Retrieved: " + doc.getContent()); + + //DeadCodeStart + // Deprecated Biometric Scanner + // This logic was bypassed in v4.0 but remains in the codebase + boolean retinaScannerActive = false; + if (retinaScannerActive) { + if (!scanRetina()) { + throw new AccessDeniedException("Retina mismatch"); + } + } + //DeadCodeEnd + + } catch (AccessDeniedException e) { + System.err.println("SECURITY ALERT: " + e.getMessage()); + } + } + + //DeadCodeStart + private static boolean scanRetina() { + // Stub for hardware interaction + return true; + } + //DeadCodeEnd + + // --- Generics & Inner Classes --- + + // Bounded Type Parameter: T must implement Securable + static class Vault { + private T item; + private String pinCode; + private boolean locked = true; + + Vault(String pin) { + this.pinCode = pin; + } + + void store(T item) { + this.item = item; + this.locked = true; + System.out.println("Item secured. Vault locked."); + } + + T retrieve(String enteredPin) throws AccessDeniedException { + if (!this.pinCode.equals(enteredPin)) { + throw new AccessDeniedException("Invalid PIN"); + } + this.locked = false; + return item; + } + } + + interface Securable { + String getContent(); + } + + static class TopSecretDoc implements Securable { + private String filename; + TopSecretDoc(String f) { this.filename = f; } + + @Override + public String getContent() { return "Content of " + filename; } + } + + static class AccessDeniedException extends Exception { + AccessDeniedException(String msg) { super(msg); } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectU.java b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectU.java new file mode 100644 index 0000000000..fe45e22cc3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectU.java @@ -0,0 +1,99 @@ +import java.util.ArrayList; +import java.util.List; + +/** + * PROJECT U: Cookie Jar + * PLAGIARISM: Steals the Generic Vault logic from SecureDataVault. + * - 'Vault' becomes 'Container' + * - 'Securable' becomes 'Edible' + * - 'AccessDeniedException' becomes 'HandSlapException' + * * DEAD CODE: Contains high-security checks (Retina Scans, Encryption) + * inside a simple cookie container. + */ +public class CookieJar { + + public static void main(String[] args) { + System.out.println("--- Grandma's Kitchen Helper ---"); + + try { + // PLAGIARISM: Identical Generic usage + Container jar = new Container<>("sugar"); + jar.putIn(new Biscuit("ChocChip")); + + // Attempt to eat + Biscuit b = jar.takeOut("sugar"); + System.out.println("Yum: " + b.getFlavor()); + + //DeadCodeStart + // EVIDENCE OF THEFT: + // This is the Biometric Scanner logic from the Vault. + // Why would a cookie jar scan your retina? + boolean eyeScanner = false; + if (eyeScanner) { + // The method name 'scanRetina' was lazily renamed to 'checkHunger' + // but the logic still throws a security exception. + if (!checkHunger()) { + throw new HandSlapException("Retina mismatch"); // "Retina" left in string + } + } + //DeadCodeEnd + + } catch (HandSlapException e) { + // "SECURITY ALERT" was changed to "BAD BOY" + System.err.println("BAD BOY: " + e.getMessage()); + } + } + + //DeadCodeStart + private static boolean checkHunger() { + // Dead logic + return true; + } + //DeadCodeEnd + + // --- Generics & Inner Classes (Stolen) --- + + // PLAGIARISM: 'Vault' renamed to 'Container' + // 'Securable' renamed to 'Edible' + static class Container { + private T snack; + private String secretWord; // Was 'pinCode' + private boolean closed = true; // Was 'locked' + + Container(String word) { + this.secretWord = word; + } + + void putIn(T snack) { + this.snack = snack; + this.closed = true; + System.out.println("Snack saved. Lid closed."); + } + + T takeOut(String spokenWord) throws HandSlapException { + if (!this.secretWord.equals(spokenWord)) { + throw new HandSlapException("Wrong secret word"); + } + this.closed = false; + return snack; + } + } + + // PLAGIARISM: 'Securable' interface copied + interface Edible { + String getFlavor(); // Was 'getContent' + } + + static class Biscuit implements Edible { + private String type; + Biscuit(String t) { this.type = t; } + + @Override + public String getFlavor() { return "Taste of " + type; } + } + + // PLAGIARISM: Custom Exception copied + static class HandSlapException extends Exception { + HandSlapException(String msg) { super(msg); } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/source.txt b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/source.txt new file mode 100644 index 0000000000..63ac130108 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/source.txt @@ -0,0 +1,33 @@ +All Code in this directory is generated with Google Gemini 3 Pro. + +Projects A-U where generated in the same chat. +Initial Prompt: " write a java example project that must contain: + + - a Main class with a main method as a starting point + + - sometimes dead code in all variants + + - sometimes one project should plagiarize from another + + - plagiarisms and dead code can be combined, and dead code can be used to hide plagiarisms " + + +Projects 1-8 where generated in the same chat. +Initial Prompt: +"Write two or more Java example projects that must contain + + - a Main class with a main method as a starting point + + - Sometimes dead code in all variants (but mostly hard to spot dead code) + + - Sometimes one project should plagiarize from another one. + + - Plagiarism and dead code can be combined, and dead code can be used to hide plagiarism. + +- one project == one file + +- Plagiarism should be hard to spot. + +- the code has to compile + +- put //DeadCodeStart and //DeadCodeEnd around all dead code lines" diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/GridOverseer.java b/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/GridOverseer.java new file mode 100644 index 0000000000..01c04d76f8 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/GridOverseer.java @@ -0,0 +1,105 @@ +import java.util.Random; +import java.util.Vector; + +/** + * GridOverseer + * Manages infrastructure components. + * * CHANGE LOG: + * - Migrated to Vector for thread safety (simulated). + * - Encapsulated state logic within components. + */ +public class GridOverseer { + + // Technique: Changed data structure from List/LinkedList to Vector + private static final Vector infrastructure = new Vector<>(); + private static final Random entropySource = new Random(); + + // Technique: Changed method signature to varargs + public static void main(String... args) { + System.out.println(">> SYSTEM BOOT SEQUENCE INITIATED <<"); + + // Initialization + infrastructure.add(new PhotoComponent("LUM-01")); + infrastructure.add(new ThermalComponent("THM-02")); + + beginSimulation(); + } + + private static void beginSimulation() { + // Technique: Standard loop instead of while, different iteration logic + for (int i = 0; i < 24; i++) { + String timeLog = String.format("Log Entry: [%02d:00]", i); + System.out.println(timeLog); + + // Technique: Indexed access loop changes the bytecode iteration pattern + for (int j = 0; j < infrastructure.size(); j++) { + GridComponent component = infrastructure.get(j); + + // Technique: Logic Shifting. The controller no longer decides *if* // the device toggles, it just tells the device to "update". + // The probability logic is now hidden inside the object. + component.performCycle(entropySource); + } + } + } + + // Technique: Introduced Interface to alter inheritance depth + interface Manageable { + void performCycle(Random r); + } + + // Abstract Base + static abstract class GridComponent implements Manageable { + protected String tag; + protected boolean isOnline = false; + + public GridComponent(String tag) { + this.tag = tag; + } + + // Logic Shift: Random check happens internally now + public void performCycle(Random r) { + if (r.nextBoolean()) { + togglePower(); + } + + if (isOnline) { + doWork(); + } + } + + private void togglePower() { + isOnline = !isOnline; + // Technique: StringBuilder changes the compiled bytecode string handling + StringBuilder sb = new StringBuilder(); + sb.append(" [INFO] ").append(tag).append(" is "); + sb.append(isOnline + "STANDBY"); + System.out.println(sb.toString()); + } + + protected abstract void doWork(); + } + + // Implementation A + static class PhotoComponent extends GridComponent { + public PhotoComponent(String s) { + super(s); + } + + @Override + protected void doWork() { + System.out.println(" -> Outputting visible spectrum light."); + } + } + + // Implementation B + static class ThermalComponent extends GridComponent { + public ThermalComponent(String s) { + super(s); + } + + @Override + protected void doWork() { + System.out.println(" -> Adjusting ambient temperature."); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/NetworkController.java b/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/NetworkController.java new file mode 100644 index 0000000000..b8875c47a3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/NetworkController.java @@ -0,0 +1,93 @@ +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +/** + * System Controller V1.0 + * Manages generic network nodes via polymorphic calls. + */ +public class NetworkController { + + // Switched to LinkedList to change memory signature + private static final List nodes = new LinkedList<>(); + + public static void main(String[] args) { + System.out.println("Initializing Network Protocol..."); + + // Instantiating renamed classes + nodes.add(new LuminaryUnit("Unit-Alpha (Light)")); + nodes.add(new ClimateManager("Unit-Beta (HVAC)")); + + runSystemLoop(); + } + + private static void runSystemLoop() { + Random rng = new Random(); + int tick = 0; + + // Switched 'for' loop to 'while' loop + while (tick < 24) { + System.out.printf("Cycle T-%02d00%n", tick); + + for (NetworkNode node : nodes) { + // Logic alteration: instead of nextBoolean(), we check an integer threshold + // Functionally identical (50% chance) + if (rng.nextInt(100) > 49) { + node.switchState(); + } + + if (node.isActive) { + node.executeTask(); + } + } + tick++; + } + } + + // --- Node Hierarchy (Refactored) --- + + // Renamed Abstract Class + abstract static class NetworkNode { + protected String uID; + protected boolean isActive; + + NetworkNode(String uID) { + this.uID = uID; + this.isActive = false; // Explicit init + } + + // Renamed method: toggle -> switchState + void switchState() { + isActive = !isActive; + String state = isActive + "[DISABLED]"; + System.out.println(" > " + uID + " status: " + state); + } + + // Renamed abstract method: operate -> executeTask + abstract void executeTask(); + } + + // Renamed Concrete Class 1 + static class LuminaryUnit extends NetworkNode { + LuminaryUnit(String uID) { + super(uID); + } + + @Override + void executeTask() { + System.out.println(" >>> " + uID + " emitting brightness at max capacity."); + } + } + + // Renamed Concrete Class 2 + static class ClimateManager extends NetworkNode { + ClimateManager(String uID) { + super(uID); + } + + @Override + void executeTask() { + System.out.println(" >>> " + uID + " stabilizing environment temp."); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/ServerProcessManager.java b/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/ServerProcessManager.java new file mode 100644 index 0000000000..bea4c67491 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/ServerProcessManager.java @@ -0,0 +1,110 @@ +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * System Allocation Tool v2.1 + * Manages thread assignments across fixed server slots. + */ +public class ServerProcessManager { + + // Logic Change: Using HashMap instead of Array to alter data structure signature + private static final int MAX_CAPACITY = 5; + private static final Map activeTasks = new HashMap<>(); + + public static void main(String[] args) { + runTerminal(); + } + + private static void runTerminal() { + Scanner io = new Scanner(System.in); + System.out.println(">> SERVER NODE INITIALIZED <<"); + + boolean systemActive = true; + + // Logic Change: 'do-while' loop structure could be used, but keeping while + // with altered internal branching (if-else instead of switch) + while (systemActive) { + printInterface(); + String input = io.nextLine().trim(); + + if (input.equals("1")) { + allocateTask(io); + } else if (input.equals("2")) { + killTask(io); + } else if (input.equals("3")) { + auditMemory(); + } else if (input.equals("4")) { + System.out.println("Shutting down core..."); + systemActive = false; + } else { + System.out.println("ERR: Unknown Protocol."); + } + } + } + + private static void printInterface() { + System.out.println("\n[1] Deploy New Task"); + System.out.println("[2] Terminate Task"); + System.out.println("[3] Memory Audit"); + System.out.println("[4] Halt System"); + System.out.print("cmd> "); + } + + // Refactored: 'bookSeat' -> 'allocateTask' + private static void allocateTask(Scanner s) { + System.out.print("Target Slot ID (0-" + (MAX_CAPACITY - 1) + "): "); + try { + int slot = Integer.parseInt(s.nextLine()); + + if (isValidSlot(slot)) { + // Changed logic: checking map key existence instead of null check + if (!activeTasks.containsKey(slot)) { + System.out.print("Process Identifier: "); + String processId = s.nextLine(); + activeTasks.put(slot, processId); + System.out.println(">> Task Allocated Successfully."); + } else { + System.out.println(">> Error: Slot Busy."); + } + } else { + System.out.println(">> Error: Invalid ID."); + } + } catch (NumberFormatException e) { + System.out.println(">> Input Failure."); + } + } + + // Refactored: 'cancelSeat' -> 'killTask' + private static void killTask(Scanner s) { + System.out.print("Target Slot ID: "); + try { + int slot = Integer.parseInt(s.nextLine()); + + // Logic change: using map remove which returns the object or null + if (activeTasks.containsKey(slot)) { + String removed = activeTasks.remove(slot); + System.out.println(">> Terminated: " + removed); + } else { + System.out.println(">> Slot is already idle."); + } + } catch (NumberFormatException e) { + System.out.println(">> Error."); + } + } + + // Refactored: 'viewManifest' -> 'auditMemory' + private static void auditMemory() { + System.out.println("--- Active Threads ---"); + for (int i = 0; i < MAX_CAPACITY; i++) { + // Using getOrDefault to simplify the null check logic logic + String state = activeTasks.getOrDefault(i, "[IDLE]"); + System.out.println("Slot [" + i + "] : " + state); + } + } + + // New helper method to change code complexity score + private static boolean isValidSlot(int index) { + return index >= 0 && index < MAX_CAPACITY; + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/source.txt b/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/source.txt new file mode 100644 index 0000000000..72252210c7 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/geminiPlag/source.txt @@ -0,0 +1,2 @@ +All Code in this directory is generated with Google Gemini 3 Pro. +Initial Prompt: ProjectX.java + "Plagiarize this program and try to hide it" diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project1.java b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project1.java new file mode 100644 index 0000000000..0aa6487376 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project1.java @@ -0,0 +1,44 @@ +import java.util.Scanner; + +// GuessingGameMain.java +public class Main { + public static void main(String[] args) { + System.out.println("Welcome to Guessing Game!"); + NumberGameLogic game = new NumberGameLogic(); + game.play(); + } + + //DeadCodeStart + static class UnusedHelper { + private int unusedField = 42; + + public void unusedMethod() { + System.out.println("Never called"); + } + } + //DeadCodeEnd +} + +// NumberGameLogic.java (direct copy of play() from Project 1 GameLogic) +class NumberGameLogic { + void play() { + //DeadCodeStart + // Hidden Project 1 snippet: int secret = (int) (Math.random() * 10) + 1; + //DeadCodeEnd + int secret = (int) (Math.random() * 10) + 1; + Scanner sc = new Scanner(System.in); + System.out.println("Guess a number between 1-10:"); + while (true) { + int guess = sc.nextInt(); + if (guess == secret) { + System.out.println("Correct! You win."); + break; + } else if (guess < secret) { + System.out.println("Too low. Try again."); + } else { + System.out.println("Too high. Try again."); + } + } + sc.close(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project2.java b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project2.java new file mode 100644 index 0000000000..f9f3f5be32 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project2.java @@ -0,0 +1,45 @@ +import java.util.Scanner; + +// NumberGuesserMain.java (plagiarizes startup from Project 1) +public class Main { + public static void main(String[] args) { + System.out.println("Welcome to Enhanced Guessing Game!"); + NumberGameLogic logic = new NumberGameLogic(); + logic.play(); + } + + //DeadCodeStart + // Copied directly from Project 1 to hide plagiarism + static class UnusedHelper { + private int unusedField = 42; + + public void unusedMethod() { + System.out.println("Never called"); + } + } + //DeadCodeEnd +} + +// NumberGameLogic.java (direct copy of play() from Project 1 GameLogic) +class NumberGameLogic { + void play() { + //DeadCodeStart + // Hidden Project 1 snippet: int secret = (int) (Math.random() * 10) + 1; + //DeadCodeEnd + int secret = (int) (Math.random() * 10) + 1; + Scanner sc = new Scanner(System.in); + System.out.println("Guess a number between 1-10:"); + while (true) { + int guess = sc.nextInt(); + if (guess == secret) { + System.out.println("Correct! You win."); + break; + } else if (guess < secret) { + System.out.println("Too low. Try again."); + } else { + System.out.println("Too high. Try again."); + } + } + sc.close(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project3.java b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project3.java new file mode 100644 index 0000000000..d5aed4c9bf --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project3.java @@ -0,0 +1,21 @@ +public class Main { + public static void main(String[] args) { + System.out.println("Hello from Project 1!"); + System.out.println("Factorial of 5: " + factorial(5)); + } + + private static int factorial(int n) { + if (n <= 1) return 1; + return n * factorial(n - 1); + } + + //DeadCodeStart + private static String reverse(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = s.length() - 1; i >= 0; i--) { + sb.append(s.charAt(i)); + } + return sb.toString(); + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project4.java b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project4.java new file mode 100644 index 0000000000..866f40b3be --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project4.java @@ -0,0 +1,22 @@ +public class Main { + public static void main(String[] args) { + System.out.println("Hello from Project 2!"); + System.out.println("Reverse of 'abc': " + reverse("abc")); + } + + private static String reverse(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = s.length() - 1; i >= 0; i--) { + sb.append(s.charAt(i)); + } + return sb.toString(); + } + + //DeadCodeStart + // (Verbatim copy of factorial from Project 1, hidden here) + private static int factorial(int n) { + if (n <= 1) return 1; + return n * factorial(n - 1); + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project5.java b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project5.java new file mode 100644 index 0000000000..9fd8d74f0c --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project5.java @@ -0,0 +1,38 @@ +public class Main { + public static void main(String[] args) { + System.out.println("Hello from Project 3!"); + //System.out.println("Factorial of 4: " + factorial(4)); + System.out.println("Reverse of 'hello': " + reverse("hello")); + } + + //DeadCodeStart + // (Verbatim copy of factorial from Project 1/2, additionally hidden here) + private static int factorial(int n) { + if (n <= 1) return 1; + return n * factorial(n - 1); + } + //DeadCodeEnd + + private static String reverse(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = s.length() - 1; i >= 0; i--) { + sb.append(s.charAt(i)); + } + return sb.toString(); + } + + //DeadCodeStart + private static void unusedSorter(int[] arr) { + // Unused bubble sort implementation + for (int i = 0; i < arr.length - 1; i++) { + for (int j = 0; j < arr.length - i - 1; j++) { + if (arr[j] > arr[j + 1]) { + int temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } + } + //DeadCodeEnd +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project6.java b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project6.java new file mode 100644 index 0000000000..3217535054 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project6.java @@ -0,0 +1,260 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class Book { + private String title; + private String author; + private String isbn; + private boolean available = true; + + //DeadCodeStart + public Book() { + } + //DeadCodeEnd + + public Book(String title, String author, String isbn) { + this.title = title; + this.author = author; + this.isbn = isbn; + } + + //DeadCodeStart + public Book(String title) { + this.title = title; + } + //DeadCodeEnd + + // Getters and setters + public String getTitle() { + return title; + } + + //DeadCodeStart + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //DeadCodeEnd + + public String getIsbn() { + return isbn; + } + + //DeadCodeStart + public void setIsbn(String isbn) { + this.isbn = isbn; + } + //DeadCodeEnd + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public int hashCode() { + return Objects.hash(isbn); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Book book = (Book) o; + return Objects.equals(isbn, book.isbn); + } + + @Override + public String toString() { + return "Book{title='" + title + "', author='" + author + "', isbn='" + isbn + "', available=" + available + "}"; + } + + //DeadCodeStart + public void reserveBook() { + if (available) { + System.out.println("Book reserved."); + } + } +//DeadCodeEnd +} + +public class Member { + private int id; + private String name; + private List borrowedBooks = new ArrayList<>(); + + public Member(int id, String name) { + this.id = id; + this.name = name; + } + + public int getId() { + return id; + } + + //DeadCodeStart + public void setId(int id) { + this.id = id; + } + //DeadCodeEnd + + public String getName() { + return name; + } + + //DeadCodeStart + public void setName(String name) { + this.name = name; + } + + + public List getBorrowedBooks() { + return borrowedBooks; + } + //DeadCodeEnd + + public void borrowBook(Book book) { + borrowedBooks.add(book); + book.setAvailable(false); + } + + public void returnBook(Book book) { + borrowedBooks.remove(book); + book.setAvailable(true); + } + + @Override + public String toString() { + return "Member{id=" + id + ", name='" + name + "', borrowedBooks.size=" + borrowedBooks.size() + "}"; + } +} + +public class Library { + private List books = new ArrayList<>(); + private List members = new ArrayList<>(); + + public void addBook(Book book) { + books.add(book); + System.out.println("Book added: " + book.getTitle()); + } + + public void addMember(Member member) { + members.add(member); + System.out.println("Member added: " + member.getName()); + } + + public void borrowBook(String isbn, int memberId) { + Book book = findBook(isbn); + Member member = findMember(memberId); + if (book != null && member != null && book.isAvailable()) { + member.borrowBook(book); + System.out.println("Book borrowed successfully."); + } else { + System.out.println("Cannot borrow book."); + } + } + + public void returnBook(String isbn, int memberId) { + Book book = findBook(isbn); + Member member = findMember(memberId); + if (book != null && member != null) { + member.returnBook(book); + System.out.println("Book returned successfully."); + } + } + + public void displayBooks() { + books.forEach(System.out::println); + } + + public void displayMembers() { + members.forEach(System.out::println); + } + + private Book findBook(String isbn) { + return books.stream().filter(b -> b.getIsbn().equals(isbn)).findFirst().orElse(null); + } + + private Member findMember(int id) { + return members.stream().filter(m -> m.getId() == id).findFirst().orElse(null); + } + + //DeadCodeStart + public void searchByAuthor(String author) { + books.stream().filter(b -> b.getAuthor().equals(author)).forEach(System.out::println); + } + + public double calculateFines(int memberId) { + return 0.0; // Placeholder + } + //DeadCodeEnd +} + +public class Main { + public static void main(String[] args) { + Library library = new Library(); + Scanner scanner = new Scanner(System.in); + boolean running = true; + + while (running) { + System.out.println("\n1. Add Book\n2. Add Member\n3. Borrow Book\n4. Return Book\n5. Display Books\n6. Display Members\n7. Exit"); + int choice = scanner.nextInt(); + scanner.nextLine(); + + switch (choice) { + case 1: + System.out.print("Title: "); + String title = scanner.nextLine(); + System.out.print("Author: "); + String author = scanner.nextLine(); + System.out.print("ISBN: "); + String isbn = scanner.nextLine(); + library.addBook(new Book(title, author, isbn)); + break; + case 2: + System.out.print("ID: "); + int id = scanner.nextInt(); + scanner.nextLine(); + System.out.print("Name: "); + String name = scanner.nextLine(); + library.addMember(new Member(id, name)); + break; + case 3: + System.out.print("ISBN: "); + String bIsbn = scanner.nextLine(); + System.out.print("Member ID: "); + int mId1 = scanner.nextInt(); + library.borrowBook(bIsbn, mId1); + break; + case 4: + System.out.print("ISBN: "); + String rIsbn = scanner.nextLine(); + System.out.print("Member ID: "); + int mId2 = scanner.nextInt(); + library.returnBook(rIsbn, mId2); + break; + case 5: + library.displayBooks(); + break; + case 6: + library.displayMembers(); + break; + case 7: + running = false; + break; + } + } + scanner.close(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project7.java b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project7.java new file mode 100644 index 0000000000..ff2358a048 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project7.java @@ -0,0 +1,229 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Scanner; + +public class Product { + private String name; // Plagiarized from title + private String supplier; // Plagiarized from author + private String productId; // Plagiarized from isbn + private boolean inStock = true; // Plagiarized from available + + //DeadCodeStart + public Product() { + } + //DeadCodeEnd + + public Product(String name, String supplier, String productId) { + this.name = name; + this.supplier = supplier; + this.productId = productId; + } + + //DeadCodeStart + public Product(String name) { + this.name = name; + } + //DeadCodeEnd + + // Plagiarized getters/setters + public String getName() { + return name; + } + + //DeadCodeStart + public void setName(String name) { + this.name = name; + } + + public String getSupplier() { + return supplier; + } + //DeadCodeEnd + + //DeadCodeStart + public void setSupplier(String supplier) { + this.supplier = supplier; + } + //DeadCodeEnd + + public String getProductId() { + return productId; + } + + //DeadCodeStart + public void setProductId(String productId) { + this.productId = productId; + } + //DeadCodeEnd + + public boolean isInStock() { + return inStock; + } + + public void setInStock(boolean inStock) { + this.inStock = inStock; + } + + @Override + public int hashCode() { + return Objects.hash(productId); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Product product = (Product) o; + return Objects.equals(productId, product.productId); + } + + @Override + public String toString() { + return "Product{name='" + name + "', supplier='" + supplier + "', productId='" + productId + "', inStock=" + inStock + "}"; + } + + //DeadCodeStart (hides plagiarized reserveBook renamed) + public void reserveProduct() { + if (inStock) { + System.out.println("Product reserved."); + } + } + //DeadCodeEnd +} + +public class Customer { + private int id; + private String name; + private List purchasedProducts = new ArrayList<>(); + + public Customer(int id, String name) { + this.id = id; + this.name = name; + } + + // Similar methods to Member + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public List getPurchasedProducts() { + return purchasedProducts; + } + + public void purchaseProduct(Product product) { + purchasedProducts.add(product); + product.setInStock(false); + } +} + +public class Inventory { + private List products = new ArrayList<>(); + private List customers = new ArrayList<>(); + + public void addProduct(Product product) { + products.add(product); + System.out.println("Product added: " + product.getName()); + } + + public void addCustomer(Customer customer) { + customers.add(customer); + System.out.println("Customer added: " + customer.getName()); + } + + public void sellProduct(String prodId, int custId) { // Plagiarized borrow renamed + Product product = findProduct(prodId); + Customer customer = findCustomer(custId); + if (product != null && customer != null && product.isInStock()) { + customer.purchaseProduct(product); + System.out.println("Product sold successfully."); + } + } + + public void restockProduct(String prodId, int custId) { // Plagiarized return renamed + Product product = findProduct(prodId); + Customer customer = findCustomer(custId); + if (product != null && customer != null && customer.getPurchasedProducts().contains(product)) { + customer.getPurchasedProducts().remove(product); + product.setInStock(true); + System.out.println("Product restocked."); + } + } + + public void displayProducts() { + products.forEach(System.out::println); + } + + private Product findProduct(String prodId) { + return products.stream().filter(p -> p.getProductId().equals(prodId)).findFirst().orElse(null); + } + + private Customer findCustomer(int id) { + return customers.stream().filter(c -> c.getId() == id).findFirst().orElse(null); + } + + //DeadCodeStart (hides plagiarized searchByAuthor renamed) + public void searchBySupplier(String supplier) { + products.stream().filter(p -> p.getSupplier().equals(supplier)).forEach(System.out::println); + } +//DeadCodeEnd +} + +public class Main { + public static void main(String[] args) { + Inventory inventory = new Inventory(); + Scanner scanner = new Scanner(System.in); + boolean running = true; + + while (running) { + System.out.println("\n1. Add Product\n2. Add Customer\n3. Sell Product\n4. Restock\n5. Display Products\n6. Exit"); + int choice = scanner.nextInt(); + scanner.nextLine(); + + switch (choice) { + case 1: + System.out.print("Name: "); + String name = scanner.nextLine(); + System.out.print("Supplier: "); + String supp = scanner.nextLine(); + System.out.print("Product ID: "); + String pid = scanner.nextLine(); + inventory.addProduct(new Product(name, supp, pid)); + break; + case 2: + System.out.print("ID: "); + int id = scanner.nextInt(); + scanner.nextLine(); + System.out.print("Name: "); + String cname = scanner.nextLine(); + inventory.addCustomer(new Customer(id, cname)); + break; + case 3: + System.out.print("Product ID: "); + String spid = scanner.nextLine(); + System.out.print("Customer ID: "); + int cid1 = scanner.nextInt(); + inventory.sellProduct(spid, cid1); + break; + case 4: + System.out.print("Product ID: "); + String rpid = scanner.nextLine(); + System.out.print("Customer ID: "); + int cid2 = scanner.nextInt(); + inventory.restockProduct(rpid, cid2); + break; + case 5: + inventory.displayProducts(); + break; + case 6: + running = false; + break; + } + } + scanner.close(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project8.java b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project8.java new file mode 100644 index 0000000000..9be12d9ef0 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/project8.java @@ -0,0 +1,152 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class Account { + private String accountNumber; + private String owner; + private double balance; + //DeadCodeStart + private boolean active = true; + //DeadCodeEnd + + public Account(String accountNumber, String owner, double balance) { + this.accountNumber = accountNumber; + this.owner = owner; + this.balance = balance; + } + + // Getters/setters + public String getAccountNumber() { + return accountNumber; + } + + //DeadCodeStart + public String getOwner() { + return owner; + } + + public double getBalance() { + return balance; + } + + public void setBalance(double balance) { + this.balance = balance; + } + + public boolean isActive() { + return active; + } + //DeadCodeEnd + + public void deposit(double amount) { + if (amount > 0) balance += amount; + } + + public boolean withdraw(double amount) { + if (amount > 0 && balance >= amount) { + balance -= amount; + return true; + } + return false; + } + + @Override + public String toString() { + return "Account{number='" + accountNumber + "', owner='" + owner + "', balance=" + balance + "}"; + } +} + +public class Bank { + private List accounts = new ArrayList<>(); + + public void addAccount(Account account) { + accounts.add(account); + System.out.println("Account added: " + account.getAccountNumber()); + } + + public void deposit(String accNum, double amount) { + Account acc = findAccount(accNum); + if (acc != null) { + acc.deposit(amount); + System.out.println("Deposited: " + amount); + } + } + + public void withdraw(String accNum, double amount) { + Account acc = findAccount(accNum); + if (acc != null && acc.withdraw(amount)) { + System.out.println("Withdrawn: " + amount); + } + } + + public void displayAccounts() { + accounts.forEach(System.out::println); + } + + private Account findAccount(String accNum) { + return accounts.stream().filter(a -> a.getAccountNumber().equals(accNum)).findFirst().orElse(null); + } + + //DeadCodeStart + // Plagiarized from Member/Customer borrow/purchase renamed and unused + public void transferFunds(String fromAcc, String toAcc, double amount) { + Account from = findAccount(fromAcc); + Account to = findAccount(toAcc); + if (from != null && to != null && from.withdraw(amount)) { + to.deposit(amount); + } + } + + public void auditAccounts() { + accounts.forEach(a -> System.out.println("Audit: " + a.getBalance())); + } +//DeadCodeEnd +} + +public class Main { + public static void main(String[] args) { + Bank bank = new Bank(); + Scanner scanner = new Scanner(System.in); + boolean running = true; + + while (running) { + System.out.println("\n1. Add Account\n2. Deposit\n3. Withdraw\n4. Display Accounts\n5. Exit"); + int choice = scanner.nextInt(); + scanner.nextLine(); + + switch (choice) { + case 1: + System.out.print("Account Number: "); + String num = scanner.nextLine(); + System.out.print("Owner: "); + String own = scanner.nextLine(); + System.out.print("Initial Balance: "); + double bal = scanner.nextDouble(); + bank.addAccount(new Account(num, own, bal)); + break; + case 2: + System.out.print("Account Number: "); + String dnum = scanner.nextLine(); + System.out.print("Amount: "); + double damt = scanner.nextDouble(); + bank.deposit(dnum, damt); + break; + case 3: + System.out.print("Account Number: "); + String wnum = scanner.nextLine(); + System.out.print("Amount: "); + double wamt = scanner.nextDouble(); + bank.withdraw(wnum, wamt); + break; + case 4: + bank.displayAccounts(); + break; + case 5: + running = false; + break; + } + } + scanner.close(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/grok/source.txt b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/source.txt new file mode 100644 index 0000000000..5a291ba448 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/grok/source.txt @@ -0,0 +1 @@ +All Code in this directory is generated with Grok 4.1 diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project1.java b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project1.java new file mode 100644 index 0000000000..bec477aede --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project1.java @@ -0,0 +1,51 @@ +public class StudentGradeCalculator { + private double[] grades; + private String studentName; + + public StudentGradeCalculator(String name, double[] gradeList) { + this.studentName = name; + this.grades = gradeList; + } + + public double calculateAverage() { + double sum = 0; + for (double grade : grades) { + sum += grade; + } + return sum / grades.length; + } + + public String getGrade() { + double avg = calculateAverage(); + if (avg >= 90) return "A"; + if (avg >= 80) return "B"; + if (avg >= 70) return "C"; + if (avg >= 60) return "D"; + return "F"; + } + + //DeadCodeStart + public void unusedMethodForLogging() { + System.out.println("This method is never called"); + int unusedVariable = 42; + String deadString = "This code is dead"; + for (int i = 0; i < 100; i++) { + // Pointless loop that does nothing + } + } + //DeadCodeEnd + + public void displayResult() { + System.out.println("Student: " + studentName); + System.out.println("Average: " + calculateAverage()); + System.out.println("Grade: " + getGrade()); + } +} + +public class Main { + public static void main(String[] args) { + double[] grades = {85, 90, 78, 92, 88}; + StudentGradeCalculator calculator = new StudentGradeCalculator("John Doe", grades); + calculator.displayResult(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project2.java b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project2.java new file mode 100644 index 0000000000..33d725e972 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project2.java @@ -0,0 +1,63 @@ +public class InventoryManager { + private String[] itemNames; + private int[] itemQuantities; + private double[] itemPrices; + private int itemCount; + + public InventoryManager(int capacity) { + itemNames = new String[capacity]; + itemQuantities = new int[capacity]; + itemPrices = new double[capacity]; + itemCount = 0; + } + + //DeadCodeStart + private int deprecatedCalculateProfit() { + int profit = 0; + for (int i = 0; i < itemCount; i++) { + profit += itemQuantities[i]; + } + return profit; + } + + private void oldLoggingSystem() { + System.out.println("Old logging disabled"); + String logMessage = "System log entries"; + int[] unusedArray = new int[1000]; + } + //DeadCodeEnd + + public void addItem(String name, int quantity, double price) { + if (itemCount < itemNames.length) { + itemNames[itemCount] = name; + itemQuantities[itemCount] = quantity; + itemPrices[itemCount] = price; + itemCount++; + } + } + + public double calculateTotalValue() { + double sum = 0; + for (int i = 0; i < itemCount; i++) { + sum += itemQuantities[i] * itemPrices[i]; + } + return sum; + } + + public void displayInventory() { + System.out.println("=== Inventory Report ==="); + for (int i = 0; i < itemCount; i++) { + System.out.println(itemNames[i] + ": " + itemQuantities[i] + " units @ $" + itemPrices[i]); + } + System.out.println("Total Value: $" + calculateTotalValue()); + } +} +public class Main { + public static void main(String[] args) { + InventoryManager inventory = new InventoryManager(10); + inventory.addItem("Laptop", 5, 999.99); + inventory.addItem("Mouse", 50, 25.50); + inventory.addItem("Keyboard", 30, 79.99); + inventory.displayInventory(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project3.java b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project3.java new file mode 100644 index 0000000000..0b29b5b998 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project3.java @@ -0,0 +1,62 @@ +public class PlagiarizedGradeAnalyzer { + private double[] scores; + private String studentName; + + public PlagiarizedGradeAnalyzer(String name, double[] scoreList) { + this.studentName = name; + this.scores = scoreList; + } + + //DeadCodeStart + // Obfuscation: Dead code to hide the plagiarized method below + private double calculateMedian() { + // This method is not used anywhere + java.util.Arrays.sort(scores); + if (scores.length % 2 == 0) { + return (scores[scores.length/2 - 1] + scores[scores.length/2]) / 2; + } + return scores[scores.length/2]; + } + + private void printDebugInfo() { + System.out.println("Debug: internal state"); + int debugCounter = 0; + while (debugCounter < 50) { + debugCounter++; + } + boolean isDebugMode = false; + } + //DeadCodeEnd + + // PLAGIARIZED: Identical logic to StudentGradeCalculator.calculateAverage() + public double calculateAverage() { + double sum = 0; + for (double grade : scores) { + sum += grade; + } + return sum / scores.length; + } + + // PLAGIARIZED: Identical logic to StudentGradeCalculator.getGrade() + public String getGrade() { + double avg = calculateAverage(); + if (avg >= 90) return "A"; + if (avg >= 80) return "B"; + if (avg >= 70) return "C"; + if (avg >= 60) return "D"; + return "F"; + } + + public void showAnalysis() { + System.out.println("Student: " + studentName); + System.out.println("Average: " + calculateAverage()); + System.out.println("Grade: " + getGrade()); + } +} +public class Main { + public static void main(String[] args) { + double[] scores = {85, 90, 78, 92, 88}; + PlagiarizedGradeAnalyzer analyzer = new PlagiarizedGradeAnalyzer("Jane Smith", scores); + analyzer.showAnalysis(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project4.java b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project4.java new file mode 100644 index 0000000000..5155f45aba --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/Project4.java @@ -0,0 +1,61 @@ +public class DataProcessor { + private int[] dataPoints; + private String processName; + + public DataProcessor(String name, int[] data) { + this.processName = name; + this.dataPoints = data; + } + + //DeadCodeStart + // Dead code from old inventory system + private String[] itemNames; + private int[] itemQuantities; + + private void addInventoryItem(String name, int quantity) { + // This inventory code is completely unused in this class + if (itemNames == null) { + itemNames = new String[10]; + itemQuantities = new int[10]; + } + } + + private double calculateInventoryValue() { + double value = 0; + // Dead code - method never called + return value; + } + + private void logDeprecatedMetrics() { + System.out.println("Logging disabled"); + int obsoleteCounter = 0; + } + //DeadCodeEnd + + // PLAGIARIZED LOGIC: From StudentGradeCalculator.calculateAverage() + public double calculateAverage() { + double sum = 0; + for (int point : dataPoints) { + sum += point; + } + return sum / dataPoints.length; + } + + public int getMedianValue() { + java.util.Arrays.sort(dataPoints); + return dataPoints[dataPoints.length / 2]; + } + + public void generateReport() { + System.out.println("Processing: " + processName); + System.out.println("Average: " + calculateAverage()); + System.out.println("Median: " + getMedianValue()); + } +} +public class Main { + public static void main(String[] args) { + int[] data = {15, 23, 45, 67, 89, 34, 56}; + DataProcessor processor = new DataProcessor("Dataset Analysis", data); + processor.generateReport(); + } +} diff --git a/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/source.txt b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/source.txt new file mode 100644 index 0000000000..e2a3f259a4 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/perplexityLabs/source.txt @@ -0,0 +1,10 @@ +All Code in this directory is generated with Perplexity Labs Ai. +Initial Prompt: " write two or more java example projects that must contain: + + - a Main class with a main method as a starting point + + - sometimes dead code in all variants + + - sometimes one project should plagiarize from another + + - plagiarisms and dead code can be combined, and dead code can be used to hide plagiarisms " diff --git a/languages/java-cpg/src/test/resources/java/combined/.gitignore b/languages/java-cpg/src/test/resources/java/combined/.gitignore new file mode 100644 index 0000000000..dc67e5ca3f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/.gitignore @@ -0,0 +1,11 @@ +# Compiled class files +*.class + +# IntelliJ IDEA +.idea/ +*.iml +*.iws +combined.iml + +# Build output +out/ diff --git a/languages/java-cpg/src/test/resources/java/combined/One/DataProcessor.java b/languages/java-cpg/src/test/resources/java/combined/One/DataProcessor.java new file mode 100644 index 0000000000..80bdc26128 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/One/DataProcessor.java @@ -0,0 +1,145 @@ +// DataProcessor.java +//@author Anthropic Claude Sonnet 4.5 +public class DataProcessor { + private static final int MAX_SIZE = 1000; + private static final int MIN_SIZE = 0; + private boolean enabled = true; + + // Dead field: never read + private String processorName = "DefaultProcessor"; + private int unusedCounter = 0; + + public void processData(int size) { + System.out.println("Processing data of size: " + size); + + // Dead code: variable assigned but never used + int temp = size * 2; + + if (size > 50) { + handleLargeData(size); + } else { + handleSmallData(size); + } + + // Dead code: condition always true based on context + if (enabled) { + System.out.println("Processor is enabled"); + } else { + // Dead code + disableProcessor(); + cleanupResources(); + } + + // Dead code: unreachable after method calls + finalizeProcessing(); + return; + } + + private void handleLargeData(int size) { + System.out.println("Handling large dataset"); + + // Dead code: contradictory conditions + if (size > 100 && size < 50) { + optimizeForSpeed(); + } + } + + private void handleSmallData(int size) { + System.out.println("Handling small dataset"); + + // Dead code: loop with impossible condition + for (int i = size; i > size; i++) { + processItem(i); + } + } + + private void finalizeProcessing() { + System.out.println("Finalizing processing"); + + // Dead code: try-finally with unreachable code + try { + System.out.println("Final operations"); + return; + } finally { + // This executes, but code after return in try is dead + System.out.println("Cleanup in finally"); + } + } + + // Dead method: never called + private void disableProcessor() { + enabled = false; + System.out.println("Processor disabled"); + } + + // Dead method: never called + private void cleanupResources() { + System.out.println("Cleaning up resources"); + } + + // Dead method: never called + private void logProcessingComplete() { + System.out.println("Processing complete"); + } + + // Dead method: never called + private void optimizeForSpeed() { + System.out.println("Optimizing for speed"); + } + + // Dead method: never called + private void processItem(int item) { + System.out.println("Processing item: " + item); + } + + // Dead method: never called + public void resetProcessor() { + enabled = true; + unusedCounter = 0; + } + + // Dead method: never called + private boolean validateSize(int size) { + return size >= MIN_SIZE && size <= MAX_SIZE; + } + + // Dead method with dead code inside + private void complexDeadMethod() { + int x = 10; + + // Dead code: condition always false + if (x > 100) { + System.out.println("X is large"); + + // Nested dead code + if (x < 50) { + System.out.println("X is also small somehow"); + } + } + + // Dead code: unreachable loop + while (x < 5) { + x++; + System.out.println("Incrementing x"); + } + } + + // Dead method: never called + public String getProcessorInfo() { + return "Processor: " + processorName + ", Status: " + + (enabled ? "Enabled" : "Disabled"); + } + + // Dead method with multiple return paths + private int calculatePriority(int value) { + if (value < 0) { + return 0; + } else if (value < 10) { + return 1; + } else if (value < 50) { + return 2; + } else { + return 3; + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/combined/One/Main.java b/languages/java-cpg/src/test/resources/java/combined/One/Main.java new file mode 100644 index 0000000000..8ba1cfeb13 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/One/Main.java @@ -0,0 +1,134 @@ +// Main.java +//@author Anthropic Claude Sonnet 4.5 +public class Main { + private static final boolean DEBUG = false; + private static final String UNUSED_CONSTANT = "This is never used"; + private int unusedField = 42; + + public static void main(String[] args) { + System.out.println("Advanced Dead Code Analysis Program"); + + Main m = new Main(); + m.demonstrateDeadCode(); + + DataProcessor processor = new DataProcessor(); + processor.processData(100); + + UtilityHelper helper = new UtilityHelper(); + helper.performOperations(); + + // Dead code: condition always false + if (false) { + System.out.println("This will never execute"); + m.neverCalledMethod(); + } + + // Dead code: unreachable after return + System.out.println("Program completed"); + return; + } + + private void demonstrateDeadCode() { + int x = 10; + int y = 20; + + // Dead code: result never used + int unused = x + y; + + // Dead code: variable assigned but never read + String message = "Hello"; + message = "World"; + + // Dead code: condition always true + if (true) { + System.out.println("This always executes"); + } else { + System.out.println("This never executes"); + performDeadCalculation(); + } + + // Dead code: complex condition that's always false + if (x > 100 && x < 50) { + System.out.println("Logically impossible"); + } + + // Dead code: loop that never executes + for (int i = 10; i < 5; i++) { + System.out.println("Never runs: " + i); + } + + // Dead code: switch case never reached + int value = 1; + switch (value) { + case 1: + System.out.println("Case 1"); + break; + case 2: + System.out.println("Case 2"); + break; + default: + break; + } + } + + // Dead method: never called + private void neverCalledMethod() { + System.out.println("This method is never invoked"); + int result = complexCalculation(5, 10); + } + + // Dead method: never called + private int complexCalculation(int a, int b) { + return a * b + (a - b) * 2; + } + + // Dead method: never called + private void performDeadCalculation() { + double pi = 3.14159; + double radius = 5.0; + double area = pi * radius * radius; + System.out.println("Area: " + area); + } + + // Dead method: never called + private void anotherUnreachableMethod() { + System.out.println("Unreachable"); + } + + // Dead method: never called + public String formatMessage(String input) { + return "[" + input.toUpperCase() + "]"; + } + + // Dead method: never called + private boolean validateInput(int input) { + return input > 0 && input < 1000; + } + + // Dead code in method with early returns + public int processValue(int value) { + if (value < 0) { + return -1; + } + + if (value == 0) { + return 0; + } + + return value * 2; + } + + // Dead code with exception handling + public void methodWithDeadCatch() { + try { + System.out.println("Normal operation"); + // No exception thrown + } catch (NullPointerException e) { + // Dead code: this specific exception never occurs + System.out.println("Handling null pointer"); + } catch (Exception e) { + // Dead code: no exception is ever thrown + System.out.println("General exception"); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/combined/One/UtilityHelper.java b/languages/java-cpg/src/test/resources/java/combined/One/UtilityHelper.java new file mode 100644 index 0000000000..5c1ecb104e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/One/UtilityHelper.java @@ -0,0 +1,170 @@ +// UtilityHelper.java +//@author Anthropic Claude Sonnet 4.5 +public class UtilityHelper { + private static final boolean FEATURE_ENABLED = false; + private static final int THRESHOLD = 100; + + // Dead fields: never used + private String helperName = "UtilityHelper"; + private int operationCount = 0; + private double multiplier = 1.5; + + public void performOperations() { + System.out.println("Performing utility operations"); + + // Dead code: constant condition + if (FEATURE_ENABLED) { + // Dead code block + advancedOperation(); + complexCalculation(); + } else { + System.out.println("Feature is disabled"); + } + + // Dead code: variable assigned but overwritten without use + int result = 100; + result = 200; + result = 300; + System.out.println("Result: " + result); + + // Dead code: unreachable due to exception + try { + System.out.println("Try block"); + throw new RuntimeException("Test"); + } catch (RuntimeException e) { + System.out.println("Caught exception: " + e.getMessage()); + } + + // Dead code in switch with return + switchOperation(5); + } + + private void switchOperation(int value) { + switch (value) { + case 1: + System.out.println("One"); + return; + case 5: + System.out.println("Five"); + break; + default: + System.out.println("Default"); + return; + } + + System.out.println("After switch"); + } + + // Dead method: never called + private void advancedOperation() { + System.out.println("Advanced operation"); + + // Dead code: nested dead conditions + if (THRESHOLD > 200) { + if (THRESHOLD < 150) { + System.out.println("Impossible condition"); + } + } + } + + // Dead method: never called + private int complexCalculation() { + int a = 10, b = 20, c = 30; + + // Dead code: result never used + int temp1 = a + b; + int temp2 = b + c; + int temp3 = a + c; + + return a + b + c; + } + + // Dead method: never called + private void cleanupOperation() { + System.out.println("Cleanup"); + } + + // Dead method: never called + public void performBatchOperation(int count) { + for (int i = 0; i < count; i++) { + processSingleItem(i); + + // Dead code: break in last iteration condition + if (i == count - 1) { + break; + } + } + + // Dead code: return at end of void method + return; + } + + // Dead method: never called + private void processSingleItem(int item) { + System.out.println("Processing: " + item); + + // Dead code: continue in single-statement loop + for (int i = 0; i < 1; i++) { + continue; + } + } + + // Dead method with multiple dead code patternsperformDeadCalculation + private void multipleDeadPatterns() { + // Pattern 1: Unreachable after return + if (Math.random() > 0.5) { + return; + } else { + return; + } + } + + // Dead method: never called + public boolean validateThreshold(int value) { + return value > THRESHOLD; + } + + // Dead method: never called + private String formatOutput(String input) { + if (input == null) { + return "null"; + } + + if (input.isEmpty()) { + return "empty"; + } + + return input.trim().toUpperCase(); + } + + // Dead method: never called + public void recursiveDeadCode(int n) { + if (n <= 0) { + return; + } + + recursiveDeadCode(n - 1); + + // Dead code: infinite recursion prevention that's never needed + if (n > 1000) { + return; + } + } + + // Dead method: never called + private void exceptionDeadCode() { + try { + System.out.println("Try block"); + // No exception thrown + } catch (ArithmeticException e) { + // Dead catch: specific exception never occurs + System.out.println("Arithmetic exception"); + } catch (NullPointerException e) { + // Dead catch: specific exception never occurs + System.out.println("Null pointer exception"); + } finally { + System.out.println("Finally block"); + return; + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/combined/Two/DeadFactoryPattern.java b/languages/java-cpg/src/test/resources/java/combined/Two/DeadFactoryPattern.java new file mode 100644 index 0000000000..abc94ddd4e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/Two/DeadFactoryPattern.java @@ -0,0 +1,408 @@ +// DeadFactoryPattern.java +//@author Anthropic Claude Sonnet 4.5 + +import java.util.ArrayList; +import java.util.List; + +// DEAD STRATEGY PATTERN: Never used +interface PaymentStrategy { + void pay(double amount); +} + +// DEAD OBSERVER PATTERN: Never used +interface EventListener { + void onEvent(String event); +} + +// DEAD DECORATOR PATTERN: Never used +interface Coffee { + double cost(); + + String description(); +} + +// DEAD ADAPTER PATTERN: Never used +interface MediaPlayer { + void play(String filename); +} + +interface AdvancedMediaPlayer { + void playVlc(String filename); + + void playMp4(String filename); +} + +// DEAD FACTORY CLASS: Never used +class CreatureFactory { + public static Creature createCreature(String type) { + switch (type.toLowerCase()) { + case "dog": + return new FactoryDog(); + case "cat": + return new FactoryCat(); + case "bird": + return new FactoryBird(); + default: + return null; + } + } +} + +// DEAD BASE CLASS for factory pattern +abstract class Creature { + protected String species; + + public abstract void makeNoise(); + + public void describe() { + System.out.println("This is a " + species); + } +} + +// DEAD CLASSES: Factory implementations never used +class FactoryDog extends Creature { + public FactoryDog() { + this.species = "Dog"; + } + + @Override + public void makeNoise() { + System.out.println("Woof!"); + } + + public void fetch() { + System.out.println("Fetching stick"); + } +} + +class FactoryCat extends Creature { + public FactoryCat() { + this.species = "Cat"; + } + + @Override + public void makeNoise() { + System.out.println("Meow!"); + } + + public void purr() { + System.out.println("Purring"); + } +} + +class FactoryBird extends Creature { + public FactoryBird() { + this.species = "Bird"; + } + + @Override + public void makeNoise() { + System.out.println("Tweet!"); + } + + public void flyAway() { + System.out.println("Flying high"); + } +} + +// DEAD SINGLETON PATTERN: Never accessed +class DatabaseConnection { + private static DatabaseConnection instance; + private String connectionString; + + private DatabaseConnection() { + connectionString = "localhost:5432"; + } + + public static DatabaseConnection getInstance() { + if (instance == null) { + instance = new DatabaseConnection(); + } + return instance; + } + + public void connect() { + System.out.println("Connected to " + connectionString); + } + + public void disconnect() { + System.out.println("Disconnected"); + } + + public void executeQuery(String query) { + System.out.println("Executing: " + query); + } +} + +// DEAD BUILDER PATTERN: Never used +class PersonBuilder { + private String name; + private int age; + private String address; + private String phone; + + public PersonBuilder setName(String name) { + this.name = name; + return this; + } + + public PersonBuilder setAge(int age) { + this.age = age; + return this; + } + + public PersonBuilder setAddress(String address) { + this.address = address; + return this; + } + + public PersonBuilder setPhone(String phone) { + this.phone = phone; + return this; + } + + public PersonData build() { + return new PersonData(name, age, address, phone); + } +} + +// DEAD CLASS: Result of builder +class PersonData { + private final String name; + private final int age; + private final String address; + private final String phone; + + public PersonData(String name, int age, String address, String phone) { + this.name = name; + this.age = age; + this.address = address; + this.phone = phone; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public String getAddress() { + return address; + } + + public String getPhone() { + return phone; + } + + @Override + public String toString() { + return "Person{name='" + name + "', age=" + age + + ", address='" + address + "', phone='" + phone + "'}"; + } +} + +class CreditCardPayment implements PaymentStrategy { + private String cardNumber; + + public CreditCardPayment(String cardNumber) { + this.cardNumber = cardNumber; + } + + @Override + public void pay(double amount) { + System.out.println("Paid $" + amount + " with credit card ending in " + + cardNumber.substring(cardNumber.length() - 4)); + } +} + +class PayPalPayment implements PaymentStrategy { + private String email; + + public PayPalPayment(String email) { + this.email = email; + } + + @Override + public void pay(double amount) { + System.out.println("Paid $" + amount + " via PayPal account " + email); + } +} + +class ShoppingCart { + private PaymentStrategy paymentStrategy; + private double total; + + public void setPaymentStrategy(PaymentStrategy strategy) { + this.paymentStrategy = strategy; + } + + public void addItem(double price) { + total += price; + } + + public void checkout() { + if (paymentStrategy != null) { + paymentStrategy.pay(total); + total = 0; + } + } +} + +class EventPublisher { + private List listeners = new ArrayList<>(); + + public void subscribe(EventListener listener) { + listeners.add(listener); + } + + public void unsubscribe(EventListener listener) { + listeners.remove(listener); + } + + public void notifyListeners(String event) { + for (EventListener listener : listeners) { + listener.onEvent(event); + } + } +} + +class EmailListener implements EventListener { + private String email; + + public EmailListener(String email) { + this.email = email; + } + + @Override + public void onEvent(String event) { + System.out.println("Email to " + email + ": " + event); + } +} + +class SMSListener implements EventListener { + private String phone; + + public SMSListener(String phone) { + this.phone = phone; + } + + @Override + public void onEvent(String event) { + System.out.println("SMS to " + phone + ": " + event); + } +} + +class SimpleCoffee implements Coffee { + @Override + public double cost() { + return 2.0; + } + + @Override + public String description() { + return "Simple coffee"; + } +} + +abstract class CoffeeDecorator implements Coffee { + protected Coffee coffee; + + public CoffeeDecorator(Coffee coffee) { + this.coffee = coffee; + } +} + +class MilkDecorator extends CoffeeDecorator { + public MilkDecorator(Coffee coffee) { + super(coffee); + } + + @Override + public double cost() { + return coffee.cost() + 0.5; + } + + @Override + public String description() { + return coffee.description() + ", milk"; + } +} + +class SugarDecorator extends CoffeeDecorator { + public SugarDecorator(Coffee coffee) { + super(coffee); + } + + @Override + public double cost() { + return coffee.cost() + 0.2; + } + + @Override + public String description() { + return coffee.description() + ", sugar"; + } +} + +class VlcPlayer implements AdvancedMediaPlayer { + @Override + public void playVlc(String filename) { + System.out.println("Playing vlc file: " + filename); + } + + @Override + public void playMp4(String filename) { + // Do nothing + } +} + +class Mp4Player implements AdvancedMediaPlayer { + @Override + public void playVlc(String filename) { + // Do nothing + } + + @Override + public void playMp4(String filename) { + System.out.println("Playing mp4 file: " + filename); + } +} + +class MediaAdapter implements MediaPlayer { + AdvancedMediaPlayer advancedPlayer; + + public MediaAdapter(String audioType) { + if (audioType.equalsIgnoreCase("vlc")) { + advancedPlayer = new VlcPlayer(); + } else if (audioType.equalsIgnoreCase("mp4")) { + advancedPlayer = new Mp4Player(); + } + } + + @Override + public void play(String filename) { + if (filename.endsWith(".vlc")) { + advancedPlayer.playVlc(filename); + } else if (filename.endsWith(".mp4")) { + advancedPlayer.playMp4(filename); + } + } +} + +class AudioPlayer implements MediaPlayer { + MediaAdapter mediaAdapter; + + @Override + public void play(String filename) { + if (filename.endsWith(".mp3")) { + System.out.println("Playing mp3 file: " + filename); + } else if (filename.endsWith(".vlc") || filename.endsWith(".mp4")) { + mediaAdapter = new MediaAdapter(filename.substring(filename.lastIndexOf('.') + 1)); + mediaAdapter.play(filename); + } else { + System.out.println("Invalid format"); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/combined/Two/Main.java b/languages/java-cpg/src/test/resources/java/combined/Two/Main.java new file mode 100644 index 0000000000..a5be2b2528 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/Two/Main.java @@ -0,0 +1,412 @@ +// Main.java +//@author Anthropic Claude Sonnet 4.5 + +public class Main { + public static void main(String[] args) { + System.out.println("=== Inheritance Dead Code Analysis ===\n"); + + // Only using Dog, never Cat or Bird + Animal animal = new Dog("Buddy"); + animal.makeSound(); + animal.eat(); + + // Using only StandardVehicle + Vehicle vehicle = new StandardVehicle("Car", 4); + vehicle.start(); + vehicle.displayInfo(); + + // Using only BasicEmployee + Employee emp = new BasicEmployee("John", 50000); + emp.work(); + emp.displaySalary(); + + // Dead code: ElectricVehicle never instantiated but condition checks for it + if (vehicle instanceof ElectricVehicle) { + ((ElectricVehicle) vehicle).charge(); + } + + System.out.println("\n=== Program Complete ==="); + } +} + +// Base Animal class +abstract class Animal { + protected String name; + + public Animal(String name) { + this.name = name; + } + + public abstract void makeSound(); + + public void eat() { + System.out.println(name + " is eating"); + } + + // Dead method: never called + public void sleep() { + System.out.println(name + " is sleeping"); + } + + // Dead method: never overridden or called + public void move() { + System.out.println(name + " is moving"); + } +} + +// Used class +class Dog extends Animal { + public Dog(String name) { + super(name); + } + + @Override + public void makeSound() { + System.out.println(name + " barks: Woof!"); + } + + // Dead method: never called + public void fetch() { + System.out.println(name + " is fetching"); + } + + // Dead method: never called + private void wagTail() { + System.out.println(name + " is wagging tail"); + } +} + +// DEAD CLASS: Never instantiated +class Cat extends Animal { + private int lives = 9; + + public Cat(String name) { + super(name); + } + + @Override + public void makeSound() { + System.out.println(name + " meows: Meow!"); + } + + public void purr() { + System.out.println(name + " is purring"); + } + + private void scratch() { + System.out.println(name + " is scratching"); + } + + public int getLives() { + return lives; + } +} + +// DEAD CLASS: Never instantiated +class Bird extends Animal { + private boolean canFly; + + public Bird(String name, boolean canFly) { + super(name); + this.canFly = canFly; + } + + @Override + public void makeSound() { + System.out.println(name + " chirps: Tweet!"); + } + + public void fly() { + if (canFly) { + System.out.println(name + " is flying"); + } else { + System.out.println(name + " cannot fly"); + } + } + + private void buildNest() { + System.out.println(name + " is building a nest"); + } +} + +// Base Vehicle class +abstract class Vehicle { + protected String type; + protected int wheels; + + public Vehicle(String type, int wheels) { + this.type = type; + this.wheels = wheels; + } + + public abstract void start(); + + public void displayInfo() { + System.out.println("Vehicle: " + type + ", Wheels: " + wheels); + } + + // Dead method: never called + public void stop() { + System.out.println(type + " is stopping"); + } + + // Dead method: never called + protected void honk() { + System.out.println(type + " is honking"); + } +} + +// Used class +class StandardVehicle extends Vehicle { + public StandardVehicle(String type, int wheels) { + super(type, wheels); + } + + @Override + public void start() { + System.out.println(type + " engine started"); + } + + // Dead method: never called + public void refuel() { + System.out.println(type + " is refueling"); + } +} + +// DEAD CLASS: Never instantiated +class ElectricVehicle extends Vehicle { + private int batteryLevel = 100; + + public ElectricVehicle(String type, int wheels) { + super(type, wheels); + } + + @Override + public void start() { + System.out.println(type + " electric motor started"); + } + + public void charge() { + batteryLevel = 100; + System.out.println(type + " is charging"); + } + + public int getBatteryLevel() { + return batteryLevel; + } + + private void checkBattery() { + System.out.println("Battery at " + batteryLevel + "%"); + } +} + +// DEAD CLASS: Never instantiated +class HybridVehicle extends Vehicle { + private boolean electricMode = false; + + public HybridVehicle(String type, int wheels) { + super(type, wheels); + } + + @Override + public void start() { + System.out.println(type + " hybrid system started"); + } + + public void switchMode() { + electricMode = !electricMode; + System.out.println("Switched to " + (electricMode ? "electric" : "gas") + " mode"); + } + + private void optimizeEfficiency() { + System.out.println("Optimizing fuel efficiency"); + } +} + +// Base Employee class +abstract class Employee { + protected String name; + protected double salary; + + public Employee(String name, double salary) { + this.name = name; + this.salary = salary; + } + + public abstract void work(); + + public void displaySalary() { + System.out.println(name + "'s salary: $" + salary); + } + + // Dead method: never called + public void attendMeeting() { + System.out.println(name + " is attending a meeting"); + } + + // Dead method: never called + protected void takeBreak() { + System.out.println(name + " is taking a break"); + } +} + +// Used class +class BasicEmployee extends Employee { + public BasicEmployee(String name, double salary) { + super(name, salary); + } + + @Override + public void work() { + System.out.println(name + " is working on tasks"); + } + + // Dead method: never called + public void submitReport() { + System.out.println(name + " submitted a report"); + } +} + +// DEAD CLASS: Never instantiated +class Manager extends Employee { + private int teamSize; + + public Manager(String name, double salary, int teamSize) { + super(name, salary); + this.teamSize = teamSize; + } + + @Override + public void work() { + System.out.println(name + " is managing team of " + teamSize); + } + + public void conductReview() { + System.out.println(name + " is conducting performance reviews"); + } + + private void planStrategy() { + System.out.println(name + " is planning strategy"); + } +} + +// DEAD CLASS: Never instantiated +class Developer extends Employee { + private String programmingLanguage; + + public Developer(String name, double salary, String language) { + super(name, salary); + this.programmingLanguage = language; + } + + @Override + public void work() { + System.out.println(name + " is coding in " + programmingLanguage); + } + + public void debugCode() { + System.out.println(name + " is debugging code"); + } + + public void commitCode() { + System.out.println(name + " is committing code"); + } + + private void reviewPullRequest() { + System.out.println(name + " is reviewing pull requests"); + } +} + +// DEAD CLASS: Never instantiated or extended +abstract class Shape { + protected String color; + + public Shape(String color) { + this.color = color; + } + + public abstract double getArea(); + + public abstract double getPerimeter(); + + public void displayColor() { + System.out.println("Color: " + color); + } +} + +// DEAD CLASS: Never instantiated +class Circle extends Shape { + private double radius; + + public Circle(String color, double radius) { + super(color); + this.radius = radius; + } + + @Override + public double getArea() { + return Math.PI * radius * radius; + } + + @Override + public double getPerimeter() { + return 2 * Math.PI * radius; + } + + public double getRadius() { + return radius; + } +} + +// DEAD CLASS: Never instantiated +class Rectangle extends Shape { + private double width; + private double height; + + public Rectangle(String color, double width, double height) { + super(color); + this.width = width; + this.height = height; + } + + @Override + public double getArea() { + return width * height; + } + + @Override + public double getPerimeter() { + return 2 * (width + height); + } + + public boolean isSquare() { + return width == height; + } +} + +// DEAD CLASS: Never instantiated +class Triangle extends Shape { + private double side1, side2, side3; + + public Triangle(String color, double s1, double s2, double s3) { + super(color); + this.side1 = s1; + this.side2 = s2; + this.side3 = s3; + } + + @Override + public double getArea() { + double s = getPerimeter() / 2; + return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3)); + } + + @Override + public double getPerimeter() { + return side1 + side2 + side3; + } + + public boolean isEquilateral() { + return side1 == side2 && side2 == side3; + } +} diff --git a/languages/java-cpg/src/test/resources/java/combined/multiInheritance1/Main.java b/languages/java-cpg/src/test/resources/java/combined/multiInheritance1/Main.java new file mode 100644 index 0000000000..9b7187d60f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/multiInheritance1/Main.java @@ -0,0 +1,164 @@ +interface Playable { + void play(String filename); + + default void pause() { + System.out.println("Paused"); + } +} + +interface Trainable { + static void displayTrainingTips() { + System.out.println("Use positive reinforcement"); + } + + void train(String command); +} + +public class Main { + public static void main(String[] args) { + System.out.println("=== Inheritance Dead Code Analysis ===\n"); + + Dog dog = new Dog(); + dog.play("video.mp4"); + dog.swim(); + dog.bark(); + + System.out.println("\n=== Program Complete ==="); + } +} + +abstract class LivingBeing { + protected String species; + + public abstract void makeSound(); + + public final void live() { + System.out.println("Living being is alive"); + } +} + +class Animal extends LivingBeing implements Playable { + private int age; + + public Animal() { + this.species = "Unknown"; + this.age = 0; + } + + public Animal(String species, int age) { + this.species = species; + this.age = age; + } + + @Override + public void play(String filename) { + System.out.println("Animal playing: " + filename); + } + + public void breathe() { + System.out.println(species + " is breathing"); + } + + @Override + public void makeSound() { + System.out.println("Generic animal sound"); + } + + protected void sleep() { + System.out.println("Animal is sleeping"); + } +} + +class LandAnimal extends Animal { + private int numberOfLegs; + + public LandAnimal() { + super(); + this.numberOfLegs = 4; + } + + public LandAnimal(String species, int age, int legs) { + super(species, age); + this.numberOfLegs = legs; + } + + public void swim() { + this.breathe(); + System.out.println("Land animal swimming with " + numberOfLegs + " legs"); + } + + @Override + public void makeSound() { + super.makeSound(); + System.out.println("Land-based sound"); + } +} + +class Dog extends LandAnimal implements Trainable { + private String breed; + + public Dog() { + super("Canine", 0, 4); + this.breed = "Generic"; + } + + public Dog(String breed, int age) { + super("Canine", age, 4); + this.breed = breed; + } + + @Override + public void makeSound() { + System.out.println("Woof!"); + } + + public void bark() { + makeSound(); + } + + @Override + public void train(String command) { + System.out.println("Dog learning: " + command); + } + + @Override + public void play(String filename) { + super.play(filename); + System.out.println("Dog enjoys playing"); + } +} + +class Husky extends Dog { + private boolean canPullSled; + + public Husky() { + super("Husky", 0); + this.canPullSled = true; + } + + public Husky(int age) { + super("Husky", age); + this.canPullSled = true; + } + + @Override + public void makeSound() { + System.out.println("Awoo! (Husky howl)"); + } + + @Override + public void train(String command) { + super.train(command); + System.out.println("Husky mastered: " + command); + } + + public void howl() { + makeSound(); + } + + public void pullSled() { + if (canPullSled) { + System.out.println("Husky is pulling a sled"); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/combined/multiInheritance2/Main.java b/languages/java-cpg/src/test/resources/java/combined/multiInheritance2/Main.java new file mode 100644 index 0000000000..e779335a7a --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/multiInheritance2/Main.java @@ -0,0 +1,163 @@ +interface Playable { + void play(String filename); + + default void pause() { + System.out.println("Paused"); + } +} + +interface Trainable { + static void displayTrainingTips() { + System.out.println("Use positive reinforcement"); + } + + void train(String command); +} + +public class Main { + public static void main(String[] args) { + System.out.println("=== Inheritance Dead Code Analysis ===\n"); + + Husky husky = new Husky(); + husky.pullSled(); + husky.howl(); + + System.out.println("\n=== Program Complete ==="); + } +} + +abstract class LivingBeing { + protected String species; + + public abstract void makeSound(); + + public final void live() { + System.out.println("Living being is alive"); + } +} + +class Animal extends LivingBeing implements Playable { + private int age; + + public Animal() { + this.species = "Unknown"; + this.age = 0; + } + + public Animal(String species, int age) { + this.species = species; + this.age = age; + } + + @Override + public void play(String filename) { + System.out.println("Animal playing: " + filename); + } + + public void breathe() { + System.out.println(species + " is breathing"); + } + + @Override + public void makeSound() { + System.out.println("Generic animal sound"); + } + + protected void sleep() { + System.out.println("Animal is sleeping"); + } +} + +class LandAnimal extends Animal { + private int numberOfLegs; + + public LandAnimal() { + super(); + this.numberOfLegs = 4; + } + + public LandAnimal(String species, int age, int legs) { + super(species, age); + this.numberOfLegs = legs; + } + + public void swim() { + this.breathe(); + System.out.println("Land animal swimming with " + numberOfLegs + " legs"); + } + + @Override + public void makeSound() { + super.makeSound(); + System.out.println("Land-based sound"); + } +} + +class Dog extends LandAnimal implements Trainable { + private String breed; + + public Dog() { + super("Canine", 0, 4); + this.breed = "Generic"; + } + + public Dog(String breed, int age) { + super("Canine", age, 4); + this.breed = breed; + } + + @Override + public void makeSound() { + System.out.println("Woof!"); + } + + public void bark() { + makeSound(); + } + + @Override + public void train(String command) { + System.out.println("Dog learning: " + command); + } + + @Override + public void play(String filename) { + super.play(filename); + System.out.println("Dog enjoys playing"); + } +} + +class Husky extends Dog { + private boolean canPullSled; + + public Husky() { + super("Husky", 0); + this.canPullSled = true; + } + + public Husky(int age) { + super("Husky", age); + this.canPullSled = true; + } + + @Override + public void makeSound() { + System.out.println("Awoo! (Husky howl)"); + } + + @Override + public void train(String command) { + super.train(command); + System.out.println("Husky mastered: " + command); + } + + public void howl() { + makeSound(); + } + + public void pullSled() { + if (canPullSled) { + System.out.println("Husky is pulling a sled"); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/combined/multiInheritance3/Main.java b/languages/java-cpg/src/test/resources/java/combined/multiInheritance3/Main.java new file mode 100644 index 0000000000..3189e0cfe1 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/combined/multiInheritance3/Main.java @@ -0,0 +1,162 @@ +interface Playable { + void play(String filename); + + default void pause() { + System.out.println("Paused"); + } +} + +interface Trainable { + static void displayTrainingTips() { + System.out.println("Use positive reinforcement"); + } + + void train(String command); +} + +public class Main { + public static void main(String[] args) { + System.out.println("=== Inheritance Dead Code Analysis ===\n"); + + Animal polymorphicDog = new Dog(); + polymorphicDog.breathe(); + + System.out.println("\n=== Program Complete ==="); + } +} + +abstract class LivingBeing { + protected String species; + + public abstract void makeSound(); + + public final void live() { + System.out.println("Living being is alive"); + } +} + +class Animal extends LivingBeing implements Playable { + private int age; + + public Animal() { + this.species = "Unknown"; + this.age = 0; + } + + public Animal(String species, int age) { + this.species = species; + this.age = age; + } + + @Override + public void play(String filename) { + System.out.println("Animal playing: " + filename); + } + + public void breathe() { + System.out.println(species + " is breathing"); + } + + @Override + public void makeSound() { + System.out.println("Generic animal sound"); + } + + protected void sleep() { + System.out.println("Animal is sleeping"); + } +} + +class LandAnimal extends Animal { + private int numberOfLegs; + + public LandAnimal() { + super(); + this.numberOfLegs = 4; + } + + public LandAnimal(String species, int age, int legs) { + super(species, age); + this.numberOfLegs = legs; + } + + public void swim() { + this.breathe(); + System.out.println("Land animal swimming with " + numberOfLegs + " legs"); + } + + @Override + public void makeSound() { + super.makeSound(); + System.out.println("Land-based sound"); + } +} + +class Dog extends LandAnimal implements Trainable { + private String breed; + + public Dog() { + super("Canine", 0, 4); + this.breed = "Generic"; + } + + public Dog(String breed, int age) { + super("Canine", age, 4); + this.breed = breed; + } + + @Override + public void makeSound() { + System.out.println("Woof!"); + } + + public void bark() { + makeSound(); + } + + @Override + public void train(String command) { + System.out.println("Dog learning: " + command); + } + + @Override + public void play(String filename) { + super.play(filename); + System.out.println("Dog enjoys playing"); + } +} + +class Husky extends Dog { + private boolean canPullSled; + + public Husky() { + super("Husky", 0); + this.canPullSled = true; + } + + public Husky(int age) { + super("Husky", age); + this.canPullSled = true; + } + + @Override + public void makeSound() { + System.out.println("Awoo! (Husky howl)"); + } + + @Override + public void train(String command) { + super.train(command); + System.out.println("Husky mastered: " + command); + } + + public void howl() { + makeSound(); + } + + public void pullSled() { + if (canPullSled) { + System.out.println("Husky is pulling a sled"); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/constantClass/SubmissionA/edu/kit/kastel/sdq/jplag/example/defining/DefiningClass.java b/languages/java-cpg/src/test/resources/java/constantClass/SubmissionA/edu/kit/kastel/sdq/jplag/example/defining/DefiningClass.java new file mode 100644 index 0000000000..4859fa5d7c --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/constantClass/SubmissionA/edu/kit/kastel/sdq/jplag/example/defining/DefiningClass.java @@ -0,0 +1,22 @@ +package submissiona.edu.kit.kastel.sdq.jplag.example.defining; + +public class DefiningClass { + + /** + * This constant is only used in UsingClass. + */ + public static final String MY_CONSTANT = "This is a constant"; + + /** + * This constant is used in multiple classes. + */ + public static final String MY_CONSTANT_MULTI_USE = "This is a constant"; + + public static void printTheConstant() { + for (int i = 0; i <= 10; i++) { + System.out.println("Here comes a constant:"); + System.out.println(MY_CONSTANT_MULTI_USE); + } + } + +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/constantClass/SubmissionA/edu/kit/kastel/sdq/jplag/example/using/UsingClass.java b/languages/java-cpg/src/test/resources/java/constantClass/SubmissionA/edu/kit/kastel/sdq/jplag/example/using/UsingClass.java new file mode 100644 index 0000000000..574075776f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/constantClass/SubmissionA/edu/kit/kastel/sdq/jplag/example/using/UsingClass.java @@ -0,0 +1,17 @@ + +package submissiona.edu.kit.kastel.sdq.jplag.example.using; +import submissiona.edu.kit.kastel.sdq.jplag.example.defining.DefiningClass; + +public class UsingClass { + + public static void main(String[] args) { + System.out.println(DefiningClass.MY_CONSTANT); + System.out.println(DefiningClass.MY_CONSTANT_MULTI_USE); + + while (true) { + boolean b = true; + } + } + + +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/constantClass/SubmissionB/kit/edu/kastel/sdq/jplag/example/defining/DefiningClass.java b/languages/java-cpg/src/test/resources/java/constantClass/SubmissionB/kit/edu/kastel/sdq/jplag/example/defining/DefiningClass.java new file mode 100644 index 0000000000..20adb84092 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/constantClass/SubmissionB/kit/edu/kastel/sdq/jplag/example/defining/DefiningClass.java @@ -0,0 +1,19 @@ +package submissionb.edu.kit.kastel.sdq.jplag.example.defining; + +public class DefiningClass { + + /** + * This constant is used in multiple classes. + */ + public static final String MY_CONSTANT_MULTI_USE = "This is a constant"; + + public static void printTheConstant() { + + for (int i = 0; i <= 10; i++) { + System.out.println("Here comes a constant:"); + System.out.println(MY_CONSTANT_MULTI_USE); + } + + } + +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/constantClass/SubmissionB/kit/edu/kastel/sdq/jplag/example/using/UsingClass.java b/languages/java-cpg/src/test/resources/java/constantClass/SubmissionB/kit/edu/kastel/sdq/jplag/example/using/UsingClass.java new file mode 100644 index 0000000000..b0d7f30296 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/constantClass/SubmissionB/kit/edu/kastel/sdq/jplag/example/using/UsingClass.java @@ -0,0 +1,20 @@ +package submissionb.edu.kit.kastel.sdq.jplag.example.using; +import submissionb.edu.kit.kastel.sdq.jplag.example.defining.DefiningClass; + +public class UsingClass { + + /** + * This constant is only used in UsingClass. + */ + public static final String MY_CONSTANT = "This is a constant"; + + public static void main(String[] args) { + System.out.println(MY_CONSTANT); + System.out.println(DefiningClass.MY_CONSTANT_MULTI_USE); + + while (true) { + boolean b = true; + } + } + +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/dfgLinearization/SubmissionA.java b/languages/java-cpg/src/test/resources/java/dfgLinearization/SubmissionA.java new file mode 100644 index 0000000000..f8ab5b5e61 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/dfgLinearization/SubmissionA.java @@ -0,0 +1,19 @@ +package edu.kit.kastel.sdq.jplag.examples; + +public class DfgLinearization { + + public int square() { + int i = 1; + int j = 10; + boolean debug = false; + System.out.println(++i); + while (i <= 10) { + int squared = i*i; + i++; + j--; + System.out.println(squared); + } + while (j <= 10) j++; + } + +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/dfgLinearization/SubmissionB.java b/languages/java-cpg/src/test/resources/java/dfgLinearization/SubmissionB.java new file mode 100644 index 0000000000..8a62297674 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/dfgLinearization/SubmissionB.java @@ -0,0 +1,18 @@ +package edu.kit.kastel.sdq.jplag.examples; + +/** + * Taken from Moritz Brödel + */ +public class DfgLinearization { + public int square() { + int i = 1; + //boolean debug removed by singleUseVariable transformation + System.out.println(++i); + while (i <= 10) { + int square = i * i; + System.out.println(square); + i++; + } + } + +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/for2While/SubmissionA.java b/languages/java-cpg/src/test/resources/java/for2While/SubmissionA.java new file mode 100644 index 0000000000..bc57961b19 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/for2While/SubmissionA.java @@ -0,0 +1,18 @@ + +class For2While { + + private int multiply(int factor1, int factor2) { + int product = 0; + for (int i = 0, j = 3; factor2 > 0; factor2--) { // <- i and j are not used + product += factor1; + String a = "Also not used."; // <- a is not used + } + double d = 4.0; // <- d is not used + int productCopy = product; + return productCopy; + } + + public static void main(String[] args) { + System.out.println(new UnusedVariableDeclaration().multiply(17, 42)); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/for2While/SubmissionB.java b/languages/java-cpg/src/test/resources/java/for2While/SubmissionB.java new file mode 100644 index 0000000000..3dbc3b8344 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/for2While/SubmissionB.java @@ -0,0 +1,24 @@ + +class For2While { + + private int multiply(int factor1, int factor2) { + int product = 0; + { + int i = 0, j = 3; + while (factor2 > 0) { + { + product += factor1; + String a = "Also not used."; + } + factor2--; + } + } + double d = 4.0; // <- d is not used + int productCopy = product; + return productCopy; + } + + public static void main(String[] args) { + System.out.println(new UnusedVariableDeclaration().multiply(17, 42)); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/gen/0/NegatedIfCondition-transformed.java b/languages/java-cpg/src/test/resources/java/gen/0/NegatedIfCondition-transformed.java new file mode 100644 index 0000000000..86072cdee3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/gen/0/NegatedIfCondition-transformed.java @@ -0,0 +1,39 @@ +package de.jplag.gen.plag0; +class NegatedIfCondition { + public int fourCases(boolean a, boolean b) { + if (!a) { + if (!b) { + return NegatedIfConditionConstants.CONSTANT_1; + } else { + int EXTRACTED_3 = 1; + return EXTRACTED_3; + } + } else if (!b) { + int EXTRACTED_2 = 2; + return EXTRACTED_2; + } else { + int EXTRACTED_1 = 3; + return EXTRACTED_1; + } + } + + public static void main(String[] args) { + int zero = NegatedIfConditionConstants.CONSTANT_2; + boolean EXTRACTED_5 = false; + boolean EXTRACTED_6 = true; + int EXTRACTED_4 = NegatedIfConditionConstants.CONSTANT_3.fourCases(EXTRACTED_5, EXTRACTED_6); + int one = EXTRACTED_4; + NegatedIfCondition EXTRACTED_8 = new NegatedIfCondition(); + boolean EXTRACTED_9 = true; + boolean EXTRACTED_10 = false; + int EXTRACTED_7 = EXTRACTED_8.fourCases(EXTRACTED_9, EXTRACTED_10); + int two = EXTRACTED_7; + NegatedIfCondition EXTRACTED_12 = new NegatedIfCondition(); + boolean EXTRACTED_13 = true; + int EXTRACTED_11 = EXTRACTED_12.fourCases(NegatedIfConditionConstants.CONSTANT_4, EXTRACTED_13); + int three = EXTRACTED_11; + String EXTRACTED_15 = "%d%d%d%d"; + String EXTRACTED_14 = EXTRACTED_15.formatted(zero, one, two, three); + System.out.println(EXTRACTED_14); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/gen/0/NegatedIfConditionConstants.java b/languages/java-cpg/src/test/resources/java/gen/0/NegatedIfConditionConstants.java new file mode 100644 index 0000000000..42c993e8e6 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/gen/0/NegatedIfConditionConstants.java @@ -0,0 +1,10 @@ +package de.jplag.gen.plag0; +class NegatedIfConditionConstants { + public static final int CONSTANT_1 = 0; + + public static final int CONSTANT_2 = new NegatedIfCondition().fourCases(false, false); + + public static final NegatedIfCondition CONSTANT_3 = new NegatedIfCondition(); + + public static final boolean CONSTANT_4 = true; +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/gen/1/NegatedIfCondition-transformed.java b/languages/java-cpg/src/test/resources/java/gen/1/NegatedIfCondition-transformed.java new file mode 100644 index 0000000000..f040fe1d3b --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/gen/1/NegatedIfCondition-transformed.java @@ -0,0 +1,45 @@ +package de.jplag.gen.plag1; + +import java.util.*; +class NegatedIfCondition { + public int fourCases(boolean a, boolean b) { + if (!a) { + if (!b) { + int EXTRACTED_2 = 0; + return EXTRACTED_2; + } else { + int EXTRACTED_1 = 1; + return EXTRACTED_1; + } + } else if (!b) { + return NegatedIfConditionConstants.CONSTANT_2; + } else { + return NegatedIfConditionConstants.CONSTANT_1; + } + } + + public static void main(String[] args) { + NegatedIfCondition EXTRACTED_4 = new NegatedIfCondition(); + Optional EXTRACTED_5_OPT = Optional.of(false); + Optional EXTRACTED_6_OPT = Optional.of(false); + int EXTRACTED_3 = EXTRACTED_4.fourCases(EXTRACTED_5_OPT.get(), EXTRACTED_6_OPT.get()); + Optional zero_OPT = Optional.of(EXTRACTED_3); + NegatedIfCondition EXTRACTED_8 = new NegatedIfCondition(); + Optional EXTRACTED_9_OPT = Optional.of(false); + boolean EXTRACTED_10 = true; + int EXTRACTED_7 = EXTRACTED_8.fourCases(EXTRACTED_9_OPT.get(), EXTRACTED_10); + int one = EXTRACTED_7; + NegatedIfCondition EXTRACTED_12 = new NegatedIfCondition(); + boolean EXTRACTED_13 = false; + int EXTRACTED_11 = EXTRACTED_12.fourCases(NegatedIfConditionConstants.CONSTANT_3, EXTRACTED_13); + Optional two_OPT = Optional.of(EXTRACTED_11); + Optional EXTRACTED_15_OPT = Optional.of(new NegatedIfCondition()); + boolean EXTRACTED_16 = true; + boolean EXTRACTED_17 = true; + int EXTRACTED_14 = EXTRACTED_15_OPT.get().fourCases(EXTRACTED_16, EXTRACTED_17); + int three = EXTRACTED_14; + Optional EXTRACTED_19_OPT = Optional.of("%d%d%d%d"); + String EXTRACTED_18 = EXTRACTED_19_OPT.get().formatted(zero_OPT.get(), one, two_OPT.get(), three); + System.out.println(EXTRACTED_18); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/gen/1/NegatedIfConditionConstants.java b/languages/java-cpg/src/test/resources/java/gen/1/NegatedIfConditionConstants.java new file mode 100644 index 0000000000..2bd20441ea --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/gen/1/NegatedIfConditionConstants.java @@ -0,0 +1,8 @@ +package de.jplag.gen.plag1; +class NegatedIfConditionConstants { + public static final int CONSTANT_1 = 3; + + public static final int CONSTANT_2 = 2; + + public static final boolean CONSTANT_3 = true; +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/gen/2/NegatedIfCondition-transformed.java b/languages/java-cpg/src/test/resources/java/gen/2/NegatedIfCondition-transformed.java new file mode 100644 index 0000000000..894ffc8a7c --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/gen/2/NegatedIfCondition-transformed.java @@ -0,0 +1,55 @@ +package de.jplag.gen.plag2; + +import java.util.Optional; +class NegatedIfCondition { + public boolean NegatedIfCondition_callMe_not() { + throw new RuntimeException("You'd better not have called me!"); + } + + public int fourCases(boolean a, boolean b) { + if (!a) + if (!b) { + int EXTRACTED_12 = 0; + int EXTRACTED_10 = EXTRACTED_12; + int EXTRACTED_4 = EXTRACTED_10; + return EXTRACTED_4; + } else { + Optional EXTRACTED_11_OPT = Optional.of(1); + int EXTRACTED_9 = EXTRACTED_11_OPT.get(); + Optional EXTRACTED_3_OPT = Optional.of(EXTRACTED_9); + return EXTRACTED_3_OPT.get(); + } + else if (!b) { + int EXTRACTED_8 = 2; + int EXTRACTED_6 = EXTRACTED_8; + int EXTRACTED_2 = EXTRACTED_6; + return EXTRACTED_2; + } else { + Optional EXTRACTED_7_OPT = Optional.of(3); + int EXTRACTED_5 = EXTRACTED_7_OPT.get(); + Optional EXTRACTED_1_OPT = Optional.of(EXTRACTED_5); + return EXTRACTED_1_OPT.get(); + } + } + + public static void main(String[] args) { + Optional EXTRACTED_14_OPT = Optional.of(new NegatedIfCondition()); + boolean EXTRACTED_15 = false; + int EXTRACTED_13 = EXTRACTED_14_OPT.get().fourCases(EXTRACTED_15, NegatedIfConditionConstants.CONSTANT_1); + int zero = EXTRACTED_13; + NegatedIfCondition EXTRACTED_17 = new NegatedIfCondition(); + Optional EXTRACTED_18_OPT = Optional.of(false); + boolean EXTRACTED_19 = true; + int EXTRACTED_16 = EXTRACTED_17.fourCases(EXTRACTED_18_OPT.get(), EXTRACTED_19); + int one = EXTRACTED_16; + int two = NegatedIfConditionConstants.CONSTANT_2; + NegatedIfCondition EXTRACTED_21 = new NegatedIfCondition(); + Optional EXTRACTED_22_OPT = Optional.of(true); + boolean EXTRACTED_23 = true; + int EXTRACTED_20 = EXTRACTED_21.fourCases(EXTRACTED_22_OPT.get(), EXTRACTED_23); + int three = EXTRACTED_20; + String EXTRACTED_25 = "%d%d%d%d"; + String EXTRACTED_24 = EXTRACTED_25.formatted(zero, one, two, three); + System.out.println(EXTRACTED_24); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/gen/2/NegatedIfConditionConstants.java b/languages/java-cpg/src/test/resources/java/gen/2/NegatedIfConditionConstants.java new file mode 100644 index 0000000000..79ae8be62f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/gen/2/NegatedIfConditionConstants.java @@ -0,0 +1,10 @@ +package de.jplag.gen.plag2; +class NegatedIfConditionConstants { + public static final boolean CONSTANT_1 = false; + + public static final int CONSTANT_2 = new NegatedIfCondition().fourCases(CONSTANT_3, false); + + public static final boolean CONSTANT_3 = CONSTANT_4; + + public static final boolean CONSTANT_4 = true; +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/gen/orig/NegatedIfCondition.java b/languages/java-cpg/src/test/resources/java/gen/orig/NegatedIfCondition.java new file mode 100644 index 0000000000..e96b5a8e23 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/gen/orig/NegatedIfCondition.java @@ -0,0 +1,19 @@ +package de.jplag.gen.orig; +class NegatedIfCondition { + + public int fourCases(boolean a, boolean b) { + if (a) { + if (b) return 3; + else return 2; + } else if (b) return 1; + else return 0; + } + + public static void main(String[] args) { + int zero = new NegatedIfCondition().fourCases(false, false); + int one = new NegatedIfCondition().fourCases(false, true); + int two = new NegatedIfCondition().fourCases(true, false); + int three = new NegatedIfCondition().fourCases(true, true); + System.out.println("%d%d%d%d".formatted(zero,one,two,three)); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/if/IfWithBraces.java b/languages/java-cpg/src/test/resources/java/if/IfWithBraces.java new file mode 100644 index 0000000000..e8492ee5e5 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/if/IfWithBraces.java @@ -0,0 +1,15 @@ +package java; + +import java.util.Arrays; + +public class IfWithBraces { + public static void main(String[] args) { + if (args == null) { + throw new IllegalArgumentException(); + } else if (args.length > 1) { + System.out.println(Arrays.toString(args)); + } else { + System.out.println(args[0]); + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/if/IfWithoutBraces.java b/languages/java-cpg/src/test/resources/java/if/IfWithoutBraces.java new file mode 100644 index 0000000000..196d646dea --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/if/IfWithoutBraces.java @@ -0,0 +1,14 @@ +package java; + +import java.util.Arrays; + +public class IfWithoutBraces { + public static void main(String[] args) { + if (args == null) + throw new IllegalArgumentException(); + else if (args.length > 1) + System.out.println(Arrays.toString(args)); + else + System.out.println(args[0]); + } +} diff --git a/languages/java-cpg/src/test/resources/java/negatedIf/SubmissionA.java b/languages/java-cpg/src/test/resources/java/negatedIf/SubmissionA.java new file mode 100644 index 0000000000..4861e955e2 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/negatedIf/SubmissionA.java @@ -0,0 +1,18 @@ +class NegatedIfCondition { + + public int fourCases(boolean a, boolean b) { + if (!a) { + if (!b) return 0; + else return 1; + } else if (!b) return 2; + else return 3; + } + + public static void main(String[] args) { + int zero = new NegatedIfCondition().fourCases(false, false); + int one = new NegatedIfCondition().fourCases(false, true); + int two = new NegatedIfCondition().fourCases(true, false); + int three = new NegatedIfCondition().fourCases(true, true); + System.out.println("%d%d%d%d".formatted(zero,one,two,three)); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/negatedIf/SubmissionB.java b/languages/java-cpg/src/test/resources/java/negatedIf/SubmissionB.java new file mode 100644 index 0000000000..f47fe9c68d --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/negatedIf/SubmissionB.java @@ -0,0 +1,19 @@ + +class NegatedIfCondition { + + public int fourCases(boolean a, boolean b) { + if (a) { + if (b) return 3; + else return 2; + } else if (b) return 1; + else return 0; + } + + public static void main(String[] args) { + int zero = new NegatedIfCondition().fourCases(false, false); + int one = new NegatedIfCondition().fourCases(false, true); + int two = new NegatedIfCondition().fourCases(true, false); + int three = new NegatedIfCondition().fourCases(true, true); + System.out.println("%d%d%d%d".formatted(zero,one,two,three)); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/progpedia/.gitkeep b/languages/java-cpg/src/test/resources/java/progpedia/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/languages/java-cpg/src/test/resources/java/progpedia/00000006/ACCEPTED/00130_00001/A.java b/languages/java-cpg/src/test/resources/java/progpedia/00000006/ACCEPTED/00130_00001/A.java new file mode 100644 index 0000000000..ece4fa1830 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/progpedia/00000006/ACCEPTED/00130_00001/A.java @@ -0,0 +1,98 @@ +import java.util.Scanner; + +class Node{ + + int val; Node next; + Node(int v, Node n){ + val=v; next=n; + } +} + +class List{ + + Node first; + int size; + List(){ first=null; size=0; } + + void add(int v, int index){ + + if(index==0) + first=new Node(v,first); + else{ + Node cursor=first; + for(int i=0;i0) + invert.push(pop()); + for(Node cursor=invert.top;cursor!=null;cursor=cursor.next) + System.out.println(cursor.val); + } +} +public class A { + + public static Scanner in=new Scanner(System.in); + + public static void main(String[] args) { + + Stack caminho=new Stack(); + int v=in.nextInt(); + while(v!=0){ + if(!caminho.exist(v)) + caminho.push(v); + else{ + while(caminho.top.val!=v) + caminho.pop(); + } + v=in.nextInt(); + } + caminho.print(); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/progpedia/00000006/WRONG_ANSWER/00217_00001/cigarras.java b/languages/java-cpg/src/test/resources/java/progpedia/00000006/WRONG_ANSWER/00217_00001/cigarras.java new file mode 100644 index 0000000000..f46f8b7daa --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/progpedia/00000006/WRONG_ANSWER/00217_00001/cigarras.java @@ -0,0 +1,44 @@ +import java.util.Scanner; + + +public class cigarras { + + public static void analisa_caminho(int valor, int[] locais){ + int i = 0; + while ( locais[i] != 0) { + if (locais[i]==valor) { + for (int c = i; c= 1; i--) + heapify(i); + } + + int extractMin() { + int vertv = a[1].vert; + swap(1, size); + pos_a[vertv] = posinvalida; // assinala vertv como removido + size--; + heapify(1); + return vertv; + } + + void decreaseKey(int vertv, int newkey) { + + int i = pos_a[vertv]; + a[i].vertkey = newkey; + + while (i > 1 && compare(i, parent(i)) < 0) { + swap(i, parent(i)); + i = parent(i); + } + } + + void insert(int vertv, int key) { + if (sizeMax == size) + new Error("Heap is full\n"); + + size++; + a[size].vert = vertv; + pos_a[vertv] = size; // supondo 1 <= vertv <= n + decreaseKey(vertv, key); // diminui a chave e corrige posicao se + // necessario + } + + void write_heap() { + System.out.printf("Max size: %d\n", sizeMax); + System.out.printf("Current size: %d\n", size); + System.out.printf("(Vert,Key)\n---------\n"); + for (int i = 1; i <= size; i++) + System.out.printf("(%d,%d)\n", a[i].vert, a[i].vertkey); + + System.out.printf("-------\n(Vert,PosVert)\n---------\n"); + + for (int i = 1; i <= sizeMax; i++) + if (pos_valida(pos_a[i])) + System.out.printf("(%d,%d)\n", i, pos_a[i]); + } + + private int parent(int i) { + return i / 2; + } + + private int left(int i) { + return 2 * i; + } + + private int right(int i) { + return 2 * i + 1; + } + + private int compare(int i, int j) { + if (a[i].vertkey < a[j].vertkey) + return -1; + if (a[i].vertkey == a[j].vertkey) + return 0; + return 1; + } + + private void heapify(int i) { + int l, r, smallest; + + l = left(i); + if (l > size) + l = i; + + r = right(i); + if (r > size) + r = i; + + smallest = i; + if (compare(l, smallest) < 0) + smallest = l; + if (compare(r, smallest) < 0) + smallest = r; + + if (i != smallest) { + swap(i, smallest); + heapify(smallest); + } + + } + + private void swap(int i, int j) { + Qnode aux; + pos_a[a[i].vert] = j; + pos_a[a[j].vert] = i; + aux = a[i]; + a[i] = a[j]; + a[j] = aux; + } + + private boolean pos_valida(int i) { + return (i >= 1 && i <= size); + } + + public boolean isEmpty() { + return size == 0; + } +} + +class Arco { + int no_final; + int valor; + + Arco(int fim, int v) { + no_final = fim; + valor = v; + } + + int extremo_final() { + return no_final; + } + + int valor_arco() { + return valor; + } +} + +class No { + int visitado = 0; + LinkedList adjs; + + No() { + adjs = new LinkedList(); + } +} + +class Grafo { + No verts[]; + int nvs, narcos; + + public Grafo(int n) { + nvs = n; + narcos = 0; + verts = new No[n + 1]; + for (int i = 0; i <= n; i++) + verts[i] = new No(); + // para vertices numerados de 1 a n (posicao 0 nao vai ser usada) + } + + public int num_vertices() { + return nvs; + } + + public int num_arcos() { + return narcos; + } + + public LinkedList adjs_no(int i) { + return verts[i].adjs; + } + + public void insert_new_arc(int i, int j, int valor_ij) { + verts[i].adjs.addFirst(new Arco(j, valor_ij)); + narcos++; + } + + public Arco find_arc(int i, int j) { + for (Arco adj : adjs_no(i)) + if (adj.extremo_final() == j) + return adj; + return null; + } +} + +public class main { + + static LinkedList l = new LinkedList(); + + public static void DFS(Grafo g, int s) { + l.add(s); + char result = (char) (s + 'A'); + System.out.print(result); + for (Arco a : g.adjs_no(s)) { + int w = a.extremo_final(); + if (!l.contains(w)) { + DFS(g, w); + } + } + } + + public static void main(String[] args) { + + Scanner stdin = new Scanner(System.in); + + String s1 = stdin.nextLine(); + String s2 = stdin.nextLine(); + + Grafo g = new Grafo(27); + + int index = 0; + char first = 0; + for (int i = 0; i < Math.min(s1.length(), s2.length()); i++) { + char c1 = s1.charAt(i); + char c2 = s2.charAt(i); + if (c1 != c2) { + if (index == 0) + first = c1; + index++; + g.insert_new_arc(c1 - 'A', c2 - 'A', 0); + break; + } + } + + if (index == 0) + System.out.print("Nenhum"); + else + DFS(g, first - 'A'); + System.out.println(); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/progpedia/00000016/ACCEPTED/00092_00002/SopaDeLetras.java b/languages/java-cpg/src/test/resources/java/progpedia/00000016/ACCEPTED/00092_00002/SopaDeLetras.java new file mode 100644 index 0000000000..f6ad53ffd2 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/progpedia/00000016/ACCEPTED/00092_00002/SopaDeLetras.java @@ -0,0 +1,220 @@ + +import java.util.LinkedList; +import java.util.Scanner; +class Qnode { + int vert; + int vertkey; + + Qnode(int v, int key) { + vert = v; + vertkey = key; + } +} + +class Heapmin { + private static int posinvalida = 0; + int sizeMax,size; + + Qnode[] a; + int[] pos_a; + + Heapmin(int vec[], int n) { + a = new Qnode[n + 1]; + pos_a = new int[n + 1]; + sizeMax = n; + size = n; + for (int i = 1; i <= n; i++) { + a[i] = new Qnode(i,vec[i]); + pos_a[i] = i; + } + + for (int i = n/2; i >= 1; i--) + heapify(i); + } + + int extractMin() { + int vertv = a[1].vert; + swap(1,size); + pos_a[vertv] = posinvalida; // assinala vertv como removido + size--; + heapify(1); + return vertv; + } + + void decreaseKey(int vertv, int newkey) { + + int i = pos_a[vertv]; + a[i].vertkey = newkey; + + while (i > 1 && compare(i, parent(i)) < 0) { + swap(i, parent(i)); + i = parent(i); + } + } + + + void insert(int vertv, int key) + { + if (sizeMax == size) + new Error("Heap is full\n"); + + size++; + a[size].vert = vertv; + pos_a[vertv] = size; // supondo 1 <= vertv <= n + decreaseKey(vertv,key); // diminui a chave e corrige posicao se necessario + } + + void write_heap(){ + System.out.printf("Max size: %d\n",sizeMax); + System.out.printf("Current size: %d\n",size); + System.out.printf("(Vert,Key)\n---------\n"); + for(int i=1; i <= size; i++) + System.out.printf("(%d,%d)\n",a[i].vert,a[i].vertkey); + + System.out.printf("-------\n(Vert,PosVert)\n---------\n"); + + for(int i=1; i <= sizeMax; i++) + if (pos_valida(pos_a[i])) + System.out.printf("(%d,%d)\n",i,pos_a[i]); + } + + private int parent(int i){ + return i/2; + } + private int left(int i){ + return 2*i; + } + private int right(int i){ + return 2*i+1; + } + + private int compare(int i, int j) { + if (a[i].vertkey < a[j].vertkey) + return -1; + if (a[i].vertkey == a[j].vertkey) + return 0; + return 1; + } + + + private void heapify(int i) { + int l, r, smallest; + + l = left(i); + if (l > size) l = i; + + r = right(i); + if (r > size) r = i; + + smallest = i; + if (compare(l,smallest) < 0) + smallest = l; + if (compare(r,smallest) < 0) + smallest = r; + + if (i != smallest) { + swap(i, smallest); + heapify(smallest); + } + + } + + private void swap(int i, int j) { + Qnode aux; + pos_a[a[i].vert] = j; + pos_a[a[j].vert] = i; + aux = a[i]; + a[i] = a[j]; + a[j] = aux; + } + + private boolean pos_valida(int i) { + return (i >= 1 && i <= size); + } +} + + +class Arco { + int no_final; + int valor; + + Arco(int fim, int v){ + no_final = fim; + valor = v; + } + + int extremo_final() { + return no_final; + } + + int valor_arco() { + return valor; + } +} + + +class No { + //int label; + LinkedList adjs; + + No() { + adjs = new LinkedList(); + } +} + + +class Grafo { + No verts[]; + int nvs, narcos; + + public Grafo(int n) { + nvs = n; + narcos = 0; + verts = new No[n+1]; + for (int i = 0 ; i <= n ; i++) + verts[i] = new No(); + // para vertices numerados de 1 a n (posicao 0 nao vai ser usada) + } + + public int num_vertices(){ + return nvs; + } + + public int num_arcos(){ + return narcos; + } + + public LinkedList adjs_no(int i) { + return verts[i].adjs; + } + + public void insert_new_arc(int i, int j, int valor_ij){ + verts[i].adjs.addFirst(new Arco(j,valor_ij)); + narcos++; + } + + public Arco find_arc(int i, int j){ + for (Arco adj: adjs_no(i)) + if (adj.extremo_final() == j) return adj; + return null; + } +} + +public class SopaDeLetras { + public static void main(String[] args) { + Scanner moo=new Scanner(System.in); + String p1=moo.nextLine(); + String p2=moo.nextLine(); + int i; + for(i=0; i adjs; + + No() { + adjs = new LinkedList(); + } +} + + +class Grafo { + No verts[]; + int nvs, narcos; + + public Grafo(int n) { + nvs = n; + narcos = 0; + verts = new No[n+1]; + for (int i = 0 ; i <= n ; i++) + verts[i] = new No(); + // para vertices numerados de 1 a n (posicao 0 nao vai ser usada) + } + + public int num_vertices(){ + return nvs; + } + + public int num_arcos(){ + return narcos; + } + + public LinkedList adjs_no(int i) { + return verts[i].adjs; + } + + public void insert_new_arc(int i, int j, int valor_ij){ + verts[i].adjs.addFirst(new Arco(j,valor_ij)); + narcos++; + } + + public Arco find_arc(int i, int j){ + for (Arco adj: adjs_no(i)) + if (adj.extremo_final() == j) return adj; + return null; + } +} + +public class Sopadeletras { + + /** + * @param args + */ + public static void main(String[] args) { + Scanner in = new Scanner(System.in); + + String s1, s2; + + s1 = in.next(); + s2 = in.next(); + + + + int t1 = s1.length(); int t2 = s1.length(); + + int t = Math.min(t1, t2); + + Grafo sopa = new Grafo(20); + int flag = 0; + char a, b; + + for(int i = 0; i< t; i++){ + a = s1.charAt(i); + b = s2.charAt(i); + + if(a != b){ + System.out.print(a); System.out.println(b); + break; + } + else if(i == (t-1)) + flag=1; + } + + if(flag == 1) + System.out.println("Nenhum"); + } + +} diff --git a/languages/java-cpg/src/test/resources/java/progpedia/source.txt b/languages/java-cpg/src/test/resources/java/progpedia/source.txt new file mode 100644 index 0000000000..817cb345f6 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/progpedia/source.txt @@ -0,0 +1,3 @@ +José Carlos Paiva, José Paulo Leal, and Álvaro Figueira. PROGpedia. por. Dec. 2022. +10.5281/zenodo.7449056. +https://zenodo.org/records/7449056 (visited on 11/04/2025). diff --git a/languages/java-cpg/src/test/resources/java/singleUseVariable/SubmissionA.java b/languages/java-cpg/src/test/resources/java/singleUseVariable/SubmissionA.java new file mode 100644 index 0000000000..422044695d --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/singleUseVariable/SubmissionA.java @@ -0,0 +1,16 @@ +package edu.kit.kastel.sdq.jplag.example.singleUse; + +public class SubmissionA { + + public void square(int i) { + String template = "%d"; + String message = template.formatted(i*i); + // template and i are not reassigned between definition and usage of message + System.out.println("Here's the message: '%s'".formatted(message)); + } + + public static void main(String[] args) { + new SubmissionA().square(24); + new SubmissionA().square(7); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/singleUseVariable/SubmissionB.java b/languages/java-cpg/src/test/resources/java/singleUseVariable/SubmissionB.java new file mode 100644 index 0000000000..40df89beef --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/singleUseVariable/SubmissionB.java @@ -0,0 +1,15 @@ +package edu.kit.kastel.sdq.jplag.example.singleUse; +public class SubmissionB { + + public void square(int i) { + + + + System.out.println("Here's the message: '%s'".formatted("%d".formatted(i*i))); + } + + public static void main(String[] args) { + new SubmissionB().square(24); + new SubmissionB().square(7); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/try/Try.java b/languages/java-cpg/src/test/resources/java/try/Try.java new file mode 100644 index 0000000000..9dffa14a5d --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/try/Try.java @@ -0,0 +1,28 @@ +package de.jplag.java; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Scanner; + +public class Try { + public static void main(String[] args) { + new Try().load("DoesNotExist.txt"); + } + + public void load(String path) { + Scanner scanner = null; + try { + // This is just here to keep the tokens similar. + Scanner other = scanner = new Scanner(new File(path)); + while (scanner.hasNext()) { + System.out.println(scanner.nextLine()); + } + } catch (FileNotFoundException exception) { + exception.printStackTrace(); + } finally { + if (scanner != null) { + scanner.close(); + } + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/try/TryWithResource.java b/languages/java-cpg/src/test/resources/java/try/TryWithResource.java new file mode 100644 index 0000000000..1bcde6c2b2 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/try/TryWithResource.java @@ -0,0 +1,26 @@ +package de.jplag.java; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Scanner; + +public class TryWithResource { + public static void main(String[] args) { + new TryWithResource().load("DoesNotExist.txt"); + } + + public void load(String path) { + Scanner other = null; // This is just here to keep the tokens similar. + try (Scanner scanner = other = new Scanner(new File(path))) { // same for = other = + while (scanner.hasNext()) { + System.out.println(scanner.nextLine()); + } + } catch (FileNotFoundException exception) { + exception.printStackTrace(); + } finally { + if (other != null) { // This as well... + other.close(); // This as well... + } + } + } +} diff --git a/languages/java-cpg/src/test/resources/java/unusedVariables/SubmissionA.java b/languages/java-cpg/src/test/resources/java/unusedVariables/SubmissionA.java new file mode 100644 index 0000000000..027fdc6987 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/unusedVariables/SubmissionA.java @@ -0,0 +1,16 @@ +class UnusedVariableDeclaration { + private int multiply(int factor1, int factor2) { + int product = 0; + for (; factor2 > 0; factor2--) { + product += factor1; + + } + + int productCopy = product; // Redundant variable copy is another problem + return productCopy; + } + + public static void main(String[] args) { + System.out.println(new UnusedVariableDeclaration().multiply(17, 42)); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/unusedVariables/SubmissionB.java b/languages/java-cpg/src/test/resources/java/unusedVariables/SubmissionB.java new file mode 100644 index 0000000000..6fb79c5f75 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/unusedVariables/SubmissionB.java @@ -0,0 +1,16 @@ +class UnusedVariableDeclaration { + private int multiply(int factor1, int factor2) { + int product = 0; + for (int i = 0, j = 3; factor2 > 0; factor2--) { // <- i and j are not used + product += factor1; + String a = "Also not used."; // <- a is not used + } + double d = 4.0; // <- d is not used + int productCopy = product; + return productCopy; + } + + public static void main(String[] args) { + System.out.println(new UnusedVariableDeclaration().multiply(17, 42)); + } +} \ No newline at end of file diff --git a/languages/java-cpg/src/test/resources/java/verbosity/Compact.java b/languages/java-cpg/src/test/resources/java/verbosity/Compact.java new file mode 100644 index 0000000000..a185c6225f --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/verbosity/Compact.java @@ -0,0 +1,24 @@ +package java; + +import java.util.ArrayList; + +public class Compact { + static int tryCount(ArrayList a, int n) { + if (a.isEmpty()) + return Integer.MAX_VALUE; + + int count = 0; + + for (int i = 0; i + 1 < a.size(); i++) + if (a.get(i) + 1 != (int) a.get(i + 1)) + count++; + + if (a.get(0) != 0) + count++; + + if (a.get(a.size() - 1) != n - 1) + count++; + + return count; + } +} diff --git a/languages/java-cpg/src/test/resources/java/verbosity/Verbose.java b/languages/java-cpg/src/test/resources/java/verbosity/Verbose.java new file mode 100644 index 0000000000..c224822c85 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/verbosity/Verbose.java @@ -0,0 +1,28 @@ +package java; + +import java.util.ArrayList; + +public class Verbose { + static int tryCount(ArrayList l, int n) { + if (l.size() == 0) { + return Integer.MAX_VALUE; + } + + int count = 0; + + for (int i = 0; i + 1 < l.size(); i++) { + if (l.get(i) + 1 != l.get(i + 1)) { + count++; + } + } + if (l.get(0) != 0) { + count++; + } + + if (l.get(l.size() - 1) != n - 1) { + count++; + } + + return count; + } +} diff --git a/languages/pom.xml b/languages/pom.xml index 0ebec973d1..ca0efe8f25 100644 --- a/languages/pom.xml +++ b/languages/pom.xml @@ -18,6 +18,7 @@ emf-model golang java + java-cpg kotlin python-3 rlang diff --git a/leon/output b/leon/output new file mode 100644 index 0000000000..ae9ace2634 Binary files /dev/null and b/leon/output differ diff --git a/leon/submissions/Submission-1.java b/leon/submissions/Submission-1.java new file mode 100644 index 0000000000..0cca7bef87 --- /dev/null +++ b/leon/submissions/Submission-1.java @@ -0,0 +1,140 @@ +import java.util.LinkedList; +import java.util.Scanner; + +class No { + int no; + int tempof; + LinkedList adj; + boolean visitado; + + No(int n) { + no = n; + tempof = 0; + adj = new LinkedList(); + visitado = false; + } + + void addLigacao(No x) { + adj.addLast(x); + } +} + +class Graph { + int tempo, npessoas; + No g[]; + No gt[]; + LinkedList grupos; + + Graph(int np) { + this.npessoas = np; + tempo = 0; + g = new No[np + 1]; + gt = new No[np + 1]; + grupos = new LinkedList(); + } + + private void inicializar() { + for (int i = 1; i <= npessoas; i++) { + g[i] = new No(i); + gt[i] = new No(i); + } + } + + void createGrafo(Scanner in) { + inicializar(); + for (int i = 1; i <= npessoas; i++) { + int pessoa = in.nextInt(); + int namigo = in.nextInt(); + for (int j = 0; j < namigo; j++) { + int amigo = in.nextInt(); + g[pessoa].addLigacao(g[amigo]); + gt[amigo].addLigacao(gt[pessoa]); + } + } + } + + void DFS() { + for (int i = 1; i <= npessoas; i++) { + if (!g[i].visitado) DFSVisit(g[i]); + } + } + + private void DFSVisit(No x) { + tempo++; + x.visitado = true; + for (No cursor : x.adj) { + if (!cursor.visitado) DFSVisit(cursor); + } + tempo++; + x.tempof = tempo; + gt[x.no].tempof = tempo; + } + + private No findMax() { + int maximo = 0; + No max = new No(0); + for (int i = 1; i <= npessoas; i++) { + if (!gt[i].visitado) { + if (gt[i].tempof > maximo) { + maximo = gt[i].tempof; + max = gt[i]; + } + } + } + return max; + } + + private boolean allVisited() { + for (int i = 1; i <= npessoas; i++) { + if (!gt[i].visitado) return false; + } + return true; + } + + void DFS_T() { + while (!allVisited()) { + No TMax = findMax(); //System.out.println(TMax.no); + int nelementos = NelementosGrupo(TMax); //System.out.println(nelementos); + grupos.addLast(nelementos); + TMax.tempof = 0; + } + } + + private int NelementosGrupo(No x) { + int count = 1; + x.visitado = true; + for (No cursor : x.adj) { + if (cursor.visitado == false) count += NelementosGrupo(cursor); + } + return count; + } + + void Output() { + int ngrupos = 0; + int deFora = 0; + for (int x : grupos) { + if (x >= 4) { + ngrupos++; + } else { + deFora += x; + } + } + System.out.printf("%d %d\n", ngrupos, deFora); + } +} + +public class Sociologia { + public static void main(String[] args) { + Scanner in = new Scanner(System.in); + int ncasos = in.nextInt(); + for (int i = 1; i <= ncasos; i++) { + int np = in.nextInt(); + Graph novo = new Graph(np); + novo.createGrafo(in); + novo.DFS(); + novo.DFS_T(); + System.out.printf("Caso #%d\n", i); + novo.Output(); + } + } +} \ No newline at end of file diff --git a/leon/submissions/Submission-2.java b/leon/submissions/Submission-2.java new file mode 100644 index 0000000000..e0b872f46f --- /dev/null +++ b/leon/submissions/Submission-2.java @@ -0,0 +1,140 @@ +import java.util.LinkedList; +import java.util.Scanner; + +class No { + int no; + int tempof; + LinkedList adj; + boolean visitado; + + No(int n) { + no = n; + tempof = 0; + adj = new LinkedList(); + visitado = false; + } + + void addLigacao(No x) { + adj.addLast(x); + } +} + +class Graph { + int tempo, npessoas; + No g[]; + No gt[]; + LinkedList grupos; + + Graph(int np) { + this.npessoas = np; + tempo = 0; + g = new No[np + 1]; + gt = new No[np + 1]; + grupos = new LinkedList(); + } + + private void inicializar() { + for (int i = 1; i <= npessoas; i++) { + g[i] = new No(i); + gt[i] = new No(i); + } + } + + void createGrafo(Scanner in) { + inicializar(); + for (int i = 1; i <= npessoas; i++) { + int pessoa = in.nextInt(); + int namigo = in.nextInt(); + for (int j = 0; j < namigo; j++) { + int amigo = in.nextInt(); + g[pessoa].addLigacao(g[amigo]); + gt[amigo].addLigacao(gt[pessoa]); + } + } + } + + void DFS() { + for (int i = 1; i <= npessoas; i++) { + if (!g[i].visitado) DFSVisit(g[i]); + } + } + + private void DFSVisit(No x) { + tempo++; + x.visitado = true; + for (No cursor : x.adj) { + if (!cursor.visitado) DFSVisit(cursor); + } + tempo++; + x.tempof = tempo; + gt[x.no].tempof = tempo; + } + + private No findMax() { + int maximo = 0; + No max = new No(0); + for (int i = 1; i <= npessoas; i++) { + if (!gt[i].visitado) { + if (gt[i].tempof > maximo) { + maximo = gt[i].tempof; + max = gt[i]; + } + } + } + return max; + } + + private boolean allVisited() { + for (int i = 1; i <= npessoas; i++) { + if (!gt[i].visitado) return false; + } + return true; + } + + void DFS_T() { + while (!allVisited()) { + No TMax = findMax(); + int nelementos = NelementosGrupo(TMax); + grupos.addLast(nelementos); + TMax.tempof = 0; + } + } + + private int NelementosGrupo(No x) { + int count = 1; + x.visitado = true; + for (No cursor : x.adj) { + if (!cursor.visitado) count += NelementosGrupo(cursor); + } + return count; + } + + void Output() { + int ngrupos = 0; + int deFora = 0; + for (int x : grupos) { + if (x >= 4) { + ngrupos++; + } else { + deFora += x; + } + } + System.out.printf("%d %d\n", ngrupos, deFora); + } +} + +public class Sociologia { + public static void main(String[] args) { + Scanner in = new Scanner(System.in); + int ncasos = in.nextInt(); + for (int i = 1; i <= ncasos; i++) { + int np = in.nextInt(); + Graph novo = new Graph(np); + novo.createGrafo(in); + novo.DFS(); + novo.DFS_T(); + System.out.printf("Caso #%d\n", i); + novo.Output(); + } + } +} \ No newline at end of file diff --git a/leon/submissions/Submission-3.java b/leon/submissions/Submission-3.java new file mode 100644 index 0000000000..d6ac1e86ee --- /dev/null +++ b/leon/submissions/Submission-3.java @@ -0,0 +1,65 @@ +public class Original { + public static void main(String[] args) { + System.out.println("Hello, world!"); + + // Basic arithmetic operations + int result = add(5, 3); + System.out.println("5 + 3 = " + result); + + int difference = subtract(10, 4); + System.out.println("10 - 4 = " + difference); + + // Array operations + int[] numbers = {1, 2, 3, 4, 5}; + int sum = calculateSum(numbers); + System.out.println("Sum of array: " + sum); + + // String operations + String message = "Java Programming"; + String reversed = reverseString(message); + System.out.println("Original: " + message); + System.out.println("Reversed: " + reversed); + + // Simple loop + System.out.println("Counting from 1 to 5:"); + for (int i = 1; i <= 5; i++) { + System.out.println("Count: " + i); + } + + // Check even/odd + checkEvenOdd(8); + checkEvenOdd(7); + } + + public static int add(int a, int b) { + return a + b; + } + + public static int subtract(int a, int b) { + return a - b; + } + + public static int calculateSum(int[] arr) { + int total = 0; + for (int num : arr) { + total += num; + } + return total; + } + + public static String reverseString(String str) { + StringBuilder reversed = new StringBuilder(); + for (int i = str.length() - 1; i >= 0; i--) { + reversed.append(str.charAt(i)); + } + return reversed.toString(); + } + + public static void checkEvenOdd(int number) { + if (number % 2 == 0) { + System.out.println(number + " is even"); + } else { + System.out.println(number + " is odd"); + } + } +} diff --git a/leon/submissions/Submission-4.java b/leon/submissions/Submission-4.java new file mode 100644 index 0000000000..9538a27f98 --- /dev/null +++ b/leon/submissions/Submission-4.java @@ -0,0 +1,142 @@ +public class Plagiarized { + public static void main(String[] args) { + if (false) { + System.out.println("This will never execute"); + int dummy = 999; + } + + System.out.println("Hello, world!"); + + // Dead code block + while (false) { + System.out.println("Unreachable while loop"); + } + + // Basic arithmetic operations (same as original) + int result = add(5, 3); + System.out.println("5 + 3 = " + result); + + if (1 > 2) { + System.out.println("This condition will never be true"); + } + + int difference = subtract(10, 4); + System.out.println("10 - 4 = " + difference); + + // More dead code + for (int j = 0; false; j++) { + System.out.println("Impossible loop iteration"); + } + + // Array operations (same functionality) + int[] numbers = {1, 2, 3, 4, 5}; + int sum = calculateSum(numbers); + System.out.println("Sum of array: " + sum); + + // Unreachable switch + switch (0) { + case 1: + System.out.println("Dead case"); + break; + default: + if (false) { + System.out.println("Nested dead code"); + } + } + + // String operations (same as original) + String message = "Java Programming"; + String reversed = reverseString(message); + System.out.println("Original: " + message); + System.out.println("Reversed: " + reversed); + + // Dead try-catch + try { + if (false) { + throw new RuntimeException("This will never throw"); + } + } catch (RuntimeException e) { + System.out.println("Dead catch block"); + } + + // Simple loop (same as original) + System.out.println("Counting from 1 to 5:"); + for (int i = 1; i <= 5; i++) { + if (false) { + continue; // Dead continue + } + System.out.println("Count: " + i); + } + + // Check even/odd (same as original) + checkEvenOdd(8); + checkEvenOdd(7); + + // Final dead code + if (true == false) { + System.out.println("Logically impossible condition"); + } + } + + public static int add(int x, int y) { + if (false) { + x = x * 100; // Dead code inside method + } + return x + y; + } + + public static int subtract(int x, int y) { + while (false) { + x--; // Unreachable decrement + } + return x - y; + } + + public static int calculateSum(int[] array) { + int total = 0; + if (false) { + total = -1; // Dead assignment + } + for (int num : array) { + if (false) { + num = 0; // Dead modification (won't affect original) + } + total += num; + } + return total; + } + + public static String reverseString(String text) { + if (false) { + return ""; // Dead return + } + StringBuilder reversed = new StringBuilder(); + for (int i = text.length() - 1; i >= 0; i--) { + if (false) { + break; // Dead break + } + reversed.append(text.charAt(i)); + } + if (false) { + reversed.append("DEAD"); // Dead append + } + return reversed.toString(); + } + + public static void checkEvenOdd(int num) { + if (false) { + num = 0; // Dead modification + } + if (num % 2 == 0) { + if (false) { + System.out.println("Dead even message"); + } + System.out.println(num + " is even"); + } else { + if (false) { + System.out.println("Dead odd message"); + } + System.out.println(num + " is odd"); + } + } +} diff --git a/leon/submissions/Submission-5.java b/leon/submissions/Submission-5.java new file mode 100644 index 0000000000..ace3c03cf1 --- /dev/null +++ b/leon/submissions/Submission-5.java @@ -0,0 +1,78 @@ +public class MathHelper { + private static final String GREETING_TEXT = "Hello, world!"; + + public static void main(String[] args) { + + System.out.println(GREETING_TEXT); + + int firstNum = 5, secondNum = 3; + int additionResult = performAddition(firstNum, secondNum); + System.out.println(firstNum + " + " + secondNum + " = " + additionResult); + + int minuend = 10, subtrahend = 4; + int subtractionResult = performSubtraction(minuend, subtrahend); + System.out.println(minuend + " - " + subtrahend + " = " + subtractionResult); + + int[] dataSet = new int[]{1, 2, 3, 4, 5}; + int totalSum = computeArraySum(dataSet); + System.out.println("Sum of array: " + totalSum); + + String originalText = "Java Programming"; + String flippedText = flipString(originalText); + System.out.println("Original: " + originalText); + System.out.println("Reversed: " + flippedText); + + System.out.println("Counting from 1 to 5:"); + int counter = 1; + while (counter <= 5) { + System.out.println("Count: " + counter); + counter++; + } + + } + + private static void analyzeNumbers() { + int[] testNumbers = {8, 7}; + for (int value : testNumbers) { + determineEvenOrOdd(value); + } + } + + // Mathematical operations with different parameter names + public static int performAddition(int operandA, int operandB) { + int sum = operandA + operandB; + return sum; + } + + public static int performSubtraction(int operandA, int operandB) { + int difference = operandA - operandB; + return difference; + } + + // Array processing with different approach + public static int computeArraySum(int[] inputArray) { + int runningTotal = 0; + int index = 0; + while (index < inputArray.length) { + runningTotal = runningTotal + inputArray[index]; + index++; + } + return runningTotal; + } + + // String manipulation with different implementation + public static String flipString(String inputText) { + char[] characters = inputText.toCharArray(); + String result = ""; + for (int pos = characters.length - 1; pos >= 0; pos--) { + result = result + characters[pos]; + } + return result; + } + + // Number analysis with different structure + public static void determineEvenOrOdd(int value) { + String classification = (value % 2 == 0) ? "even" : "odd"; + System.out.println(value + " is " + classification); + } +} diff --git a/pom.xml b/pom.xml index 3354a1e029..9a027cbabd 100644 --- a/pom.xml +++ b/pom.xml @@ -372,6 +372,11 @@ java ${revision} + + de.jplag + java-cpg + ${revision} + de.jplag python-3 @@ -689,7 +694,8 @@ maven-surefire-plugin ${version.plugin.maven.surefire} - @{argLine} -javaagent:"${settings.localRepository}/org/mockito/mockito-core/${version.mockito}/mockito-core-${version.mockito}.jar" + @{argLine} + -javaagent:"${settings.localRepository}/org/mockito/mockito-core/${version.mockito}/mockito-core-${version.mockito}.jar" diff --git a/report-viewer/model/src/Language.ts b/report-viewer/model/src/Language.ts index 829f2177d2..32cca8847c 100644 --- a/report-viewer/model/src/Language.ts +++ b/report-viewer/model/src/Language.ts @@ -11,6 +11,7 @@ enum ParserLanguage { EMF_METAMODEL = 'emf', EMF_MODEL = 'emf-model', GO = 'go', + JAVA_CPG = 'java-cpg', KOTLIN = 'kotlin', R_LANG = 'rlang', RUST = 'rust', diff --git a/report-viewer/ui-components/widget/fileDisplaying/CodeHighlighter.ts b/report-viewer/ui-components/widget/fileDisplaying/CodeHighlighter.ts index 177d76b623..fcb3ba0eae 100644 --- a/report-viewer/ui-components/widget/fileDisplaying/CodeHighlighter.ts +++ b/report-viewer/ui-components/widget/fileDisplaying/CodeHighlighter.ts @@ -74,6 +74,7 @@ function getHighlightLanguage(lang: Language) { hljs.registerLanguage('typescript', typescript) return 'typescript' case ParserLanguage.JAVA: + case ParserLanguage.JAVA_CPG: default: return 'java' }