diff --git a/.gitignore b/.gitignore index 8c87f64b07..16388692ac 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ target/ .settings/ .classpath .factorypath -website +website/node_modules *.class diff --git a/core/src/main/java/de/jplag/Submission.java b/core/src/main/java/de/jplag/Submission.java index b7181c673a..3c130a5068 100644 --- a/core/src/main/java/de/jplag/Submission.java +++ b/core/src/main/java/de/jplag/Submission.java @@ -71,11 +71,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 +101,8 @@ public boolean equals(Object obj) { } @Override - public int hashCode() { - return Objects.hash(name); + public String toString() { + return name; } /** @@ -100,6 +113,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 +174,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 +214,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 +222,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 +237,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 +348,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 65303e5efd..8981f25a1d 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/.gitignore b/languages/java-cpg/.gitignore new file mode 100644 index 0000000000..2f17f3d09c --- /dev/null +++ b/languages/java-cpg/.gitignore @@ -0,0 +1,2 @@ +.attach_pid* +*.csv diff --git a/languages/java-cpg/pom.xml b/languages/java-cpg/pom.xml index ab13f1bae4..02fe3d778f 100644 --- a/languages/java-cpg/pom.xml +++ b/languages/java-cpg/pom.xml @@ -40,6 +40,16 @@ kotlin-test test + + de.jplag + jplag + test + + + de.jplag + java + test + 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 index 8579ad1065..51534c8496 100644 --- 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 @@ -1,5 +1,7 @@ package de.jplag.java_cpg; +import static de.jplag.java_cpg.JavaCpgLanguage.allTransformations; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -7,22 +9,32 @@ import java.util.Set; import java.util.concurrent.ExecutionException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + 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.AiMethodPass; +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; @@ -40,26 +52,58 @@ */ public class CpgAdapter { + private final boolean removeDeadCode; + private final boolean removeSimpleDeadCode; + private final boolean detectDeadCode; + private int deadLinesCount; + private int deadCodeCount; private boolean reorderingEnabled = true; + /** + * Sets if only methods are analyzed instead of the whole program. + */ + private final boolean methodAnalysisMode = false; + /** + * Sets if the analysis should try to continue if errors are encountered. The detected dead code will not be correct if + * this is true. + */ + private static final boolean continueOnError = false; /** * Constructs a new {@link 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 removeSimpleDeadCode whether dead code should be removed in the DFG sort pass, reordering has to be enabled + * for this to matter + * @param reorder whether statements may be reordered */ - public CpgAdapter(GraphTransformation... transformations) { + public CpgAdapter(boolean removeDeadCode, boolean detectDeadCode, boolean reorder, boolean removeSimpleDeadCode, + GraphTransformation... transformations) { addTransformations(transformations); + this.removeDeadCode = removeDeadCode; + this.detectDeadCode = detectDeadCode; + this.removeSimpleDeadCode = removeSimpleDeadCode; + setReorderingEnabled(reorder); + } + + /** + * Constructs a new {@link CpgAdapter} with all transformations enabled and nor abstract interpretation.. + */ + @TestOnly + public CpgAdapter() { + this(false, false, true, true, allTransformations()); } @SuppressWarnings("unchecked") - /* package-private */ List adapt(Set files, boolean normalize) throws ParsingException, InterruptedException { + List adapt(@NotNull Set files, boolean normalize) throws ParsingException, InterruptedException { assert !files.isEmpty(); + deadLinesCount = 0; if (!normalize) { clearTransformations(); + addTransformations(JavaCpgLanguage.minimalTransformations()); setReorderingEnabled(false); } - TranslationResult translate = translate(files); - return (List) translate.getScratch().getOrDefault("tokenList", List.of()); } @@ -67,7 +111,7 @@ public CpgAdapter(GraphTransformation... transformations) { * Adds a transformation at the end of its respective ATransformationPass. * @param transformation a {@link GraphTransformation} */ - public void addTransformation(GraphTransformation transformation) { + public void addTransformation(@NotNull GraphTransformation transformation) { switch (transformation.phase()) { case OBLIGATORY -> PrepareTransformationPass.registerTransformation(transformation); case AST_TRANSFORM -> AstTransformationPass.registerTransformation(transformation); @@ -91,7 +135,7 @@ public void clearTransformations() { CpgTransformationPass.clearTransformations(); } - private > KClass getKClass(Class javaPassClass) { + private @NotNull > KClass getKClass(Class javaPassClass) { return JvmClassMappingKt.getKotlinClass(javaPassClass); } @@ -103,34 +147,64 @@ public void setReorderingEnabled(boolean enabled) { this.reorderingEnabled = enabled; } - /* package-private */ TranslationResult translate(Set files) throws ParsingException, InterruptedException { + TranslationResult translate(@NotNull Set files) throws ParsingException, InterruptedException { InferenceConfiguration inferenceConfiguration = InferenceConfiguration.builder().inferRecords(true).inferDfgForUnresolvedCalls(true).build(); - TranslationResult translationResult; + AiPass.AiPassCompanion.setRemoveDeadCode(CpgAdapter.this.removeDeadCode); + AiPass.AiPassCompanion.setContinueOnError(continueOnError); + AiPass.AiPassCompanion.setDeadLinesCallback(CpgAdapter.this::setDeadLinesCount); + AiPass.AiPassCompanion.setDeadCountCallback(CpgAdapter.this::setDeadCodeCount); + AiMethodPass.AiMethodPassCompanion.setRemoveDeadCode(CpgAdapter.this.removeDeadCode); + AiMethodPass.AiMethodPassCompanion.setContinueOnError(continueOnError); + AiMethodPass.AiMethodPassCompanion.setDeadLinesCallback(CpgAdapter.this::setDeadLinesCount); + AiMethodPass.AiMethodPassCompanion.setDeadCountCallback(CpgAdapter.this::setDeadCodeCount); + DfgSortPass.DfgSortPassCompanion.setRemoveDeadCode(CpgAdapter.this.removeSimpleDeadCode); 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, - ImportResolver.class, SymbolResolver.class, PrepareTransformationPass.class, FixAstPass.class, DynamicInvokeResolver.class, - FilenameMapper.class, AstTransformationPass.class, EvaluationOrderGraphPass.class, // creates - // EOG + 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, TokenizationPass.class)); - - if (!reorderingEnabled) + if (methodAnalysisMode) { + passClasses.add(AiMethodPass.class); + } else { + passClasses.add(AiPass.class); + } + if (!reorderingEnabled) { passClasses.remove(DfgSortPass.class); - + } + if (!detectDeadCode && !removeDeadCode) { + passClasses.remove(AiPass.class); + passClasses.remove(AiMethodPass.class); + } for (Class> passClass : passClasses) { configBuilder.registerPass(getKClass(passClass)); } - translationResult = TranslationManager.builder().config(configBuilder.build()).build().analyze().get(); - - } catch (InterruptedException e) { - throw e; - } catch (ExecutionException | ConfigurationException e) { + } catch (ConfigurationException | ExecutionException e) { throw new ParsingException(List.copyOf(files).getFirst(), e); } return translationResult; } + + private void setDeadLinesCount(int count) { + this.deadLinesCount = count; + } + + int getDeadLinesCount() { + return deadLinesCount; + } + + private void setDeadCodeCount(int count) { + this.deadCodeCount = count; + } + + @TestOnly + int getDeadCodeCount() { + return deadCodeCount; + } + } 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 index 7ac36f15f0..927e2746e4 100644 --- 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 @@ -26,15 +26,25 @@ import java.util.List; import java.util.Set; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + 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; import com.google.auto.service.AutoService; +import kotlin.Pair; /** - * This class represents the frond end of the CPG module of JPlag. + * This class represents the front end of the CPG module of JPlag. */ @AutoService(Language.class) public class JavaCpgLanguage implements Language { @@ -48,53 +58,102 @@ public class JavaCpgLanguage implements Language { * Creates a new {@link JavaCpgLanguage}. */ public JavaCpgLanguage() { - this.cpgAdapter = new CpgAdapter(allTransformations()); + this.cpgAdapter = new CpgAdapter(true, true, true, true, allTransformations()); } /** - * Adds the given {@link GraphTransformation} to the list to apply to the submissions. - * @param transformation the transformation + * 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 removeSimpleDeadCode whether dead code should be removed in the DFG sort pass, reordering has to be enabled + * for this to matter */ - public void addTransformation(GraphTransformation transformation) { - this.cpgAdapter.addTransformation(transformation); + public JavaCpgLanguage(boolean removeDeadCode, boolean detectDeadCode, boolean reorder, boolean removeSimpleDeadCode) { + this.cpgAdapter = new CpgAdapter(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, allTransformations()); } /** - * Adds the given {@link GraphTransformation}s to the list to apply to the submissions. - * @param transformations the 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 removeSimpleDeadCode whether dead code should be removed in the DFG sort pass, reordering has to be enabled + * for this to matter */ - public void addTransformations(GraphTransformation[] transformations) { - this.cpgAdapter.addTransformations(transformations); + public JavaCpgLanguage(boolean removeDeadCode, boolean detectDeadCode, boolean reorder, boolean removeSimpleDeadCode, + GraphTransformation[] transformations) { + this.cpgAdapter = new CpgAdapter(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, transformations); } - @Override - public String getIdentifier() { - return IDENTIFIER; + /** + * 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 removeSimpleDeadCode whether dead code should be removed in the DFG sort pass, reordering has to be enabled + * for this to matter + * @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, boolean removeSimpleDeadCode, + GraphTransformation[] transformations, IntAiType intAiType, FloatAiType floatAiType, StringAiType stringAiType, CharAiType charAiType, + ArrayAiType arrayAiType) { + this(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, transformations); + Value.setUsedIntAiType(intAiType); + Value.setUsedFloatAiType(floatAiType); + Value.setUsedStringAiType(stringAiType); + Value.setUsedCharAiType(charAiType); + Value.setUsedArrayAiType(arrayAiType); } - @Override - public String getName() { - return NAME; + /** + * @return array with only the minimal set of transformations needed for a standard tokenization + */ + @NotNull + public static GraphTransformation[] minimalTransformations() { + return new GraphTransformation[] {removeLibraryRecord, removeLibraryField,}; } - @Override - public int minimumTokenMatch() { - return DEFAULT_MINIMUM_TOKEN_MATCH; + /** + * @return array with only the set of transformations needed for dead code removal + */ + @NotNull + public static GraphTransformation[] deadCodeRemovalTransformations() { + return new GraphTransformation[] {removeEmptyDeclarationStatement, removeLibraryRecord, removeLibraryField, removeUnsupportedConstructor, + removeUnsupportedMethod, removeEmptyRecord,}; } - @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(); - } + /** + * Returns a set of all transformations. + * @return the array of all transformations + */ + public static GraphTransformation[] allTransformations() { + return new GraphTransformation[] {ifWithNegatedConditionResolution, forStatementToWhileStatement, removeOptionalOfCall, removeOptionalGetCall, + removeGetterMethod, moveConstantToOnlyUsingClass, inlineSingleUseConstant, inlineSingleUseVariable, removeEmptyDeclarationStatement, + removeImplicitStandardConstructor, removeLibraryRecord, removeLibraryField, removeEmptyConstructor, removeUnsupportedConstructor, + removeUnsupportedMethod, removeEmptyRecord,}; } - @Override - public boolean requiresCoreNormalization() { - return false; + /** + * 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); } /** @@ -123,15 +182,9 @@ public GraphTransformation[] standardTransformations() { 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 boolean requiresCoreNormalization() { + return false; } @Override @@ -139,6 +192,57 @@ public List fileExtensions() { return 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 @NotNull List parse(@NotNull Set files, boolean normalize) throws ParsingException { + try { + return cpgAdapter.adapt(files, normalize); + } catch (InterruptedException _) { + Thread.currentThread().interrupt(); + return List.of(); + } + } + + /** + * Parses the given files and returns a pair of the resulting tokens and the number of dead code lines detected, if + * enabled. + * @param files the files to parse + * @param normalize whether to apply normalization transformations + * @return a pair of the resulting tokens and the number of dead code lines detected, + * @throws ParsingException if an error occurs during parsing + */ + @TestOnly + public @NotNull Pair, Integer> parse2(@NotNull Set files, boolean normalize) throws ParsingException { + try { + List tokens = cpgAdapter.adapt(files, normalize); + // int deadLines = cpgAdapter.getDeadLinesCount(); + int deadLines = cpgAdapter.getDeadCodeCount(); + return new Pair<>(tokens, deadLines); + } catch (InterruptedException _) { + Thread.currentThread().interrupt(); + return new Pair<>(List.of(), 0); + } + } + + @Override + public boolean expectsSubmissionOrder() { // FixMe: parallelimus seems to only sometimes work correctly + return true; + } + @Override public boolean supportsNormalization() { return true; @@ -148,4 +252,5 @@ public boolean supportsNormalization() { public boolean hasPriority() { 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..2e83d9b38c --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AbstractInterpretation.java @@ -0,0 +1,1818 @@ +package de.jplag.java_cpg.ai; + +import static de.jplag.java_cpg.ai.variables.VariableStore.ANONYMOUS_THIS_NAME; + +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.BranchingNode; +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.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.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.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.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.FloatingPointType; +import de.fraunhofer.aisec.cpg.graph.types.FunctionType; +import de.fraunhofer.aisec.cpg.graph.types.HasType; +import de.fraunhofer.aisec.cpg.graph.types.IntegerType; +import de.fraunhofer.aisec.cpg.graph.types.ObjectType; +import de.fraunhofer.aisec.cpg.graph.types.ParameterizedType; +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.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 { + + // protected static int visitedNodesCounter = 0; + /** + * Helper to detect recursive method calls. + */ + private static final List lastVisitedMethod = new ArrayList<>(); + private static boolean continueOnError = false; + /** + * Recorder for visited lines to detect dead methods/classes later. + */ + private final VisitedLinesRecorder visitedLinesRecorder; + /** + * Helper: if we are recording changes for while loops. + */ + @NotNull + private final RecordingChanges recordingChanges; + private List returnStorage; + private boolean removeDeadCode; + /** + * 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; + /** + * Flag: if we only analyze one method and ignore method calls. + */ + private boolean methodAnalysisMode = 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<>(); + this.visitedLinesRecorder = visitedLinesRecorder; + this.removeDeadCode = removeDeadCode; + this.recordingChanges = recordingChanges; + } + + private AbstractInterpretation(VisitedLinesRecorder visitedLinesRecorder, boolean removeDeadCode, @NotNull RecordingChanges recordingChanges, + @NotNull VariableName relatedClassName) { + this(visitedLinesRecorder, removeDeadCode, recordingChanges); + variables.setThisName(relatedClassName); + } + + protected static IJavaObject createNewObject(@NotNull ConstructExpression ce) { + IJavaObject newObject; + String name = ce.getType().getName().toString(); + name = name.split("<")[0]; // remove generics + switch (name) { + case "java.lang.String" -> { + newObject = Value.getNewStringValue(); + } + case "java.util.HashMap", "java.util.Map" -> newObject = new de.jplag.java_cpg.ai.variables.objects.HashMap(); + case "java.util.HashSet", "java.util.Set", "java.util.TreeSet" -> newObject = new de.jplag.java_cpg.ai.variables.objects.HashSet(); + 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", "java.util.Deque", "java.util.Stack", "java.util.ListIterator", "java.util.Queue" -> { + de.jplag.java_cpg.ai.variables.Type innerCpgType; + if (ce.getType() instanceof ObjectType objectType) { + if (objectType.getGenerics().isEmpty()) { + innerCpgType = new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.UNKNOWN); + } else { + assert objectType.getGenerics().size() == 1; + Type innerType = objectType.getGenerics().getFirst(); + innerCpgType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(innerType); + } + } else { + throw new IllegalStateException("Expected parameterized type for collection, but got " + ce.getType().getClass()); + } + newObject = Value.getNewArayValue(innerCpgType); + } + case "java.util.Comparator" -> throw new JavaLanguageFeatureNotSupportedException( + "Comparator objects are not supported in abstract interpretation yet."); + case "java.lang.StringBuilder" -> newObject = new de.jplag.java_cpg.ai.variables.objects.StringBuilder(); + default -> { + de.jplag.java_cpg.ai.variables.Type objectType = new de.jplag.java_cpg.ai.variables.Type( + de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT, name); + newObject = new JavaObject(objectType); + } + } + 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 CpgErrorException if the number of classes or namespaces is unexpected. + */ + 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() + .flatMap(ns -> ns.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 CpgErrorException("No RecordDeclaration with public static main method found in translation unit")); + } else if (tud.getDeclarations().stream().map(Declaration::getClass).anyMatch(x -> x.equals(RecordDeclaration.class))) { + // Code without a package + List mainClasses = 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())) + .toList(); + if (mainClasses.isEmpty()) { + throw new CpgErrorException("No RecordDeclaration with public static main method found in translation unit"); + } else if (mainClasses.size() > 1) { + throw new CpgErrorException("Multiple RecordDeclarations with public static main method found in translation unit"); + } + mainClas = mainClasses.getFirst(); + } else { + throw new CpgErrorException("Unexpected number of classes or namespaces in translation unit generated by cpg" + + tud.getDeclarations().stream().map(Declaration::getClass).map(Class::getName).toList()); + } + de.jplag.java_cpg.ai.variables.Type objectType = new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT, + mainClas.getName().toString()); + JavaObject mainClassVar = new JavaObject(objectType); + setupClass(mainClas, mainClassVar); + assert mainClas.getMethods().stream().map(MethodDeclaration::getName).filter(x -> x.getLocalName().equals("main")) + .count() == 1 : "Unexpected number of main methods in class " + mainClas.getName() + ": " + + mainClas.getMethods().stream().filter(m -> m.getName().getLocalName().equals("main")).count(); + 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(new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.STRING)))); + graphWalker(eog.getFirst()); + variables.removeScope(); + } + } + // ignores include declaration + } + + /** + * 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, + @Nullable ConstructorDeclaration constructor) { + setupClass(rd, objectInstance); + visitedLinesRecorder.recordFirstLineVisited(constructor); + for (Type type : rd.getImplementedInterfaces()) { + visitedLinesRecorder.recordFirstLineVisited(((ObjectType) type).getRecordDeclaration()); + } + // Run constructor method + if (constructor == null) { + return; + } + this.inConstructor = true; + if (constructor.getBody() == null && constructor.getRecordDeclaration() != null) { // cpg has lost the method body -> try to restore + constructor = (ConstructorDeclaration) restoreFunctionDeclaration(constructor.getRecordDeclaration(), constructor, + new ArrayList<>(constructor.getRecordDeclaration().getConstructors())); + } + 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))); + } + boolean removeDeadCodeBackup = this.removeDeadCode; + removeDeadCode = false; // do not remove dead code inside constructors as we do not have usage information + graphWalker(eog.getFirst()); + removeDeadCode = removeDeadCodeBackup; + variables.removeScope(); + } else if (eog.isEmpty()) { + // empty constructor -> return + } else { + throw new IllegalStateException("Unexpected value: " + eog.size()); + } + this.inConstructor = false; + } + + protected void setupObject(@NotNull IJavaObject objectInstance, @NotNull String name) { + objectInstance.setAbstractInterpretation(this); + variables.addVariable(new Variable(new VariableName(name), objectInstance)); + variables.setThisName(new VariableName(name)); + 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.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.Integer.getName(), new de.jplag.java_cpg.ai.variables.objects.Integer())); + 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.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())); + 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.System.getName(), new de.jplag.java_cpg.ai.variables.objects.System())); + this.object = objectInstance; + } + + /** + * 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) { + setupObject(objectInstance, rd.getName().toString()); + visitedLinesRecorder.recordFirstLineVisited(rd); + setupFieldDeclarations(rd, objectInstance); + RecordDeclaration currentClass = rd; + while (true) { + Set superClass = currentClass.getSuperTypeDeclarations(); + List superClassTypes = currentClass.getSuperClasses(); + superClass = superClass.stream() // necessary to filter out interfaces + .filter(x -> superClassTypes.stream().anyMatch(t -> x.toType().equals(t))).collect(java.util.stream.Collectors.toSet()); + if (superClass.isEmpty() || superClassTypes.isEmpty()) { + break; + } + assert superClass.size() == 1 : superClass.size() + " inheritance is not supported in Java."; + RecordDeclaration superRd = superClass.stream().findFirst().orElseThrow(); + for (Type type : superRd.getImplementedInterfaces()) { + visitedLinesRecorder.recordFirstLineVisited(((ObjectType) type).getRecordDeclaration()); + } + 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.getTypeEnum() == de.jplag.java_cpg.ai.variables.Type.TypeEnum.ARRAY + || aiType.getTypeEnum() == de.jplag.java_cpg.ai.variables.Type.TypeEnum.LIST) { + IJavaArray arrayValue = Value.getNewArayValue(aiType.getInnerType()); + 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 { + if (fd.getInitializer() instanceof NewExpression newExpr // filter out recursive constructor calls + && newExpr.getInitializer() instanceof ConstructExpression ce && ce.getConstructor() != null + && ce.getConstructor().getRecordDeclaration() != null && ce.getConstructor().getRecordDeclaration().equals(rd)) { + objectInstance.setField(new Variable(new VariableName(name.toString()), new VoidValue())); + } 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); + // visitedNodesCounter++; + // System.out.println(visitedNodesCounter + " " + 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 || (nextEOG.size() == 2 && nextEOG.getLast() instanceof ShortCircuitOperator); + nextNode = nextEOG.getFirst(); + } + case Reference ref -> { // adds its value to the value stack + walkReference(ref); + 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 -> { + walkDeclarationStatement(ds); + nextNode = nextEOG.getFirst(); + } + case AssignExpression ae -> nextNode = walkAssignExpression(ae); + case ShortCircuitOperator scop -> nextNode = walkShortCircuitOperator(scop); + case BinaryOperator bop -> { + walkBinaryOperator(bop); + if (nextEOG.isEmpty()) { // when used as a field initializer + IValue value = valueStack.getLast(); + valueStack.removeLast(); + return value; + } + assert nextEOG.size() == 1 || (nextEOG.size() == 2 && nextEOG.getLast() instanceof ShortCircuitOperator); + nextNode = nextEOG.getFirst(); + } + case UnaryOperator uop -> nextNode = walkUnaryOperator(uop); + 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 rs -> { + return walkReturnStatement(rs); + } + case ConstructExpression ce -> nextNode = walkConstructExpression(ce); + 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 -> { + IJavaArray list = walkInitializerListExpression(ile); + 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 JavaLanguageFeatureNotSupportedException("ConditionalExpression not supported yet"); + } + 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(new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.FUNCTION))); + nodeStack.add(le); + assert nextEOG.size() == 1; + nextNode = nextEOG.getFirst(); + } + case EmptyStatement es -> { + // occurs, for example, when the while loop body is empty or when ";" is only statement in a line -> we should not + // return then + assert nextEOG.size() == 1; + if (";".equals(es.getCode())) { + nextNode = nextEOG.getFirst(); + } else { + 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 JavaLanguageFeatureNotSupportedException("ContinueStatement not supported yet"); + case BreakStatement _ -> { + if (methodAnalysisMode) { + assert nextEOG.size() == 1; + nodeStack.add(nextEOG.getFirst()); + return null; + } + throw new JavaLanguageFeatureNotSupportedException("BreakStatement not supported yet"); + } + case DoStatement _ -> throw new JavaLanguageFeatureNotSupportedException("DoStatement not supported yet"); + default -> throw new IllegalStateException("Unexpected value: " + node); + } + assert nextNode != null; + return graphWalker(nextNode); + } + + private void walkMemberExpression(@NotNull MemberExpression me) { + de.jplag.java_cpg.ai.variables.Type expectedCpgType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(me.getType()); + if (me.getAssignedTypes().size() != 1) { // sometimes cpg does not set the type right (mostly with arraylists defined in class) + expectedCpgType = new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.UNKNOWN); + } + if (me.getRefersTo() instanceof FieldDeclaration || me.getRefersTo() instanceof EnumConstantDeclaration) { + if (valueStack.getLast() instanceof IJavaObject javaObject) { + nodeStack.removeLast(); + // like Reference + nodeStack.add(me); + assert me.getName().getParent() != null; + valueStack.removeLast(); // remove object reference + IValue result = javaObject.accessField(me.getName().getLocalName(), expectedCpgType); + 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(new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT))); + valueStack.add(result); + } + } else if (me.getRefersTo() instanceof MethodDeclaration) { + nodeStack.removeLast(); + nodeStack.add(me); + } else { + // unknown: look at the last item on the value stack + IValue value = valueStack.getLast(); + if (value instanceof VoidValue) { + valueStack.removeLast(); // remove object reference + valueStack.add(new VoidValue()); + } else { + if (me.getRefersTo() == null) { + throw new CpgErrorException("MemberExpression refers to null"); + } + throw new IllegalStateException("Unexpected value: " + value); + } + nodeStack.removeLast(); + nodeStack.add(me); + } + } + + private void walkReference(@NotNull Reference ref) { // adds its value to the value stack + de.jplag.java_cpg.ai.variables.Type expectedType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(ref.getType()); + if (ref.getName().getLocalName().equals("this")) { + valueStack.add(this.object); + } else if (ref.getRefersTo() instanceof FieldDeclaration fieldDeclaration && fieldDeclaration.getName().toString().equals("Comparator")) { + throw new JavaLanguageFeatureNotSupportedException("Comparators are not supported yet."); + } else { + Variable variable = variables.getVariable(new VariableName(ref.getName().toString())); + if (variable != null) { + valueStack.add(Objects.requireNonNull(variables.getVariable(new VariableName(ref.getName().toString()))).getValue()); + } else if (object.accessField(ref.getName().toString(), + new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.UNKNOWN)) != null) { + // sometimes cpg does not insert "this". + IValue value = object.accessField(ref.getName().toString(), expectedType); + if (value.getType().equals(new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.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 ref.getNextEOG().size() == 1 || (ref.getNextEOG().size() == 2 && ref.getNextEOG().getLast() instanceof ShortCircuitOperator); + + } + + 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(new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.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.fromCpgType(se.getArrayExpression().getType())); + if (!(ref instanceof IJavaArray)) { // cpg error + ref = Value.getNewArayValue(); + } + } + 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; + de.jplag.java_cpg.ai.variables.Type expectedType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(mce.getType()); + 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(); + de.jplag.java_cpg.ai.variables.Type objectType = new de.jplag.java_cpg.ai.variables.Type( + de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT); // ToDo: insert right object name + valueStack.add(new JavaObject(new AbstractInterpretation(visitedLinesRecorder, removeDeadCode, recordingChanges, ANONYMOUS_THIS_NAME), + objectType)); + } + if (!(valueStack.getLast() instanceof IJavaObject) && memberName.getLocalName().equals("intValue")) { // special case + assert valueStack.getLast() instanceof INumberValue; + result = valueStack.getLast(); + } else if (!(valueStack.getLast() instanceof IJavaObject) && memberName.getLocalName().equals("toString")) { + result = valueStack.getLast().unaryOperation("toString"); + } else { + IJavaObject javaObject = (JavaObject) valueStack.getLast(); + if (mce.getInvokes().isEmpty()) { // CPG sometimes cannot find the method declaration + result = Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.fromCpgType(mce.getType())); + } else { + if (!javaObject.hasAbstractInterpretation() && !methodAnalysisMode) { + javaObject.setAbstractInterpretation( + new AbstractInterpretation(visitedLinesRecorder, removeDeadCode, recordingChanges, ANONYMOUS_THIS_NAME)); + } + result = javaObject.callMethod(memberName.getLocalName(), null, (MethodDeclaration) mce.getInvokes().getLast(), expectedType); + } + } + } 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(); + de.jplag.java_cpg.ai.variables.Type objectType = new de.jplag.java_cpg.ai.variables.Type( + de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT); // ToDo: insert right object name + valueStack.add(new JavaObject(new AbstractInterpretation(visitedLinesRecorder, removeDeadCode, recordingChanges, ANONYMOUS_THIS_NAME), + objectType)); + } + if (!(valueStack.getLast() instanceof IJavaObject) && memberName.getLocalName().equals("equals")) { // special case + assert argumentList.size() == 1; + result = valueStack.getLast().binaryOperation("==", argumentList.getFirst()); + } else if (!(valueStack.getLast() instanceof IJavaObject) && memberName.getLocalName().equals("compareTo")) { // special case + assert argumentList.size() == 1; + argumentList.add(valueStack.getLast()); + Collections.reverse(argumentList); + result = new de.jplag.java_cpg.ai.variables.objects.Double().callMethod("compare", argumentList, null, expectedType); + } else { + JavaObject javaObject = (JavaObject) valueStack.getLast(); + if (!javaObject.hasAbstractInterpretation() && !methodAnalysisMode) { + javaObject.setAbstractInterpretation( + new AbstractInterpretation(visitedLinesRecorder, removeDeadCode, recordingChanges, ANONYMOUS_THIS_NAME)); + } + result = javaObject.callMethod(memberName.getLocalName(), argumentList, + (!mce.getInvokes().isEmpty()) ? (MethodDeclaration) mce.getInvokes().getLast() : null, expectedType); + } + } + 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 void walkDeclarationStatement(@NotNull DeclarationStatement ds) { + for (int i = ds.getDeclarations().size() - 1; i >= 0; i--) { + de.jplag.java_cpg.ai.variables.Type expectedType = de.jplag.java_cpg.ai.variables.Type + .fromCpgType(((VariableDeclaration) ds.getDeclarations().get(i)).getType()); + 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 { + if (valueStack.isEmpty()) { + valueStack.add(new VoidValue()); + nodeStack.addLast(new Node()); + } + if (valueStack.getLast() instanceof IJavaObject javaObject && javaObject.isNull() + && expectedType.getTypeEnum() != de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT) { + // Fix for missing types in null literals + valueStack.removeLast(); + valueStack.add(Value.valueFactory(expectedType)); + valueStack.getLast().setInitialValue(); + } + 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 (ds.getNextEOG().getFirst() instanceof ForEachStatement) { + valueStack.add(variable.getValue()); + } + } + } + assert ds.getNextEOG().size() == 1; + } + + private Node walkAssignExpression(@NotNull AssignExpression ae) { + if (!ae.getRhs().isEmpty() && ae.getRhs().getFirst() instanceof ProblemExpression) { + // cpg does not properly translate x = switch() {...} + throw new CpgErrorException("CPG error: switch expression is not properly translated, cannot analyze assignment expression"); + } + 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; + if (ae.getOperatorCode().equals("+=") || ae.getOperatorCode().equals("-=")) { + de.jplag.java_cpg.ai.variables.Type expectedType = new de.jplag.java_cpg.ai.variables.Type( + de.jplag.java_cpg.ai.variables.Type.TypeEnum.UNKNOWN); + IValue oldValue = classVal.accessField((nodeStack.get(nodeStack.size() - 2)).getName().getLocalName(), expectedType); + assert oldValue != null; + IValue newValue; + if (ae.getOperatorCode().equals("+=")) { + newValue = oldValue.binaryOperation("+", valueStack.getLast()); + } else { + newValue = oldValue.binaryOperation("-", valueStack.getLast()); + } + classVal.changeField((nodeStack.get(nodeStack.size() - 2)).getName().getLocalName(), newValue); + } else { + classVal.changeField((nodeStack.get(nodeStack.size() - 2)).getName().getLocalName(), valueStack.getLast()); + } + } else { + if (ae.getOperatorCode().equals("+=")) { + variable.setValue(variable.getValue().binaryOperation("+", valueStack.getLast())); + } else if (ae.getOperatorCode().equals("-=")) { + variable.setValue(variable.getValue().binaryOperation("-", 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()); + } + if (!(valueStack.get(valueStack.size() - 2) instanceof BooleanValue)) { + valueStack.remove(valueStack.size() - 2); + } + 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 (scop.getNextEOG().size() == 1 && scop.getNextEOG().getFirst() instanceof AssignExpression) { + nodeStack.removeLast(); + nodeStack.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 JavaLanguageFeatureNotSupportedException(scop.getOperatorCode() + " is not supported in ShortCircuitOperator"); + } + assert scop.getNextEOG().size() == 1 || scop.getNextEOG().size() == 2; + return scop.getNextEOG().getFirst(); + } + + private void walkBinaryOperator(@NotNull 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); + } + + private Node walkUnaryOperator(@NotNull 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); + if (uop.getNextEOG().isEmpty()) { // when throw is the last statement in a block + ReturnStatement nextNode = new ReturnStatement(); + nextNode.setReturnValue(new Expression() { + }); + return nextNode; + } else { + assert uop.getNextEOG().size() == 1 || (uop.getNextEOG().size() == 2 && uop.getNextEOG().getLast() instanceof ShortCircuitOperator); + return uop.getNextEOG().getFirst(); + } + } + + @Nullable + private Node walkIfStatement(@NotNull IfStatement ifStmt) { + Node nextNode; + List nextEOG = ifStmt.getNextEOG(); + boolean elsePresent = ifStmt.getElseStatement() != null; + // 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 : "Expected BooleanValue on value stack, but found: " + valueStack.getLast().getClass(); + 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) { + 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 + 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(); // ToDo: can be other ifStatement and not block + // (BoardGame/human/subm304) + assert containingBlock != null; + List statements = containingBlock.getStatements(); + statements.remove(ifStmt); + containingBlock.setStatements(statements); + } + } + } + } + nodeStack.removeLast(); // remove condition + if (nextEOG.size() != 2) { + throw new CpgErrorException("Expected 2 statements in if next EOG, found: " + nextEOG.size()); + } + VariableStore originalVariables = variables; + VariableStore thenVariables = new VariableStore(variables); + VariableStore elseVariables = new VariableStore(variables); + // then statement + if (runThenBranch) { + boolean restoreBlock = false; + if (!(ifStmt.getThenStatement() instanceof Block) && !(ifStmt.getThenStatement() instanceof BranchingNode)) { + restoreBlock = true; + Block block = new Block(); + block.setNextEOG(ifStmt.getThenStatement().getNextEOG()); + ifStmt.getThenStatement().setNextEOG(List.of(block)); + } + variables.newScope(); + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + if (restoreBlock) { + ifStmt.getThenStatement().setNextEOG(ifStmt.getThenStatement().getNextEOG().getFirst().getNextEOG()); + } + if (!nodeStack.isEmpty() + && (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++; + } + boolean restoreBlock = false; + if (!(ifStmt.getElseStatement() instanceof Block) && !(ifStmt.getElseStatement() instanceof BranchingNode)) { + restoreBlock = true; + Block block = new Block(); + block.setNextEOG(ifStmt.getElseStatement().getNextEOG()); + ifStmt.getElseStatement().setNextEOG(List.of(block)); + } + if (runThenBranch) { + variables = elseVariables; + this.object = variables.getThisObject(); + } + variables.newScope(); + graphWalker(nextEOG.getLast()); + variables.removeScope(); + if (nodeStack.getLast() == null) { + nodeStack.add(nextEOG.getFirst()); + } + if (restoreBlock) { + ifStmt.getElseStatement().setNextEOG(ifStmt.getElseStatement().getNextEOG().getFirst().getNextEOG()); + } + } + // 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 ((elsePresent || condition.getInformation()) + && (returnStorage.size() >= 2 || (!returnStorage.isEmpty() && (runThenBranch != runElseBranch) && condition.getInformation()))) { + if (ifStmt.getElseStatement() instanceof IfStatement) { // problem: if else branch is if else + return nextNode; + } + // return in every branch + valueStack.add(returnStorage.getLast()); + nextNode = new ReturnStatement(); + ((ReturnStatement) nextNode).setReturnValue(new Expression() { + }); + } + return nextNode; + } + + private IValue walkReturnStatement(@NotNull ReturnStatement rs) { + IValue result; + if (rs.getReturnValues().isEmpty() || 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) { + if (result instanceof NullValue || result instanceof JavaObject javaObject && javaObject.isNull()) { + result = Value.valueFactory(value.getType()); + result.setInitialValue(); + } + result.merge(value); + } + returnStorage.clear(); + } + nodeStack.add(null); + return result; + } + + @NotNull + private Node walkConstructExpression(@NotNull ConstructExpression ce) { + // inside Constructors, no NewExpression nodes come after ConstructExpression nodes + if (inConstructor && !(ce.getNextEOG().getFirst() instanceof NewExpression)) { + ConstructorDeclaration constructor = ce.getConstructor(); + if (constructor == null) { + if (ce.getName().toString().equals("java.lang.Exception") || ce.getName().toString().equals("java.lang.RuntimeException") + || ce.getName().toString().equals("java.lang.Throwable") + || ce.getName().toString().equals("java.lang.IllegalArgumentException")) { + // skip exception + assert ce.getNextEOG().size() == 1; + return ce.getNextEOG().getFirst(); + } + throw new CpgErrorException("Constructor not present in ConstructExpression"); + } + 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 ce.getNextEOG().size() == 1; + return ce.getNextEOG().getFirst(); + } + + private Node walkSwitchStatement(@NotNull SwitchStatement sw) { // ToDo delete dead Code in switch + Node nextNode; + assert !valueStack.isEmpty(); + 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; + assert variables != null; + 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) { + ConstructExpression ce = (ConstructExpression) nodeStack.getLast(); + RecordDeclaration classNode = (RecordDeclaration) ce.getInstantiates(); + List arguments = new ArrayList<>(); + nodeStack.removeLast(); // remove ConstructExpression + if (!ce.getArguments().isEmpty()) { + if (ce.getArguments().stream().anyMatch(ProblemExpression.class::isInstance)) { + throw new CpgErrorException("ProblemExpression found in constructor arguments"); + } + int size = ce.getArguments().size(); + for (int i = 0; i < size; i++) { + arguments.add(valueStack.getLast()); + valueStack.removeLast(); + nodeStack.removeLast(); + } + } + Collections.reverse(arguments); + IJavaObject newObject = createNewObject(ce); + valueStack.add(newObject); + if (classNode == null) { // cpg has not found it, try to restore it + try { + ObjectType classType = (ObjectType) ce.getType(); + classNode = classType.getRecordDeclaration(); + } catch (Exception _) { + // give up + } + } + // run constructor + if (classNode != null && !methodAnalysisMode) { + AbstractInterpretation classAi = new AbstractInterpretation(visitedLinesRecorder, removeDeadCode, recordingChanges); + assert classNode != null; + classAi.runClass(classNode, newObject, arguments, 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) { + if (variables.getVariable(variable.getName()) != null) { + 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(); + List returnStorageBefore = returnStorage; + returnStorage = new ArrayList<>(); + 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) { + if (variables.getVariable(variable.getName()) != null) { + variables.getVariable(variable.getName()).setToUnknown(); + } + } + variables.newScope(); + returnStorage = returnStorageBefore; + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + // 3: restore variables and set changed variables to unknown + this.variables = originalVariables; + for (Variable variable : changedVariables) { + if (variables.getVariable(variable.getName()) != null) { + Objects.requireNonNull(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); + } + // continue with the next node after the while loop + lastVisitedLoopOrIf.removeLast(); + nextNode = nextEOG.getLast(); + 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 + 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(); + List returnStorageBefore = returnStorage; + returnStorage = new ArrayList<>(); + 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) { + if (variables.getVariable(variable.getName()) != null) { + 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 if (ws.getIterationStatement() instanceof ExpressionList) { + throw new JavaLanguageFeatureNotSupportedException(" Expression List in for each not supported yet."); + } else { + throw new IllegalStateException(); + } + if (iterVar != null) { + iterVar.setToUnknown(); + } + } + variables.newScope(); + returnStorage = returnStorageBefore; + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + // 3: restore variables and set changed variables to unknown + this.variables = originalVariables; + for (Variable variable : changedVariables) { + if (variables.getVariable(variable.getName()) != null) { + Objects.requireNonNull(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); + } + // continue with the next node after the for loop + lastVisitedLoopOrIf.removeLast(); + nextNode = nextEOG.getLast(); + 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(); + assert fes.getIterable() != null; + de.jplag.java_cpg.ai.variables.Type arrayType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(((HasType) fes.getIterable()).getType()); + if (arrayType.getTypeEnum() == de.jplag.java_cpg.ai.variables.Type.TypeEnum.ARRAY + || arrayType.getTypeEnum() == de.jplag.java_cpg.ai.variables.Type.TypeEnum.LIST) { + valueStack.add(Value.valueFactory(arrayType)); + } else { + valueStack.add(Value.valueFactory(new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.LIST, + new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.UNKNOWN)))); + } + } + assert valueStack.getLast() instanceof IJavaArray : "Expected array value in for each, but got: " + valueStack.getLast(); + 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", + new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.INT)) 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); + } + } 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) { + Objects.requireNonNull(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(); + List returnStorageBefore = returnStorage; + returnStorage = new ArrayList<>(); + 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) { + if (variables.getVariable(variable.getName()) != null) { + Objects.requireNonNull(variables.getVariable(variable.getName())).setToUnknown(); + } + } + variables.newScope(); + returnStorage = returnStorageBefore; + graphWalker(nextEOG.getFirst()); + variables.removeScope(); + // 3: restore variables and set changed variables to unknown + this.variables = originalVariables; + for (Variable variable : changedVariables) { + Variable variable2 = variables.getVariable(variable.getName()); + if (variable2 != null) { + variable2.setToUnknown(); + } + } + } + } + // continue with the next node after for each + lastVisitedLoopOrIf.removeLast(); + nextNode = nextEOG.getLast(); + return nextNode; + } + + @NotNull + private IJavaArray walkInitializerListExpression(@NotNull InitializerListExpression ile) { + IJavaArray list; + if (ile.getInitializers().stream().anyMatch(ProblemExpression.class::isInstance)) { // catch cpg issues + list = Value.getNewArayValue(); + } else { + 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(); + } + list = Value.getNewArayValue(arguments); + } + return list; + } + + 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(new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.INT))); + } else { + dimensions.add((INumberValue) valueStack.getLast()); + } + valueStack.removeLast(); + nodeStack.removeLast(); + } + Collections.reverse(dimensions); // Dimensions are popped in reverse order + // recover inner type (element type, not array type) + de.jplag.java_cpg.ai.variables.Type innerType = null; + if (nae.getTypeObservers().iterator().hasNext() + && ((HasType) nae.getTypeObservers().iterator().next()).getType() instanceof PointerType pointerType) { + de.fraunhofer.aisec.cpg.graph.types.Type elementType = pointerType.getElementType(); + while (elementType instanceof PointerType pt) { + elementType = pt.getElementType(); + } + innerType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(elementType); + } + // Build from innermost to outermost + IJavaArray newArray = Value.getNewArayValue(innerType, dimensions.getLast()); + for (int i = dimensions.size() - 2; i >= 0; i--) { + INumberValue dimension = dimensions.get(i); + if (dimension.getInformation()) { + List innerArrays = new ArrayList<>(); + for (int j = 0; j < dimension.getValue(); j++) { + innerArrays.add(Value.getNewArayValue(innerType, dimensions.get(i + 1))); + } + newArray = Value.getNewArayValue(innerArrays); + } else { + // Dimension is unknown - create an array with an unknown size + newArray = Value.getNewArayValue( + new de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.ARRAY, innerType), dimension); + break; + } + } + valueStack.add(newArray); + } else if (nae.getInitializer() != null) { + if (nae.getPrevEOG().getFirst() instanceof InitializerListExpression) { + // initializer has already been processed + assert valueStack.getLast() instanceof IJavaArray; + 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; + } + + private @NotNull FunctionDeclaration restoreFunctionDeclaration(RecordDeclaration recordDeclaration, FunctionDeclaration original, + List candidates) { + try { + assert recordDeclaration != null; + // match on name and arguments + for (FunctionDeclaration candidate : candidates) { + if (candidate.getName().getLocalName().equals(original.getName().getLocalName()) + && candidate.getParameters().size() == original.getParameters().size()) { + boolean parametersMatch = true; + for (int i = 0; i < original.getParameters().size(); i++) { + Type candidateType = candidate.getParameters().get(i).getType(); + Type methodType = original.getParameters().get(i).getType(); + // some of them can be wrong, but this is necessary + boolean typesMatch = candidateType.equals(methodType) + || (candidateType instanceof ParameterizedType && methodType instanceof ObjectType) + || (candidateType instanceof ObjectType && methodType instanceof ObjectType + && methodType.getTypeOrigin() == Type.Origin.UNRESOLVED) + || (candidateType instanceof ObjectType && methodType instanceof ObjectType + && methodType.getName().getLocalName().equals("E")) + || (candidateType.getTypeName().contains("List") && methodType.getTypeName().contains("List")) + || (candidateType instanceof IntegerType && methodType instanceof IntegerType) + || (candidateType instanceof FloatingPointType && methodType instanceof FloatingPointType) + || (candidateType instanceof FloatingPointType && methodType instanceof IntegerType); + if (!typesMatch) { + parametersMatch = false; + break; + } + } + if (parametersMatch && candidate.getBody() != null) { + visitedLinesRecorder.recordFirstLineVisited(candidate); + return candidate; + } + } + } + } catch (Exception _) { + // cannot restore method body -> give up + } + return original; + } + + /** + * 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. + * @param expectedType the expected return type of the method; used for type checking and to determine the return type + * of this method. + * @return null if the method is not known. + */ + public IValue runMethod(@NotNull String name, List paramVars, @Nullable MethodDeclaration method, + @NotNull de.jplag.java_cpg.ai.variables.Type expectedType) { + if (method == null) { + return Value.valueFactory(expectedType); + } + if (method.getBody() == null) { // cpg has lost the method body -> try to restore + assert method.getRecordDeclaration() != null; + method = (MethodDeclaration) restoreFunctionDeclaration(method.getRecordDeclaration(), method, + new ArrayList<>(method.getRecordDeclaration().getMethods())); + } + if (lastVisitedMethod.contains(method)) { + // recursive call detected + // ToDo: just set all changed variables to unknown like in loops + this.variables.setEverythingUnknown(); + return Value.valueFactory(expectedType); + } + lastVisitedMethod.add(method); + visitedLinesRecorder.recordFirstLineVisited(method); + for (FunctionDeclaration subMethod : method.getOverriddenBy()) { + visitedLinesRecorder.recordFirstLineVisited(subMethod); + } + boolean removeDeadCodeBackup = this.removeDeadCode; + int numberOfCalls = method.getUsages().size(); + if (numberOfCalls > 1) { + // method is called multiple times + removeDeadCode = false; + // ToDo: collect dead code in this methods and only remove if dead in all calls + } + ArrayList oldNodeStack = this.nodeStack; // Save stack + ArrayList oldValueStack = this.valueStack; + List oldLastVisitedLoopOrIf = this.lastVisitedLoopOrIf; + int oldIfElseCounter = this.ifElseCounter; + List oldReturnStorage = this.returnStorage; + this.returnStorage = new ArrayList<>(); + this.ifElseCounter = 0; + 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() || method.getType() instanceof FunctionType; + 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) { + if (continueOnError) { + try { + result = graphWalker(method.getNextEOG().getFirst()); + } catch (Exception _) { + variables.setEverythingUnknown(); + result = Value.valueFactory(expectedType); + } + } else { + result = graphWalker(method.getNextEOG().getFirst()); + } + } else { + result = Value.valueFactory(expectedType); + } + variables.removeScope(); + this.removeDeadCode = removeDeadCodeBackup; + this.nodeStack = oldNodeStack; // restore stack + this.valueStack = oldValueStack; + this.lastVisitedLoopOrIf = oldLastVisitedLoopOrIf; + lastVisitedMethod.removeLast(); + ifElseCounter = oldIfElseCounter; + this.returnStorage = oldReturnStorage; + return result; + } + + protected void setMethodAnalysisMode() { + this.methodAnalysisMode = true; + } + + /** + * @return true if this abstract interpretation engine is currently only analyzing a method without running any other + * methods or constructors, false otherwise. + */ + public boolean isMethodAnalysisMode() { + return this.methodAnalysisMode; + } + + /** + * If set to true, the abstract interpretation engine will continue execution even if it encounters an error during + * execution. This can lead to more imprecise results, but can be useful to still get some results even if the cpg is + * not perfect. + * @param continueOnError whether to continue on error or not + */ + public static void setContinueOnError(boolean continueOnError) { + AbstractInterpretation.continueOnError = continueOnError; + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AiMethodPass.kt b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AiMethodPass.kt new file mode 100644 index 0000000000..2002a9ef07 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AiMethodPass.kt @@ -0,0 +1,91 @@ +package de.jplag.java_cpg.ai + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.methods +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression +import de.fraunhofer.aisec.cpg.graph.types.FunctionType +import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.passes.TranslationUnitPass +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore +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.passes.CpgTransformationPass +import de.jplag.java_cpg.passes.TokenizationPass +import java.util.function.Consumer + +@DependsOn(CpgTransformationPass::class) +@ExecuteBefore(TokenizationPass::class) +class AiMethodPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + //passes have to be in kotlin! + + /** + * Empty cleanup method. + */ + override fun cleanup() { + // Nothing to do + } + + override fun accept(p0: TranslationUnitDeclaration) { + val visitedLinesRecorder = VisitedLinesRecorder() + val methods: List = p0.methods + for (method in methods) { + if (method.hasBody() && method !is ConstructorDeclaration) { + analyseMethod(method, visitedLinesRecorder) + } + } + deadLinesCallback!!.accept(visitedLinesRecorder.deadLinesCount) + deadCountCallback!!.accept(visitedLinesRecorder.deadCodeCount) + } + + fun analyseMethod(method: MethodDeclaration, visitedLinesRecorder: VisitedLinesRecorder) { + val abstractInterpretation = AbstractInterpretation(visitedLinesRecorder, removeDeadCode) + AbstractInterpretation.setContinueOnError(continueOnError) + abstractInterpretation.setMethodAnalysisMode() + val recordName: String = method.recordDeclaration?.name?.toString() ?: "Main" + val javaObject = JavaObject( + de.jplag.java_cpg.ai.variables.Type( + de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT, + recordName + ) + ) + abstractInterpretation.setupObject(javaObject, "this") + + val paramVars = ArrayList() + for (paramDecl in method.parameters) { + val type: Type = paramDecl.type + val cpgType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(type) + var value: IValue + if (cpgType == de.jplag.java_cpg.ai.variables.Type(de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT)) { + val constructExpression = ConstructExpression() + constructExpression.name = paramDecl.name + constructExpression.type = type + value = AbstractInterpretation.createNewObject(constructExpression) + } else { + value = Value.valueFactory(de.jplag.java_cpg.ai.variables.Type.fromCpgType(type)) + } + paramVars.add(value) + } + runCatching { + abstractInterpretation.runMethod( + method.name.toString(), paramVars, method, + de.jplag.java_cpg.ai.variables.Type.fromCpgType((method.type as FunctionType).returnTypes.first()) + ) + }.onFailure { t -> + log.error("AI pass failed for method: ${method.name}", t) + throw t + } + } + + companion object AiMethodPassCompanion { + var removeDeadCode: Boolean = true + var continueOnError: Boolean = false + var deadLinesCallback: Consumer? = null + var deadCountCallback: Consumer? = null + } + +} 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..4272edaaf9 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/AiPass.kt @@ -0,0 +1,158 @@ +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.function.Consumer + +/** + * 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() + AbstractInterpretation.setContinueOnError(continueOnError) + val abstractInterpretation = AbstractInterpretation(visitedLinesRecorder, removeDeadCode) + assert(p0.components.size == 1) + 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 + } + } + } + //find dead methods and classes + try { + for (translationUnit in comp.translationUnits) { + for (recordDeclaration in translationUnit.records) { + if (checkIfCompletelyDead(recordDeclaration, visitedLinesRecorder) && removeDeadCode) { + // 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) { + if (method.name.localName == "toString" || method.name.localName == "equals" || method.name.localName == "hashCode" + || method.name.localName == "compareTo" || method.name.localName == "compare" + ) { + continue //methods that are sometimes not visited by the AI but could still be called implicitly + //this is only a last resort as methods called inside these methods may still incorrectly be detected as dead code + } + val index = recordDeclaration.methods.indexOf(method) + recordDeclaration.methodEdges.removeAt(index) + } + } + //inner classes + for (innerClass in recordDeclaration.records) { + if (checkIfCompletelyDead(innerClass, visitedLinesRecorder) && removeDeadCode) { + val index = recordDeclaration.records.indexOf(innerClass) + recordDeclaration.recordEdges.removeAt(index) + innerClass.disconnectFromGraph() + } + } + } + } + } catch (e: Exception) { + log.error("Error while detecting dead classes and methods", e) + } + deadLinesCallback!!.accept(visitedLinesRecorder.deadLinesCount) + deadCountCallback!!.accept(visitedLinesRecorder.deadCodeCount) + } + + 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 + var continueOnError: Boolean = false + var deadLinesCallback: Consumer? = null + var deadCountCallback: Consumer? = null + } + +} + +/** + * 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/CpgErrorException.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/CpgErrorException.java new file mode 100644 index 0000000000..912c573821 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/CpgErrorException.java @@ -0,0 +1,18 @@ +package de.jplag.java_cpg.ai; + +/** + * This exception is thrown when we encounter a malformed CPG Graph. + * @author ujiqk + * @version 1.0 + */ +public class CpgErrorException extends RuntimeException { + + /** + * Constructs a new CpgErrorException with the specified detail message. + * @param message the detail message. + */ + public CpgErrorException(String message) { + super(message); + } + +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/JavaLanguageFeatureNotSupportedException.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/JavaLanguageFeatureNotSupportedException.java new file mode 100644 index 0000000000..036eb641de --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/JavaLanguageFeatureNotSupportedException.java @@ -0,0 +1,18 @@ +package de.jplag.java_cpg.ai; + +/** + * This exception is thrown when a Java language feature is used that is not supported by the analysis. + * @author ujiqk + * @version 1.0 + */ +public class JavaLanguageFeatureNotSupportedException extends RuntimeException { + + /** + * Constructs a new JavaLanguageFeatureNotSupportedException with the specified detail message. + * @param message the detail message. + */ + public JavaLanguageFeatureNotSupportedException(String message) { + super(message); + } + +} 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..9e92f964bf --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/README.md @@ -0,0 +1,60 @@ +# 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 + +This module offers two passes for cpg. +The AiPass analyzes the whole cpg translation result. +The AiMethodPass analyzes every single method from a translation result independently. +Both of them use the AbstractInterpretation class, which is the main class in this module. + +## 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 + +## 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/VisitedLinesRecorder.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/VisitedLinesRecorder.java new file mode 100644 index 0000000000..592e5db135 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/VisitedLinesRecorder.java @@ -0,0 +1,195 @@ +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.Nullable; +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; + private int deadLinesCount = 0; + private int deadCodeCount = 0; + + /** + * 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 the first line visited in the given node + */ + public void recordFirstLineVisited(@Nullable Node node) { + if (node == null) { + return; + } + 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); + deadLinesCount += (endLine - startLine + 1); + deadCodeCount++; + } + + /** + * 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; + } + + /** + * @return the number of lines that have been detected to be dead code. + */ + @TestOnly + public int getDeadLinesCount() { + return deadLinesCount; + } + + /** + * @return the number of code regions that have been detected to be dead code. + */ + @TestOnly + public int getDeadCodeCount() { + return deadCodeCount; + } + +} 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..7ffcfd410a --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Scope.java @@ -0,0 +1,163 @@ +package de.jplag.java_cpg.ai.variables; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import de.jplag.java_cpg.ai.variables.values.IJavaObject; +import de.jplag.java_cpg.ai.variables.values.JavaObject; + +/** + * 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); + } + } + + /** + * Copy constructor. Performs a deep copy of the variables. + * @param scope the scope to copy. + * @param copiedObjects map of already copied objects to handle cyclic references. + */ + public Scope(@NotNull Scope scope, Map copiedObjects) { + for (Map.Entry entry : scope.variables.entrySet()) { + Variable clonedValue = new Variable(entry.getValue(), copiedObjects); + this.variables.put(entry.getKey(), 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) { + merge(otherScope, Collections.newSetFromMap(new IdentityHashMap<>())); + } + + /** + * 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. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + public void merge(@NotNull Scope otherScope, Set visited) { + for (Map.Entry entry : otherScope.variables.entrySet()) { + if (!(this.variables.containsKey(entry.getKey()))) { + entry.getValue().setToUnknown(); + continue; + } + this.variables.get(entry.getKey()).merge(entry.getValue(), visited); + } + otherScope.variables = this.variables; + } + + /** + * Sets all variables to completely unknown. + */ + public void setEverythingUnknown() { + setEverythingUnknown(Collections.newSetFromMap(new IdentityHashMap<>())); + } + + /** + * Sets all variables to completely unknown. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + public void setEverythingUnknown(Set visited) { + for (Variable variable : variables.values()) { + variable.setToUnknown(visited); + } + } + + /** + * Sets all variables to their initial value. The initial value depends on the variable type. + */ + public void setEverythingInitialValue() { + setEverythingInitialValue(Collections.newSetFromMap(new IdentityHashMap<>())); + } + + /** + * Sets all variables to their initial value. The initial value depends on the variable type. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + public void setEverythingInitialValue(Set visited) { + for (Variable variable : variables.values()) { + variable.setInitialValue(visited); + } + } + + /** + * 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..4e77e36501 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Type.java @@ -0,0 +1,253 @@ +package de.jplag.java_cpg.ai.variables; + +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.ARRAY; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.BOOLEAN; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.CHAR; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.FLOAT; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.FUNCTION; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.INT; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.LIST; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.OBJECT; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.STRING; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.UNKNOWN; +import static de.jplag.java_cpg.ai.variables.Type.TypeEnum.VOID; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import de.fraunhofer.aisec.cpg.graph.types.ObjectType; + +/** + * Enumeration of supported variable types. + * @author ujiqk + * @version 1.0 + */ +public class Type { + + /** + * Enumeration of supported variable types. + * @author ujiqk + * @version 1.0 + */ + public enum TypeEnum { + INT, + FLOAT, + STRING, + BOOLEAN, + OBJECT, + ARRAY, + LIST, + NULL, + FUNCTION, + CHAR, + UNKNOWN, + VOID; + } + + private final @NotNull TypeEnum typeEnum; + private final @Nullable String typeName; // only used for the OBJECT type to save the class name + private final @Nullable Type innerType; + + /** + * Constructor for non-OBJECT, non-ARRAY, non-LIST types. + * @param typeEnum the type enum of this type. + */ + public Type(@NotNull TypeEnum typeEnum) { + this(typeEnum, null, null); + // assert typeEnum != OBJECT && typeEnum != ARRAY && typeEnum != LIST; + assert typeEnum != ARRAY && typeEnum != LIST; + } + + /** + * Constructor for Array/List Types. + * @param typeEnum the type enum of this type. + * @param innerType the inner type of this type; only valid for the ARRAY and LIST types, otherwise null. + */ + public Type(@NotNull TypeEnum typeEnum, @Nullable Type innerType) { + this(typeEnum, null, innerType); + assert typeEnum == ARRAY || typeEnum == LIST; + } + + /** + * Constructor for Object Types. + * @param typeEnum the type enum of this type. + * @param typeName the type name of this type; only valid for the OBJECT type, otherwise null. + */ + public Type(@NotNull TypeEnum typeEnum, @Nullable String typeName) { + this(typeEnum, typeName, null); + assert typeEnum == OBJECT; + } + + /** + * Constructor for Types. + * @param typeEnum the type enum of this type. + * @param typeName the type name of this type; only valid for the OBJECT type, otherwise null. + * @param innerType the inner type of this type; only valid for the ARRAY and LIST types, otherwise null. + */ + public Type(@NotNull TypeEnum typeEnum, @Nullable String typeName, @Nullable Type innerType) { + this.typeEnum = typeEnum; + this.typeName = typeName; + this.innerType = innerType; + assert typeEnum == OBJECT || typeName == null; + assert (typeEnum == ARRAY || typeEnum == LIST) || innerType == null; + } + + /** + * @return the type name of this type; only valid for the OBJECT type, otherwise null. + */ + public String getTypeName() { + assert this.typeEnum == OBJECT; + assert typeName != null; + return typeName; + } + + /** + * @return the inner type of this type; only valid for the ARRAY and LIST types, otherwise null. + */ + public Type getInnerType() { + assert this.typeEnum == ARRAY || this.typeEnum == LIST; + if (this.innerType == null) { + return new Type(UNKNOWN); + } + return innerType; + } + + /** + * @return the type enum of this type. + */ + public @NotNull TypeEnum getTypeEnum() { + return typeEnum; + } + + /** + * @param cpgType CPG type to convert. + * @return the corresponding Type enum. + * @throws IllegalArgumentException if the CPG type is not supported. + */ + public static @NotNull Type fromCpgType(@NotNull de.fraunhofer.aisec.cpg.graph.types.Type cpgType) { + if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.IntegerType.class) { + return new Type(INT); + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.StringType.class) { + return new Type(STRING); + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.BooleanType.class) { + return new Type(BOOLEAN); + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.ObjectType.class && cpgType.getName().toString().contains("[]")) { + Type innerType = stringToType(cpgType.getTypeName().split("\\[")[0]); + return new Type(ARRAY, innerType); + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.ObjectType.class) { + String name = cpgType.getName().toString(); + name = name.split("<")[0]; // remove generics + switch (name) { + case "java.util.ArrayList", "java.util.List", "java.util.Vector", "java.util.LinkedList", "java.util.PriorityQueue", "java.util.Deque", "java.util.Stack", "java.util.ListIterator", "java.util.Queue", "java.util.Set", "java.util.HashSet" -> { + ObjectType objectType = (ObjectType) cpgType; + Type innerCpgType; + if (objectType.getGenerics().isEmpty()) { + innerCpgType = new Type(UNKNOWN); + } else { + assert objectType.getGenerics().size() == 1 : "Expected exactly one generic type for List, but got " + + objectType.getGenerics().size(); + de.fraunhofer.aisec.cpg.graph.types.Type innerType = objectType.getGenerics().getFirst(); + innerCpgType = de.jplag.java_cpg.ai.variables.Type.fromCpgType(innerType); + } + return new Type(LIST, innerCpgType); + } + case "java.lang.String" -> { + return new Type(STRING); + } + case "java.lang.Boolean" -> { + return new Type(BOOLEAN); + } + case "java.lang.Character" -> { + return new Type(CHAR); + } + case "java.lang.Integer", "java.lang.Long", "java.lang.Short", "java.lang.Byte" -> { + return new Type(INT); + } + case "java.lang.Float", "java.lang.Double" -> { + return new Type(FLOAT); + } + case "T", "E", "K", "V" -> { + return new Type(UNKNOWN); + } + default -> { + return new Type(OBJECT, name); // we lose generics here + } + } + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.PointerType.class) { + Type elementType = fromCpgType(((de.fraunhofer.aisec.cpg.graph.types.PointerType) cpgType).getElementType()); + return new Type(ARRAY, elementType); // in java pointer types are used only for arrays + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.FloatingPointType.class) { + return new Type(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 new Type(VOID); + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.ParameterizedType.class) { + return new Type(VOID); + } else if (cpgType.getClass() == de.fraunhofer.aisec.cpg.graph.types.FunctionType.class) { + return new Type(FUNCTION); + } else { + throw new IllegalArgumentException("Unsupported CPG type: " + cpgType); + } + } + + private static @NotNull Type stringToType(@NotNull String typeStr) { + return switch (typeStr) { + case "int" -> new Type(INT); + case "float", "double" -> new Type(FLOAT); + case "boolean" -> new Type(BOOLEAN); + case "char" -> new Type(CHAR); + case "void" -> new Type(VOID); + case "String", "java.lang.String" -> new Type(STRING); + case "T", "E", "K", "V" -> new Type(UNKNOWN); + default -> new Type(OBJECT, typeStr); + }; + } + + @Override + public @NotNull String toString() { + return switch (typeEnum) { + case INT -> "int"; + case FLOAT -> "float"; + case STRING -> "String"; + case BOOLEAN -> "boolean"; + case OBJECT -> typeName != null ? typeName : "Object"; + case ARRAY -> innerType != null ? innerType + "[]" : "Array"; + case LIST -> innerType != null ? "List<" + innerType + ">" : "List"; + case NULL -> "null"; + case FUNCTION -> "function"; + case CHAR -> "char"; + case UNKNOWN -> "unknown"; + case VOID -> "void"; + }; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Type type = (Type) o; + if (typeEnum != type.typeEnum) + return false; + if (typeEnum == OBJECT) { + return typeName != null && typeName.equals(type.typeName); + } else if (typeEnum == ARRAY || typeEnum == LIST) { + return innerType != null && innerType.equals(type.innerType); + } else { + return true; // for primitive types, only the type enum matters + } + } + + @Override + public int hashCode() { + int result = typeEnum.hashCode(); + if (typeEnum == OBJECT) { + result = 31 * result + (typeName != null ? typeName.hashCode() : 0); + } else if (typeEnum == ARRAY || typeEnum == LIST) { + result = 31 * result + (innerType != null ? innerType.hashCode() : 0); + } + return result; + } + +} 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..4db75c8453 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/Variable.java @@ -0,0 +1,199 @@ +package de.jplag.java_cpg.ai.variables; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; + +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.arrays.IJavaArray; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * 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 + } + + /** + * Copy constructor with copied objects map. + * @param variable the variable to deep copy. + * @param copiedObjects map of already copied JavaObjects to handle circular references. + */ + public Variable(@NotNull Variable variable, Map copiedObjects) { + this.name = variable.name; + this.value = variable.value.copy(copiedObjects); + this.changeRecorders = variable.changeRecorders; + } + + /** + * @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 other the variable whose content will be merged into this one; must have the same name. + */ + public void merge(@NotNull Variable other) { + merge(other, Collections.newSetFromMap(new IdentityHashMap<>())); + } + + /** + * 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 other the variable whose content will be merged into this one; must have the same name. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + public void merge(@NotNull Variable other, Set visited) { + assert this.changeRecorders.equals(other.changeRecorders); + assert other.name.equals(this.name); + if (this.value instanceof NullValue && !(other.value instanceof NullValue)) { + if (other.value instanceof IStringValue) { + this.value = Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } else if (other.value instanceof IJavaArray) { + this.value = Value.valueFactory(new Type(Type.TypeEnum.ARRAY)); + } else if (other.value instanceof IJavaObject) { + this.value = Value.valueFactory(new Type(Type.TypeEnum.OBJECT)); + } + } + this.value.merge(other.value, visited); + } + + /** + * Delete all content information in this variable. + */ + public void setToUnknown() { + setToUnknown(Collections.newSetFromMap(new IdentityHashMap<>())); + } + + /** + * Delete all content information in this variable. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + public void setToUnknown(Set visited) { + if (getValue() instanceof NullValue) { + setValue(Value.valueFactory(new Type(Type.TypeEnum.OBJECT))); + } else { + value.setToUnknown(visited); + } + 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() { + setInitialValue(Collections.newSetFromMap(new IdentityHashMap<>())); + } + + /** + * Reset all information in this variable expect type and name. Initial values depend on the type. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + public void setInitialValue(Set visited) { + value.setInitialValue(visited); + 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..79fa8eed68 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/VariableStore.java @@ -0,0 +1,195 @@ +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 org.jetbrains.annotations.Nullable; + +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; + +/** + * Stores variables in different scopes. + * @author ujiqk + * @version 1.0 + */ +public class VariableStore { + + /** + * The name used for the "this" object in anonymous classes. + */ + public static final VariableName ANONYMOUS_THIS_NAME = new VariableName("$this"); + private final ArrayList scopes = new ArrayList<>(); + private int currentScopeIndex = 0; + private VariableName thisObject; + private List changeRecorders = new ArrayList<>(); + + /** + * 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; + assert thisObject != null; + this.changeRecorders = variableStore.changeRecorders; // no deep copy + } + + /** + * 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(@NotNull Variable variable) { + scopes.get(currentScopeIndex).addVariable(variable); + } + + /** + * @param thisName the name of the {@link JavaObject} this variable store is associated with. + */ + public void setThisName(@NotNull VariableName thisName) { + this.thisObject = thisName; + } + + /** + * @return the {@link IJavaObject} this variable store is associated with, or null if not set. + */ + @NotNull + public IJavaObject getThisObject() { + assert thisObject != null; + Variable variable = getVariable(thisObject); + if (variable == null) { // does not exist yet -> create it + assert thisObject == ANONYMOUS_THIS_NAME; + Variable thisClassVar = new Variable(thisObject, new JavaObject(new Type(Type.TypeEnum.OBJECT))); + for (ChangeRecorder recorder : changeRecorders) { + thisClassVar.addChangeRecorder(recorder); + } + scopes.getFirst().addVariable(thisClassVar); + variable = getVariable(thisObject); + assert variable != null; + } + IValue value = variable.getValue(); + assert value instanceof IJavaObject; + return (JavaObject) value; + } + + /** + * @param name the name of the variable to retrieve. + * @return the variable with the given name or null if it does not exist. + */ + @Nullable + public Variable getVariable(@NotNull VariableName name) { + 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. + */ + @Nullable + 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); + } + this.changeRecorders.add(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() { + this.changeRecorders.removeLast(); + 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..9e31cec5cd --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Arrays.java @@ -0,0 +1,110 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.arrays.IJavaArray; + +/** + * 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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "toString" -> { + assert paramVars.size() == 1; + IJavaArray array = (IJavaArray) paramVars.getFirst(); + return array.callMethod("toString", List.of(), null, expectedType); + } + 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; + IJavaArray array = (IJavaArray) paramVars.getFirst(); + return array.callMethod("fill", paramVars.subList(1, paramVars.size()), null, expectedType); + } + 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; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.getNewArayValue()); + } + IJavaArray array = (IJavaArray) paramVars.getFirst(); + return array.callMethod("sort", paramVars.subList(1, paramVars.size()), null, expectedType); + } + case "copyOfRange" -> { // int[] copyOfRange(int[] original, int from, int to) + assert paramVars.size() == 3; + IJavaArray array = (IJavaArray) paramVars.getFirst(); + return array.callMethod("copyOfRange", paramVars.subList(1, paramVars.size()), null, expectedType); + } + case "asList" -> { // List asList(T... a) + return Value.getNewArayValue(); + } + case "stream" -> { // Stream stream(T[] array) + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.getNewArayValue()); + } + IJavaArray array = (IJavaArray) paramVars.getFirst(); + return array.callMethod(methodName, List.of(), null, expectedType); + } + case "copyOf" -> { + assert paramVars.size() == 2 || paramVars.size() == 3; + return Value.getNewArayValue(); + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Arrays(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..f486a0952f --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Boolean.java @@ -0,0 +1,78 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "parseBoolean" -> { + assert paramVars.size() == 1; + IValue value = paramVars.getFirst(); + switch (value) { + case IStringValue str -> { + return str.callMethod("parseBoolean", paramVars, null, expectedType); + } + default -> throw new IllegalStateException("Unexpected value: " + value); + } + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Boolean(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..f772edd5cf --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Double.java @@ -0,0 +1,101 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "parseDouble", "valueOf" -> { + assert paramVars.size() == 1; + IValue value = paramVars.getFirst(); + switch (value) { + case IStringValue str -> { + return str.callMethod("parseDouble", paramVars, null, expectedType); + } + case INumberValue num -> { + if (num.getInformation()) { + return Value.getNewFloatValue(num.getValue()); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + } + } + case VoidValue _ -> { + return Value.valueFactory(new Type(Type.TypeEnum.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(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..f0853e27cd --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/HashMap.java @@ -0,0 +1,132 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.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(new Type(Type.TypeEnum.OBJECT)); + } + + /** + * @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, @NotNull Type expectedType) { + // ToDo: not yet implemented + switch (methodName) { + case "put" -> { + assert paramVars.size() == 2; + return Value.valueFactory(expectedType); + } + case "get" -> { + assert paramVars.size() == 1; + return Value.valueFactory(expectedType); + } + case "containsKey" -> { + assert paramVars.size() == 1; + return Value.valueFactory(expectedType); + } + case "size" -> { + assert paramVars == null || paramVars.size() == 0; + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case "remove" -> { + assert paramVars.size() == 1; + return Value.valueFactory(expectedType); + } + case "putAll" -> { + assert paramVars.size() == 1; + return Value.valueFactory(expectedType); + } + case "clear" -> { + assert paramVars == null || paramVars.size() == 0; + return Value.valueFactory(expectedType); + } + case "keySet" -> { + assert paramVars == null || paramVars.size() == 0; + return Value.valueFactory(expectedType); + } + case "clone" -> { + assert paramVars == null || paramVars.size() == 0; + return new HashMap(); + } + case "entrySet" -> { + assert paramVars == null || paramVars.size() == 0; + return Value.valueFactory(expectedType); + } + case "getOrDefault" -> { + assert paramVars.size() == 2; + return Value.valueFactory(expectedType); + } + case "replace" -> { + assert paramVars.size() == 2 || paramVars.size() == 3; + return Value.valueFactory(expectedType); + } + case "replaceAll" -> { + assert paramVars.size() == 1; + return Value.valueFactory(expectedType); + } + case "putIfAbsent" -> { + assert paramVars.size() == 2; + return Value.valueFactory(expectedType); + } + case "isEmpty" -> { + assert paramVars == null || paramVars.size() == 0; + assert expectedType.getTypeEnum() == Type.TypeEnum.BOOLEAN || expectedType.getTypeEnum() == Type.TypeEnum.UNKNOWN; + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @NotNull + @Override + public JavaObject copy() { + return new HashMap(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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/HashSet.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/HashSet.java new file mode 100644 index 0000000000..b04bf0fd1e --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/HashSet.java @@ -0,0 +1,104 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.arrays.IJavaArray; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +/** + * Representation of the java.util.HashSet class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class HashSet extends JavaObject implements ISpecialObject, IJavaArray { + + private static final java.lang.String PATH = "java.util"; + private static final java.lang.String NAME = "HashSet"; + + /** + * Representation of the java.util.HashMap class. + */ + public HashSet() { + super(new Type(Type.TypeEnum.OBJECT)); + } + + /** + * @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, @NotNull Type expectedType) { + // ToDo: not yet implemented + switch (methodName) { + case "add" -> { + assert paramVars.size() == 1 : "add expects 1 parameter"; + return Value.valueFactory(expectedType); + } + case "remove" -> { + assert paramVars.size() == 1 : "remove expects 1 parameter"; + return Value.valueFactory(expectedType); + } + case "contains" -> { + assert paramVars.size() == 1 : "contains expects 1 parameter"; + return Value.valueFactory(expectedType); + } + case "size" -> { + assert paramVars == null || paramVars.isEmpty() : "size expects 0 parameters"; + return Value.valueFactory(expectedType); + } + case "first" -> { + assert paramVars == null || paramVars.isEmpty() : "first expects 0 parameters"; + return Value.valueFactory(expectedType); + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @NotNull + @Override + public JavaObject copy() { + return new HashSet(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof HashSet; + // Nothing to merge yet + } + + @Override + public IValue arrayAccess(INumberValue index) { + return new VoidValue(); + } + + @Override + public void arrayAssign(INumberValue index, IValue value) { + // Do nothing + } + +} 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..2190857c74 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/ISpecialObject.java @@ -0,0 +1,12 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import de.jplag.java_cpg.ai.variables.values.IJavaObject; + +/** + * Interface for special Java objects like java.lang.Double. + * @author ujiqk + * @version 1.0 + */ +public interface ISpecialObject extends IJavaObject { + +} 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..42df5ac70c --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/InputStream.java @@ -0,0 +1,75 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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(new Type(Type.TypeEnum.OBJECT)); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "read" -> { + if (paramVars == null || paramVars.isEmpty()) { + return Value.valueFactory(new Type(Type.TypeEnum.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(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..ff909891b2 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Integer.java @@ -0,0 +1,110 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.IIntNumber; +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.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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "parseInt", "valueOf" -> { + assert paramVars.size() == 1; + IValue value = paramVars.getFirst(); + switch (value) { + case IStringValue str -> { + return str.callMethod("parseInt", paramVars, null, expectedType); + } + case VoidValue _ -> { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case IIntNumber intNumber -> { + return intNumber; + } + default -> throw new IllegalStateException("Unexpected value: " + value); + } + } + case "toString" -> { + if (paramVars.size() == 2) { // with radix + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + assert paramVars.size() == 1 : "toString method of Integer expects exactly one parameter but got " + paramVars.size(); + IValue value = paramVars.getFirst(); + switch (value) { + case INumberValue intValue -> { + if (intValue.getInformation()) { + return Value.valueFactory(java.lang.Double.toString(intValue.getValue())); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + } + case VoidValue _ -> { + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + default -> throw new IllegalStateException("Unexpected value: " + value); + } + } + default -> { + return new VoidValue(); + } + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Integer(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..e11fdeed95 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Math.java @@ -0,0 +1,153 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + 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 : "Expected first parameter of min to be a number, but got " + + paramVars.get(0).getType(); + 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(new Type(Type.TypeEnum.FLOAT)); + } + assert paramVars.getFirst() instanceof INumberValue; + return paramVars.getFirst().unaryOperation("sin"); + } + case "random" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + } + case "ceil" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + return Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + } + assert paramVars.getFirst() instanceof INumberValue; + return paramVars.getFirst().unaryOperation("ceil"); + } + case "floorMod" -> { + assert paramVars.size() == 2; + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case "floor" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + return Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + } + assert paramVars.getFirst() instanceof INumberValue; + return paramVars.getFirst().unaryOperation("floor"); + } + case "round" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + assert paramVars.getFirst() instanceof INumberValue; + return paramVars.getFirst().unaryOperation("round"); + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Math(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..2bbf913d1b --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Pattern.java @@ -0,0 +1,83 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.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; + +/** + * 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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "matches", "find" -> { + return new BooleanValue(); + } + case "compile", "matcher" -> { + assert paramVars.size() == 1 || paramVars.size() == 2; + return new Pattern(); + } + case "toString" -> { + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + case "group" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Pattern(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..dc12b48445 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/PrintStream.java @@ -0,0 +1,80 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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; + +/** + * 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(new Type(Type.TypeEnum.OBJECT)); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "println", "print", "printf" -> { + // do nothing + return new VoidValue(); + } + case "format" -> { + // do nothing & return this + return this; + } + case "flush" -> { + // do nothing & return this + return this; + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new PrintStream(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..30aa7677f5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Random.java @@ -0,0 +1,82 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "nextInt" -> { + if (paramVars == null || paramVars.isEmpty()) { + return Value.valueFactory(new Type(Type.TypeEnum.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, @NotNull Type expectedType) { + switch (fieldName) { + default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported in " + PATH + "." + NAME); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Random(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..dae472ada7 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/Scanner.java @@ -0,0 +1,112 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.JavaLanguageFeatureNotSupportedException; +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(new Type(Type.TypeEnum.OBJECT)); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "nextLine", "next" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + case "close" -> { + assert paramVars == null || paramVars.isEmpty(); + return new VoidValue(); + } + case "nextInt", "nextLong", "nextBigInteger" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case "nextDouble", "nextFloat", "nextBigDecimal" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + } + case "hasNextInt", "hasNext", "hasNextLine" -> { + assert paramVars == null || paramVars.isEmpty() + || (paramVars.size() == 1 && paramVars.getFirst().getType().getTypeEnum() == Type.TypeEnum.STRING); + return Value.valueFactory(new Type(Type.TypeEnum.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; + } + case "nextByte" -> { + assert paramVars == null || paramVars.isEmpty(); + throw new JavaLanguageFeatureNotSupportedException("byte is not supported"); + } + default -> throw new UnsupportedOperationException(methodName + " is not supported in Scanner."); + } + } + + @Override + public Value accessField(@NotNull java.lang.String fieldName, @NotNull Type expectedType) { + switch (fieldName) { + default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported in Scanner."); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new Scanner(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..3131dc8115 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/String.java @@ -0,0 +1,92 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.IStringValue; +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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "format" -> { + assert !paramVars.isEmpty(); + return Value.getNewStringValue(); + } + case "join" -> { + assert paramVars.size() >= 2; + assert paramVars.stream().map(IStringValue.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(((IStringValue) paramVars.get(0)).getValue(), + paramVars.subList(1, paramVars.size()).stream().map(x -> ((StringValue) x).getValue()).toArray(java.lang.String[]::new)); + return Value.getNewStringValue(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 Value.getNewStringValue(); + } + case "valueOf" -> { + assert paramVars.size() == 1; + return Value.getNewStringValue(); + } + default -> throw new UnsupportedOperationException(methodName); + } + } + + @NotNull + @Override + public JavaObject copy() { + return new String(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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/StringBuilder.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/StringBuilder.java new file mode 100644 index 0000000000..d6d12e1ca1 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/StringBuilder.java @@ -0,0 +1,94 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.lang.StringBuilder class. + * @author ujiqk + * @version 1.0 + * @see Oracle Docs + */ +public class StringBuilder extends JavaObject implements ISpecialObject { + + private static final java.lang.String PATH = "java.lang"; + private static final java.lang.String NAME = "StringBuilder"; + + /** + * Creates a new representation of the java.lang.StringBuilder class. + */ + public StringBuilder() { + super(new Type(Type.TypeEnum.OBJECT)); + } + + /** + * @return The variable name java.lang.StringBuilder + */ + @NotNull + @Pure + public static VariableName getName() { + return new VariableName(PATH + "." + NAME); + } + + @Override + public IValue callMethod(@NotNull java.lang.String methodName, List paramVars, MethodDeclaration method, @NotNull Type expectedType) { + switch (methodName) { + case "append" -> { + assert !paramVars.isEmpty(); + return this; + } + case "length" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case "toString" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + case "deleteCharAt" -> { + assert paramVars.size() == 1; + return this; + } + case "delete", "setCharAt" -> { + assert paramVars.size() == 2; + return this; + } + case "substring" -> { + assert paramVars.size() == 1 || paramVars.size() == 2; + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @NotNull + @Override + public JavaObject copy() { + return new StringBuilder(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @Override + public void merge(@NotNull IValue other) { + assert other instanceof StringBuilder; + // 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..c96930296d --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/objects/System.java @@ -0,0 +1,105 @@ +package de.jplag.java_cpg.ai.variables.objects; + +import java.util.List; +import java.util.Map; + +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.JavaLanguageFeatureNotSupportedException; +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.arrays.IJavaArray; + +/** + * 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(new Type(Type.TypeEnum.OBJECT, getName().toString())); + } + + /** + * @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, @NotNull Type expectedType) { + switch (methodName) { + case "lineSeparator" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.getNewStringValue("\n"); + } + case "exit" -> throw new JavaLanguageFeatureNotSupportedException("System.exit() called"); + + case "currentTimeMillis" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case "arraycopy" -> { // void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) + IJavaArray srcArray = (IJavaArray) paramVars.getFirst(); + IJavaArray destArray = (IJavaArray) paramVars.get(2); + srcArray.setToUnknown(); + destArray.setToUnknown(); + return new VoidValue(); + } + case "getProperty" -> { + assert paramVars.size() == 1; + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + default -> throw new UnsupportedOperationException(methodName + " is not supported in " + PATH + "." + NAME); + } + } + + @Override + public Value accessField(@NotNull java.lang.String fieldName, @NotNull Type expectedType) { + 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(); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @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..595db444a8 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/BooleanValue.java @@ -0,0 +1,184 @@ +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(new Type(Type.TypeEnum.BOOLEAN)); + information = false; + } + + /** + * Creates a BooleanValue with exact information. + * @param value the boolean value + */ + public BooleanValue(boolean value) { + super(new Type(Type.TypeEnum.BOOLEAN)); + this.value = value; + information = true; + } + + private BooleanValue(boolean value, boolean information) { + super(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(); + } + } + 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 : "Cannot merge " + this.getType() + " with " + other.getType(); + 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..e6c7e8cfc8 --- /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(new Type(Type.TypeEnum.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..bb5f416f40 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IJavaObject.java @@ -0,0 +1,69 @@ +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.Type; +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. + * @param expectedType the expected return type of the method; used for type checking and to determine the return type + * of this method. + * @return the return value of the method. + */ + IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method, @NotNull Type expectedType); + + /** + * Accesses a field of this object. + * @param fieldName the name of the field to access. + * @param expectedType the expected type of the field; used for type checking and to determine the return type of this + * method. + * @return the value of the field. + */ + IValue accessField(@NotNull String fieldName, @NotNull Type expectedType); + + /** + * 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); + + /** + * @return true if this object has an abstract interpretation assigned. + */ + boolean hasAbstractInterpretation(); + + /** + * @return whether the object is known to be null. + */ + boolean isNull(); + +} 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..c7fc6edd7f --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/IValue.java @@ -0,0 +1,129 @@ +package de.jplag.java_cpg.ai.variables.values; + +import java.util.Map; +import java.util.Set; + +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(); + + /** + * Creates and returns a deep copy of this value. + * @param copiedObjects map of already copied objects to handle cyclic references. + * @return a deep copy of this value. + */ + default IValue copy(Map copiedObjects) { + return copy(); // default: ignore the map + } + + /** + * 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); + + /** + * 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. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + default void merge(@NotNull IValue other, Set visited) { + merge(other); // default: ignore visited set + } + + /** + * Delete all information in this value. + */ + void setToUnknown(); + + /** + * Delete all information in this value. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + default void setToUnknown(Set visited) { + setToUnknown(); // default: ignore visited set + } + + /** + * Resets all information about this value except its type. The initial value depends on the specific value type. + */ + void setInitialValue(); + + /** + * Resets all information about this value except its type. The initial value depends on the specific value type. + * @param visited set of already visited JavaObjects to handle cyclic references. + */ + default void setInitialValue(Set visited) { + setInitialValue(); // default: ignore visited set + } + + /** + * {@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..a0c7930816 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/JavaObject.java @@ -0,0 +1,269 @@ +package de.jplag.java_cpg.ai.variables.values; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +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; + +/** + * 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, @NotNull Type type) { + super(new Type(Type.TypeEnum.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. + * @param type the type of the object; used for type checking. + */ + public JavaObject(@NotNull AbstractInterpretation abstractInterpretation, @NotNull Type type) { + super(new Type(Type.TypeEnum.OBJECT)); + this.fields = new Scope(); + this.abstractInterpretation = abstractInterpretation; + abstractInterpretation.setRelatedObject(this); + } + + /** + * Internal constructor for special classes like arrays. + * @param type the type of the object. + */ + public JavaObject(@NotNull 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. + * @param expectedType the expected return type of the method; used for type checking and to determine the return type + * of this method. + * @return null if the method is not known. + */ + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method, @NotNull Type expectedType) { + if (abstractInterpretation == null || abstractInterpretation.isMethodAnalysisMode()) { + return Value.valueFactory(expectedType); + } + return abstractInterpretation.runMethod(methodName, paramVars, method, expectedType); + } + + /** + * @param fieldName the name of the field to access. + * @param expectedType the expected type of the field; used for type checking and to determine the return type of this + * method. + * @return the value of the field or VoidValue if the field does not exist. + */ + public IValue accessField(@NotNull String fieldName, @NotNull Type expectedType) { + if (fields == null) { + return Value.valueFactory(expectedType); + } + Variable result = fields.getVariable(new VariableName(fieldName)); + if (result == null) { + return Value.valueFactory(expectedType); + } + 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) { + if (fields == null) { + // reset information + fields = new Scope(); + return; + } + 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) { + assert this.fields != null; + 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; + if (abstractInterpretation != null) { + abstractInterpretation.setRelatedObject(this); + } + } + + @Override + public boolean hasAbstractInterpretation() { + return this.abstractInterpretation != null; + } + + @Override + public boolean isNull() { + return fields == null; + } + + @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, new Type(Type.TypeEnum.STRING)); + if (toStringResult instanceof IStringValue stringFromObject && stringValue.getInformation() + && stringFromObject.getInformation()) { + return Value.getNewStringValue(stringValue.getValue() + stringFromObject.getValue()); + } + return Value.getNewStringValue(); + } else { + throw new UnsupportedOperationException( + "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType()); + } + } + case "instanceof" -> { + return new BooleanValue(); + } + default -> { + return new VoidValue(); + } + } + } + + @NotNull + @Override + public JavaObject copy() { + return copy(new IdentityHashMap<>()); + } + + /** + * Deep copy of this object. + * @param copiedObjects map of already copied objects to handle cyclic references. + * @return the copied object. + */ + @Override + @NotNull + public JavaObject copy(Map copiedObjects) { + if (fields == null) { + return new JavaObject(null, this.abstractInterpretation, this.getType()); + } + if (copiedObjects.containsKey(this)) { + return copiedObjects.get(this); + } + JavaObject newObject = new JavaObject(new Scope(), this.abstractInterpretation, this.getType()); + copiedObjects.put(this, newObject); + newObject.fields = new Scope(this.fields, copiedObjects); + return newObject; + } + + @Override + public void merge(@NotNull IValue other) { + merge(other, Collections.newSetFromMap(new IdentityHashMap<>())); + } + + @Override + public void merge(@NotNull IValue other, Set visited) { + if (!(other instanceof JavaObject otherObj)) { + setToUnknown(); + return; + } + if (fields == null && otherObj.fields == null) { + return; + } + if (fields == null || otherObj.fields == null) { + fields = new Scope(); + return; + } + // Cycle detection + if (visited.contains(this)) { + return; + } + visited.add(this); + this.fields.merge(otherObj.fields, visited); + } + + @Override + public void setToUnknown() { + setToUnknown(Collections.newSetFromMap(new IdentityHashMap<>())); + } + + @Override + public void setToUnknown(Set visited) { + if (visited.contains(this)) { + return; + } + visited.add(this); + if (fields != null) { + fields.setEverythingUnknown(visited); + } else { + fields = new Scope(); + } + } + + @Override + public void setInitialValue() { + setInitialValue(Collections.newSetFromMap(new IdentityHashMap<>())); + } + + @Override + public void setInitialValue(Set visited) { + if (visited.contains(this)) { + return; + } + visited.add(this); + if (fields != null) { + fields.setEverythingInitialValue(visited); + } + 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..c89665ddc8 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/NullValue.java @@ -0,0 +1,58 @@ +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(new Type(Type.TypeEnum.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) { + if (!(other instanceof NullValue)) { + throw new IllegalStateException(); + } + assert other instanceof NullValue; // other cases are handled in Variable.merge() + } + + @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..f71db6c148 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/Value.java @@ -0,0 +1,471 @@ +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.JavaLanguageFeatureNotSupportedException; +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.IStringValue; +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.getTypeEnum()) { + case INT -> getNewIntValue(); + case STRING -> getNewStringValue(); + case BOOLEAN -> new BooleanValue(); + case OBJECT -> new JavaObject(type); + case VOID, UNKNOWN -> new VoidValue(); // ToDo: split VOID and UNKNOWN + case ARRAY, LIST -> getNewArayValue(type.getInnerType()); + case NULL -> { + JavaObject obj = new JavaObject(type); + obj.setInitialValue(); + yield obj; + } + 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. + * @throws JavaLanguageFeatureNotSupportedException if the value type is not supported by the AI configuration. + */ + @NotNull + public static IValue valueFactory(@Nullable Object value) { + if (value == null) { + IJavaObject obj = new JavaObject(new Type(Type.TypeEnum.OBJECT)); + obj.setInitialValue(); + return obj; + } + 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 + if (l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE) { + throw new JavaLanguageFeatureNotSupportedException("Long values are not supported"); + } + 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 + protected static INumberValue 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); + }; + } + + /** + * Creates a new string value based on the configured AI type. + * @return a new string value instance without information. + */ + @NotNull + public static IStringValue getNewStringValue() { + return switch (usedStringAiType) { + case DEFAULT -> new StringValue(); + case CHAR_INCLUSION -> new StringCharInclValue(); + case REGEX -> new StringRegexValue(); + }; + } + + /** + * Creates a new string value based on the configured AI type. + * @param value the string value. + * @return a new string value instance. + */ + @NotNull + public 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); + }; + } + + /** + * Creates a new array value based on the configured AI type. + * @return a new array value instance. + */ + @NotNull + public static IJavaArray getNewArayValue() { + return switch (usedArrayAiType) { + case DEFAULT -> new JavaArray(new Type(Type.TypeEnum.UNKNOWN)); + case LENGTH -> new JavaLengthArray(new Type(Type.TypeEnum.UNKNOWN)); + }; + } + + /** + * 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) { + if (innerType == null) { + innerType = new Type(Type.TypeEnum.UNKNOWN); + } + 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..03ef88fa11 --- /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.IFloatNumber; +import de.jplag.java_cpg.ai.variables.values.numbers.IIntNumber; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * 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(new Type(Type.TypeEnum.VOID)); + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + switch (operator) { + case "==", ">", "<", ">=", "<=", "!=", "instanceof" -> { + return new BooleanValue(); + } + case "+", "-", "*", "/", "%" -> { + return switch (other) { + case IIntNumber _ -> Value.valueFactory(new Type(Type.TypeEnum.INT)); + case IFloatNumber _ -> Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + case IStringValue _ -> Value.valueFactory(new Type(Type.TypeEnum.STRING)); + 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", "+", "-", "throw" -> { + 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..5e9a905908 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaArray.java @@ -0,0 +1,600 @@ +package de.jplag.java_cpg.ai.variables.values.arrays; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.JavaLanguageFeatureNotSupportedException; +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; + +/** + * 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 + // ToDo: add explicit information boolean + + /** + * a Java Array with no information and undefined size. + * @param innerType the type of the array elements. + */ + public JavaArray(Type innerType) { + super(new Type(Type.TypeEnum.ARRAY, innerType)); + this.innerType = innerType; + } + + /** + * a Java Array with exact information. + * @param values the values of the array in the correct order. + * @throws UnsupportedOperationException if the inner type is not supported. + */ + public JavaArray(@NotNull List values) { + super(new Type(Type.TypeEnum.ARRAY, new Type(Type.TypeEnum.UNKNOWN))); + if (values.isEmpty()) { + this.innerType = null; + this.values = values; + return; + } + this.innerType = new Type(Type.TypeEnum.VOID); + for (IValue value : values) { + if (this.innerType.getTypeEnum() != Type.TypeEnum.VOID) { + assert value.getType().equals(this.innerType) + || value.getType().getTypeEnum() == Type.TypeEnum.VOID : "Inconsistent types in array initialization: " + this.innerType + + " and " + value.getType(); + continue; + } + if (value.getType().getTypeEnum() != Type.TypeEnum.VOID) { + this.innerType = value.getType(); + } + } + List newValues = new ArrayList<>(); + for (IValue value : values) { // exchange void values with unknown values of the inner type + if (value.getType().getTypeEnum() == Type.TypeEnum.VOID) { + switch (this.innerType.getTypeEnum()) { + case INT -> newValues.add(Value.valueFactory(new Type(Type.TypeEnum.INT))); + case BOOLEAN -> newValues.add(new BooleanValue()); + case STRING -> newValues.add(Value.valueFactory(new Type(Type.TypeEnum.STRING))); + case OBJECT -> newValues.add(new JavaObject(innerType)); + case ARRAY, LIST -> newValues.add(new JavaArray(innerType.getInnerType())); + case FLOAT -> newValues.add(Value.valueFactory(new Type(Type.TypeEnum.FLOAT))); + case CHAR -> newValues.add(Value.valueFactory(new Type(Type.TypeEnum.CHAR))); + case VOID -> { + } + default -> throw new UnsupportedOperationException("Array of type " + this.innerType + " not supported"); + } + } else { + newValues.add(value); + } + + } + this.values = newValues; + } + + /** + * 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(new Type(Type.TypeEnum.ARRAY, innerType)); + 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(new Type(Type.TypeEnum.ARRAY, innerType)); + 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.getTypeEnum()) { + case INT -> Value.valueFactory(new Type(Type.TypeEnum.INT)); + case BOOLEAN -> new BooleanValue(); + case STRING -> Value.valueFactory(new Type(Type.TypeEnum.STRING)); + case OBJECT -> new JavaObject(innerType); + case ARRAY, LIST -> new JavaArray(innerType.getInnerType()); + case FLOAT -> Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + case CHAR -> Value.valueFactory(new Type(Type.TypeEnum.CHAR)); + case VOID, UNKNOWN -> new VoidValue(); + 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, @NotNull Type expectedType) { + switch (methodName) { + case "toString" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.getNewStringValue(); + } + 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", "toArray" -> { + if (paramVars != null && paramVars.size() == 1) { + return paramVars.getFirst(); + } + assert paramVars == null || paramVars.isEmpty() : "Method " + methodName + " does not take parameters but " + paramVars.size() + + " were given"; + return this; + } + case "size" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null) { + return Value.valueFactory(values.size()); + } + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case "map" -> { + // ToDo + return this; + } + case "max" -> { + // ToDo + if (innerType.getTypeEnum() == Type.TypeEnum.INT) { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } else if (innerType.getTypeEnum() == Type.TypeEnum.FLOAT) { + return Value.valueFactory(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.INT)); + } + case "remove" -> { + if (paramVars == null || paramVars.isEmpty()) { // remove head + return this.callMethod("removeFirst", null, method, expectedType); + } + 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(new Type(Type.TypeEnum.INT))); + } + assert paramVars.getFirst() instanceof INumberValue : "Method " + methodName + " requires a number as parameter but " + + paramVars.getFirst().getType() + " was given"; + 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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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 Value.valueFactory(innerType); + } + case "addLast", "push" -> { + assert paramVars.size() == 1; + if (values != null) { + assert paramVars.getFirst().getType().equals(innerType); + values.add(paramVars.getFirst()); + } + return Value.valueFactory(innerType); + } + case "removeFirst", "poll" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null && !values.isEmpty()) { + return values.removeFirst(); + } + // no information + values = null; + return Value.valueFactory(innerType); + } + case "isEmpty" -> { + assert paramVars == null || paramVars.isEmpty(); + if (values != null) { + return Value.valueFactory(values.isEmpty()); + } + return Value.valueFactory(new Type(Type.TypeEnum.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(); + } + this.values = null; // no information after sorting + 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 JavaLanguageFeatureNotSupportedException("Iterators are not supported"); + } + case "clone" -> { + assert paramVars == null || paramVars.isEmpty(); + return this.copy(); + } + case "filter", "collect" -> { + this.values = null; + return Value.valueFactory(new Type(Type.TypeEnum.LIST, this.innerType)); + } + case "findFirst" -> { + if (values != null) { + if (!values.isEmpty()) { + return values.getFirst(); + } + } + return new VoidValue(); + } + case "forEach" -> { + this.values = null; + this.innerType = new Type(Type.TypeEnum.VOID); + return Value.valueFactory(new Type(Type.TypeEnum.LIST, new Type(Type.TypeEnum.UNKNOWN))); + } + case "set" -> { + assert paramVars.size() == 2; + 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.set(idx, paramVars.getLast()); + } else { + values = null; // no information + } + } else { + values = null; // no information + } + } + return new VoidValue(); + } + case "offer" -> { + assert paramVars.size() == 1; + setToUnknown(); + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + case "containsAll" -> { + assert paramVars.size() == 1; + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + case "removeAll", "retainAll" -> { + assert paramVars.size() == 1; + setToUnknown(); + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + case "hasNext", "hasPrevious" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + case "next", "previous" -> { + assert paramVars == null || paramVars.isEmpty(); + if (innerType == null) { + return new VoidValue(); + } + return Value.valueFactory(innerType); + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @Override + public IValue accessField(@NotNull String fieldName, @NotNull Type expectedType) { + switch (fieldName) { + case "length" -> { + if (values != null) { + return Value.valueFactory(values.size()); + } + return Value.valueFactory(new Type(Type.TypeEnum.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); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue || other instanceof IJavaObject) { // cannot merge different types + other = new JavaArray(this.innerType); + } + assert other instanceof 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 setToUnknown(Set visited) { + setToUnknown(); + } + + @Override + public void setInitialValue(Set visited) { + setInitialValue(); + } + + @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..c4d47f67d2 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaLengthArray.java @@ -0,0 +1,239 @@ +package de.jplag.java_cpg.ai.variables.values.arrays; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +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.Value; +import de.jplag.java_cpg.ai.variables.values.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue; + +/** + * Represents a Java array by its length and inner type. + * @author ujiqk + */ +public class JavaLengthArray extends JavaObject implements IJavaArray { + + private Type innerType; + private INumberValue length; // array == null -> length == null + + /** + * 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(new Type(Type.TypeEnum.ARRAY, innerType)); + this.innerType = innerType; + this.length = Value.getNewIntValue(); + } + + /** + * 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(new Type(Type.TypeEnum.ARRAY, innerType)); + this.length = length; + this.innerType = Objects.requireNonNullElse(innerType, new Type(Type.TypeEnum.UNKNOWN)); + } + + /** + * 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(new Type(Type.TypeEnum.ARRAY, values.isEmpty() ? new Type(Type.TypeEnum.UNKNOWN) : values.getFirst().getType())); + if (values.isEmpty()) { + this.innerType = new Type(Type.TypeEnum.UNKNOWN); + } else { + 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.getTypeEnum() == Type.TypeEnum.UNKNOWN) { + return new VoidValue(); + } + return switch (innerType.getTypeEnum()) { + case INT -> Value.valueFactory(new Type(Type.TypeEnum.INT)); + case BOOLEAN -> new BooleanValue(); + case STRING -> Value.valueFactory(new Type(Type.TypeEnum.STRING)); + case OBJECT -> new JavaObject(innerType); + case ARRAY -> Value.valueFactory(new Type(Type.TypeEnum.ARRAY, innerType.getInnerType())); + case LIST -> Value.valueFactory(new Type(Type.TypeEnum.LIST, innerType.getInnerType())); + case FLOAT -> Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + case CHAR -> Value.valueFactory(new Type(Type.TypeEnum.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, @NotNull Type expectedType) { + switch (methodName) { + case "toString" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.getNewStringValue(); + } + 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(new Type(Type.TypeEnum.INT)); + } + case "remove" -> { + assert paramVars == null || paramVars.size() == 1 || paramVars.isEmpty(); + // information lost + length.setToUnknown(); + return new VoidValue(); + } + case "get", "elementAt" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.getNewIntValue()); + } + assert paramVars.getFirst() instanceof INumberValue : "Parameter for get/elementAt must be a number value but was " + + paramVars.getFirst().getClass(); + return arrayAccess((INumberValue) paramVars.getFirst()); + } + case "contains" -> { + assert paramVars.size() == 1; + return Value.valueFactory(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.BOOLEAN)); + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @Override + public IValue accessField(@NotNull String fieldName, @NotNull Type expectedType) { + switch (fieldName) { + case "length" -> { + return this.length; + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @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); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + other = new JavaLengthArray(this.innerType); + } + assert other instanceof JavaLengthArray : "Can only merge with another JavaLengthArray but got " + other.getClass(); + final JavaLengthArray otherArray = (JavaLengthArray) other; + if (this.innerType.getTypeEnum() == Type.TypeEnum.UNKNOWN) { + this.innerType = otherArray.innerType; + } + if (otherArray.innerType.getTypeEnum() != Type.TypeEnum.UNKNOWN) { + assert Objects.equals(this.innerType, otherArray.innerType) : "Cannot merge arrays of different inner types: " + this.innerType + " and " + + ((JavaLengthArray) other).innerType; + } + if (this.length == null || otherArray.length == null) { + this.length = null; + } else { + this.length.merge(otherArray.length); + } + } + + @Override + public void setToUnknown() { + length = Value.getNewIntValue(); + } + + @Override + public void setToUnknown(Set visited) { + setToUnknown(); + } + + @Override + public void setInitialValue(Set visited) { + setInitialValue(); + } + + @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..391f172256 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharSetValue.java @@ -0,0 +1,189 @@ +package de.jplag.java_cpg.ai.variables.values.chars; + +import java.util.HashSet; +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.VoidValue; +import de.jplag.java_cpg.ai.variables.values.numbers.IFloatNumber; +import de.jplag.java_cpg.ai.variables.values.numbers.IIntNumber; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * 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(new Type(Type.TypeEnum.CHAR)); + this.information = false; + } + + /** + * an exactly known char value. + * @param character the known character + */ + public CharSetValue(char character) { + super(new Type(Type.TypeEnum.CHAR)); + this.information = true; + this.maybeContained = new HashSet<>(); + this.maybeContained.add(character); + } + + /** + * a char value that can be one of the given characters. + * @param characters the possible characters + */ + public CharSetValue(@NotNull Set characters) { + super(new Type(Type.TypeEnum.CHAR)); + this.information = true; + this.maybeContained = characters; + } + + /** + * Copy constructor. + */ + private CharSetValue(Set maybeContained, boolean information) { + super(new Type(Type.TypeEnum.CHAR)); + this.maybeContained = maybeContained; + this.information = information; + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + switch (operator) { + case "!=" -> { + CharSetValue otherCharValue = (CharSetValue) other; + if (!this.information || !otherCharValue.information) { + return new BooleanValue(); + } + for (char c1 : this.maybeContained) { // true if disjoint + for (char c2 : otherCharValue.maybeContained) { + if (c1 == c2) { + return new BooleanValue(false); + } + } + } + return new BooleanValue(true); + } + case "==" -> { + CharSetValue otherCharValue = (CharSetValue) other; + if (!this.information || !otherCharValue.information) { + return new BooleanValue(); + } + if (this.maybeContained.size() == 1 && otherCharValue.maybeContained.size() == 1) { + if (this.maybeContained.iterator().next().equals(otherCharValue.maybeContained.iterator().next())) { + return new BooleanValue(true); + } + } + return new BooleanValue(false); + } + case "-" -> { + CharSetValue otherCharValue = (CharSetValue) other; + if (this.information && otherCharValue.information) { + Set maybeContainedCopy = Set.copyOf(this.maybeContained); + Set otherMaybeContained = otherCharValue.maybeContained; + Set result = new HashSet<>(); + for (char c1 : maybeContainedCopy) { + for (char c2 : otherMaybeContained) { + result.add((char) (c1 - c2)); + } + } + return new CharSetValue(result); + } else { + return new CharSetValue(); + } + } + case "+" -> { + if (other instanceof IIntNumber || other instanceof IFloatNumber) { + return new CharSetValue(); + } else if (other instanceof IStringValue) { + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + CharSetValue otherCharValue = (CharSetValue) other; + if (this.information && otherCharValue.information) { + Set maybeContainedCopy = Set.copyOf(this.maybeContained); + Set otherMaybeContained = otherCharValue.maybeContained; + Set result = new HashSet<>(); + for (char c1 : maybeContainedCopy) { + for (char c2 : otherMaybeContained) { + result.add((char) (c1 + c2)); + } + } + return new CharSetValue(result); + } else { + return new CharSetValue(); + } + } + default -> { + return new VoidValue(); + } + } + } + + @Override + public IValue unaryOperation(@NotNull String operator) { + switch (operator) { + default -> { + return new VoidValue(); + } + } + } + + @NotNull + @Override + public Value copy() { + return new CharSetValue(this.maybeContained, this.information); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + setToUnknown(); + return; + } + 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 = new HashSet<>(); + this.maybeContained.add(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..ea1278dc4c --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharValue.java @@ -0,0 +1,222 @@ +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.VoidValue; +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(new Type(Type.TypeEnum.CHAR)); + this.information = false; + } + + /** + * an exactly known char value. + * @param value the known character + */ + public CharValue(char value) { + super(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.CHAR)); + this.information = information; + this.value = value; + } + + @Override + public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) { + if (other instanceof VoidValue) { + other = new CharValue(); + } + 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(); + } + } else if (other instanceof INumberValue numberValue) { + if (this.information && numberValue.getInformation()) { + return Value.getNewIntValue((char) (this.value + (char) numberValue.getValue())); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + } + IStringValue otherStringValue = (IStringValue) other; + if (this.information && otherStringValue.getInformation()) { + return Value.valueFactory(this.value + otherStringValue.getValue()); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.STRING)); + } + } + case "-" -> { + INumberValue otherCharValue = (INumberValue) other; + if (this.information && otherCharValue.getInformation()) { + return new CharValue((char) (this.value - otherCharValue.getValue())); + } else { + return new CharValue(); + } + } + case "<" -> { + if (other instanceof INumberValue numberValue) { + if (this.information && numberValue.getInformation()) { + return new BooleanValue(this.value < numberValue.getValue()); + } + } + return new BooleanValue(); + } + case ">" -> { + if (other instanceof INumberValue numberValue) { + if (this.information && numberValue.getInformation()) { + return new BooleanValue(this.value > numberValue.getValue()); + } + } + return new BooleanValue(); + } + case ">=" -> { + if (other instanceof INumberValue numberValue) { + if (this.information && numberValue.getInformation()) { + return new BooleanValue(this.value >= numberValue.getValue()); + } + } + return new BooleanValue(); + } + case "<=" -> { + if (other instanceof INumberValue numberValue) { + if (this.information && numberValue.getInformation()) { + return new BooleanValue(this.value <= numberValue.getValue()); + } + } + return new BooleanValue(); + } + case "*" -> { + INumberValue otherCharValue = (INumberValue) other; + if (this.information && otherCharValue.getInformation()) { + return Value.valueFactory(this.value * otherCharValue.getValue()); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + } + 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; + } + } + } + case VoidValue _ -> 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..f81e789349 --- /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 implements IFloatNumber { + + /** + * Default constructor c Float value represented as a set of intervals with no information. + */ + public FloatSetValue() { + super(new Type(Type.TypeEnum.FLOAT)); + values.add(new DoubleInterval()); + } + + private FloatSetValue(TreeSet values) { + super(new Type(Type.TypeEnum.FLOAT), values); + } + + /** + * Constructor for FloatSetValue that is known to be a single number. + * @param number the single float number + */ + public FloatSetValue(double number) { + super(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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..bee170e933 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatValue.java @@ -0,0 +1,289 @@ +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; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * Represents a floating point value with optional exact information. + */ +public class FloatValue extends Value implements INumberValue, IFloatNumber { + + private double value; + private boolean information; // whether exact information is available + + /** + * a IntValue with no information. + */ + public FloatValue() { + super(new Type(Type.TypeEnum.FLOAT)); + information = false; + } + + /** + * Constructor for FloatValue with exact information. + * @param value the float value. + */ + public FloatValue(double value) { + super(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.FLOAT)); + if (values.size() == 1) { + this.value = values.iterator().next(); + this.information = true; + } else { + this.information = false; + } + } + + private FloatValue(double value, boolean information) { + super(new Type(Type.TypeEnum.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(); + } + if (other instanceof IStringValue) { + return other.binaryOperation(operator, this); + } + assert other instanceof INumberValue : "Expected a number value for binary operation, but got " + other.getType(); + 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(); + } + } + case "%" -> { + if (information && ((INumberValue) other).getInformation()) { + return new FloatValue(this.value % ((INumberValue) other).getValue()); + } else { + return new FloatValue(); + } + } + case "compareTo" -> { + if (information && ((INumberValue) other).getInformation()) { + double otherValue = ((INumberValue) other).getValue(); + if (this.value < otherValue) { + return new FloatValue(-1); + } else if (this.value > otherValue) { + return new FloatValue(1); + } else { + return new FloatValue(0); + } + } else { + return new FloatValue(); + } + } + 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(); + } + } + case "abs" -> { + if (information) { + return new FloatValue(Math.abs(this.value)); + } else { + return new FloatValue(); + } + } + case "ceil" -> { + if (information) { + return new FloatValue(Math.ceil(this.value)); + } else { + return new FloatValue(); + } + } + case "floor" -> { + if (information) { + return new FloatValue(Math.floor(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/IFloatNumber.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IFloatNumber.java new file mode 100644 index 0000000000..2b8efbe64c --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IFloatNumber.java @@ -0,0 +1,9 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +/** + * Marker interface for float values. + * @author ujiqk + * @version 1.0 + */ +public interface IFloatNumber extends INumberValue { +} diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IIntNumber.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IIntNumber.java new file mode 100644 index 0000000000..83251c9875 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IIntNumber.java @@ -0,0 +1,9 @@ +package de.jplag.java_cpg.ai.variables.values.numbers; + +/** + * Marker interface for integer values. + * @author ujiqk + * @version 1.0 + */ +public interface IIntNumber extends INumberValue { +} 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..4e8cfe7947 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntIntervalValue.java @@ -0,0 +1,190 @@ +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.chars.ICharValue; +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, IIntNumber { + + private final IntInterval interval; + + /** + * a IntIntervalValue with no information. + */ + public IntIntervalValue() { + super(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.INT)); + interval = new IntInterval(lowerBound, upperBound); + } + + /** + * Constructor for IntIntervalValue with exact information. + * @param number the integer value. + */ + public IntIntervalValue(int number) { + super(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.INT)); + java.util.List values = possibleValues.stream().toList(); + interval = new IntInterval(values.getFirst(), values.getLast()); + } + + private IntIntervalValue(IntInterval interval) { + super(new Type(Type.TypeEnum.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(); + } + if (other instanceof IFloatNumber || other instanceof ICharValue) { // This could be better + INumberValue number = (INumberValue) other; + if (number.getInformation() && number.getValue() == (int) number.getValue()) { + other = new IntIntervalValue((int) number.getValue()); + } else { + other = new IntIntervalValue(); + } + } + IntIntervalValue otherValue = (IntIntervalValue) other; + 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 -> { + return new VoidValue(); + } + } + } + + @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 -> { + return new VoidValue(); + } + } + } + + @NotNull + @Override + public IValue copy() { + return new IntIntervalValue(interval.copy()); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + other = new IntIntervalValue(); + } + if (other instanceof IFloatNumber floatNumber) { // can happen because some casts are not explicit in eog + if (floatNumber.getInformation()) { + other = new IntIntervalValue((int) floatNumber.getValue()); + } else { + other = new IntIntervalValue(); + } + } + assert other instanceof IntIntervalValue : "Cannot merge " + this.getClass() + " with " + other.getClass(); + 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..de00afce51 --- /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 implements IIntNumber { + + /** + * Integer value represented as a set of intervals with no information. + */ + public IntSetValue() { + super(new Type(Type.TypeEnum.INT)); + values.add(new IntInterval()); + } + + private IntSetValue(TreeSet values) { + super(new Type(Type.TypeEnum.INT), values); + } + + /** + * Constructor for IntSetValue that is known to be a single number. + * @param number the single integer number + */ + public IntSetValue(int number) { + super(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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..4bd1025123 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntValue.java @@ -0,0 +1,355 @@ +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; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * Represents an integer value with optional exact information. + * @author ujiqk + * @version 1.0 + */ +public class IntValue extends Value implements INumberValue, IIntNumber { + + private int value; + private boolean information; // whether exact information is available + + /** + * a IntValue with no information. + */ + public IntValue() { + super(new Type(Type.TypeEnum.INT)); + information = false; + } + + /** + * Constructor for IntValue with exact information. + * @param value the integer value. + */ + public IntValue(int value) { + super(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.INT)); + assert lowerBound <= upperBound; + if (lowerBound == upperBound) { + this.value = lowerBound; + this.information = true; + } else { + this.information = false; + } + } + + private IntValue(int value, boolean information) { + super(new Type(Type.TypeEnum.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 IStringValue) { + return other.binaryOperation(operator, this); + } + 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(); + } + } + case "pow" -> { + if (information && otherNumber.getInformation()) { + return new IntValue((int) Math.pow(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(); + } + } + 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 "+" -> { + if (information) { + 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(new Type(Type.TypeEnum.FLOAT)); + } + } + case "sqrt" -> { + if (information) { + return Value.valueFactory(Math.sqrt(this.value)); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + } + } + case "ceil", "floor" -> { + if (information) { + return Value.valueFactory((int) (double) this.value); + } else { + return new IntValue(); + } + } + 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", new Type(Type.TypeEnum.UNKNOWN)) 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..7860c0ce0a --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/NumberSetValue.java @@ -0,0 +1,317 @@ +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.VoidValue; +import de.jplag.java_cpg.ai.variables.values.chars.ICharValue; +import de.jplag.java_cpg.ai.variables.values.numbers.helpers.Interval; +import de.jplag.java_cpg.ai.variables.values.string.IStringValue; + +/** + * 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 VoidValue || other instanceof ICharValue) { + other = createInstance(new TreeSet<>()); + } else if (other instanceof IStringValue stringValue) { + return stringValue.binaryOperation(operator, this); + } + NumberSetValue otherValue = (NumberSetValue) other; + if (this.values.isEmpty() || otherValue.values.isEmpty()) { + return new VoidValue(); + } + 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().doubleValue() < otherValue.values.getFirst().getLowerBound().doubleValue()) { + return new BooleanValue(true); + } else if (values.getFirst().getLowerBound().doubleValue() >= otherValue.values.getLast().getUpperBound().doubleValue()) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + case ">" -> { + if (values.getFirst().getLowerBound().doubleValue() > otherValue.values.getLast().getUpperBound().doubleValue()) { + return new BooleanValue(true); + } else if (values.getLast().getUpperBound().doubleValue() <= otherValue.values.getFirst().getLowerBound().doubleValue()) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + case "<=" -> { + if (values.getLast().getUpperBound().doubleValue() <= otherValue.values.getFirst().getLowerBound().doubleValue()) { + return new BooleanValue(true); + } else if (values.getFirst().getLowerBound().doubleValue() > otherValue.values.getLast().getUpperBound().doubleValue()) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + case ">=" -> { + if (values.getFirst().getLowerBound().doubleValue() >= otherValue.values.getLast().getUpperBound().doubleValue()) { + return new BooleanValue(true); + } else if (values.getLast().getUpperBound().doubleValue() < otherValue.values.getFirst().getLowerBound().doubleValue()) { + 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 -> { + return new VoidValue(); + } + } + } + + @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 -> { + return new VoidValue(); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + this.setToUnknown(); + return; + } + if (other instanceof IFloatNumber floatNumber && !(this instanceof IFloatNumber)) { // can happen because some casts are not explicit in eog + if (floatNumber.getInformation()) { + int value = (int) floatNumber.getValue(); + TreeSet newValues = new TreeSet<>(); + newValues.add(createInterval((T) Integer.valueOf(value), (T) Integer.valueOf(value))); + other = createInstance(newValues); + } else { + other = createInstance(new TreeSet<>()); + } + } else if (other instanceof IIntNumber integerNumber && !(this instanceof IIntNumber)) { // can happen because some casts are not explicit in + // eog + if (integerNumber.getInformation()) { + double value = integerNumber.getValue(); + TreeSet newValues = new TreeSet<>(); + newValues.add(createInterval((T) Double.valueOf(value), (T) Double.valueOf(value))); + other = createInstance(newValues); + } else { + other = createInstance(new TreeSet<>()); + } + } + assert other.getClass().equals(this.getClass()) : "Cannot merge different value types" + this.getClass() + " and " + other.getClass(); + TreeSet otherValues = ((NumberSetValue) other).values; + this.values.addAll(otherValues); + mergeOverlappingIntervals(); + } + + @Override + public void setToUnknown() { + values = new TreeSet<>(); + values.add(createFullInterval()); + } + + 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().doubleValue() >= interval.getLowerBound().doubleValue()) { + T maxUpper = lastInterval.getUpperBound().doubleValue() > interval.getUpperBound().doubleValue() ? 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..a771801c81 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/DoubleInterval.java @@ -0,0 +1,269 @@ +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 : "Lower bound must be less than or equal to upper bound: " + lowerBound + " > " + upperBound; + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + /** + * Safely extracts the lower bound as a double value. Handles both Double and Integer intervals due to type erasure. + */ + @Pure + private static double getLowerBound(@NotNull final Interval interval) { + return ((Number) interval.lowerBound).doubleValue(); + } + + /** + * Safely extracts the upper bound as a double value. Handles both Double and Integer intervals due to type erasure. + */ + @Pure + private static double getUpperBound(@NotNull final Interval interval) { + return ((Number) interval.upperBound).doubleValue(); + } + + @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 + getLowerBound(other); + double hi = upperBound + getUpperBound(other); + return new DoubleInterval(lo, hi); + } + + @Pure + @Override + public DoubleInterval minus(@NotNull Interval other) { + double lo = lowerBound - getUpperBound(other); + double hi = upperBound - getLowerBound(other); + return new DoubleInterval(lo, hi); + } + + @Pure + @Override + public DoubleInterval times(@NotNull Interval other) { + if (lowerBound == 0 && upperBound == MAX_VALUE && getLowerBound(other) == 0 && getUpperBound(other) == MAX_VALUE) { + return new DoubleInterval(0, MAX_VALUE); + } + double p1 = lowerBound * getLowerBound(other); + double p2 = lowerBound * getUpperBound(other); + double p3 = upperBound * getLowerBound(other); + double p4 = upperBound * getUpperBound(other); + double lo = Math.min(Math.min(p1, p2), Math.min(p3, p4)); + double hi = Math.max(Math.max(p1, p2), Math.max(p3, p4)); + if (Double.isNaN(lo) || Double.isNaN(hi)) { + return new DoubleInterval(MIN_VALUE, MAX_VALUE); + } + return new DoubleInterval(lo, hi); + } + + @Pure + @Override + public DoubleInterval divided(@NotNull Interval other) { + if (getLowerBound(other) <= 0 && getUpperBound(other) >= 0) { + return new DoubleInterval(MIN_VALUE, MAX_VALUE); + } + double p1 = lowerBound / getLowerBound(other); + double p2 = lowerBound / getUpperBound(other); + double p3 = upperBound / getLowerBound(other); + double p4 = upperBound / getUpperBound(other); + double lo = Math.min(Math.min(p1, p2), Math.min(p3, p4)); + double hi = Math.max(Math.max(p1, p2), Math.max(p3, p4)); + if (Double.isNaN(lo) || Double.isNaN(hi)) { + return new DoubleInterval(MIN_VALUE, MAX_VALUE); + } + return new DoubleInterval(lo, hi); + } + + @Pure + @Override + public BooleanValue equal(@NotNull Interval other) { + if (lowerBound.equals(upperBound) && getLowerBound(other) == getUpperBound(other) && lowerBound == getLowerBound(other)) { + return new BooleanValue(true); + } else if (upperBound < getLowerBound(other) || lowerBound > getUpperBound(other)) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue notEqual(@NotNull Interval other) { + if (upperBound < getLowerBound(other) || lowerBound > getUpperBound(other)) { + return new BooleanValue(true); + } else if (lowerBound.equals(upperBound) && getLowerBound(other) == getUpperBound(other) && lowerBound == getLowerBound(other)) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue smaller(@NotNull Interval other) { + if (upperBound < getLowerBound(other)) { + return new BooleanValue(true); + } else if (lowerBound >= getUpperBound(other)) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue smallerEqual(@NotNull Interval other) { + if (upperBound <= getLowerBound(other)) { + return new BooleanValue(true); + } else if (lowerBound > getUpperBound(other)) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue bigger(@NotNull Interval other) { + if (lowerBound > getUpperBound(other)) { + return new BooleanValue(true); + } else if (upperBound <= getLowerBound(other)) { + return new BooleanValue(false); + } else { + return new BooleanValue(); + } + } + + @Pure + @Override + public BooleanValue biggerEqual(@NotNull Interval other) { + if (lowerBound >= getUpperBound(other)) { + return new BooleanValue(true); + } else if (upperBound < getLowerBound(other)) { + 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, getLowerBound(o)); + } else { + return Double.compare(this.upperBound, getUpperBound(o)); + } + } + +} 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..d7537f04f5 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/IntInterval.java @@ -0,0 +1,281 @@ +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 : lowerBound + " is not <= " + upperBound; + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + @Pure + private static int safeAbs(int x) { + if (x == Integer.MIN_VALUE) { + return Integer.MAX_VALUE; + } + return Math.abs(x); + } + + /** + * Safely extracts the lower bound as an int value. Handles both Double and Integer intervals due to type erasure. + */ + private static int getLowerBound(@NotNull final Interval interval) { + return ((Number) interval.lowerBound).intValue(); + } + + /** + * Safely extracts the upper bound as an int value. Handles both Double and Integer intervals due to type erasure. + */ + private static int getUpperBound(@NotNull final Interval interval) { + return ((Number) interval.upperBound).intValue(); + } + + @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) getLowerBound(other); + long hiSum = (long) upperBound + (long) getUpperBound(other); + 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.upperBound; + long hiSum = (long) upperBound - (long) other.lowerBound; + 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); + assert lo <= hi; + return new IntInterval(lo, hi); + } + + @Pure + @Override + public IntInterval times(@NotNull Interval other) { + long p1 = (long) lowerBound * getLowerBound(other); + long p2 = (long) lowerBound * getUpperBound(other); + long p3 = (long) upperBound * getLowerBound(other); + long p4 = (long) upperBound * getUpperBound(other); + 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 (getLowerBound(other) <= 0 && getUpperBound(other) >= 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) && getLowerBound(other) == getUpperBound(other) && lowerBound == getLowerBound(other)) { + return new BooleanValue(true); + } else if (upperBound < getLowerBound(other) || lowerBound > getUpperBound(other)) { + 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, getLowerBound(other)); + int largerUpperBound = Math.max(this.upperBound, getUpperBound(other)); + assert smallerLowerBound <= largerUpperBound; + this.lowerBound = smallerLowerBound; + this.upperBound = largerUpperBound; + } + + @Override + public int compareTo(@NotNull Interval o) { + if (!this.lowerBound.equals(getLowerBound(o))) { + return Integer.compare(this.lowerBound, getLowerBound(o)); + } else { + return Integer.compare(this.upperBound, getUpperBound(o)); + } + } + +} 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..fdbb71fccf --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringCharInclValue.java @@ -0,0 +1,313 @@ +package de.jplag.java_cpg.ai.variables.values.string; + +import java.util.BitSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +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.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; + +/** + * String representation using character inclusion sets. Uses BitSet for efficient character set representation and + * copying. + * @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 + private BitSet certainContained; + /** + * Characters that may be contained in the string. + */ + private BitSet maybeContained; + + /** + * A string value with no information. + */ + public StringCharInclValue() { + super(new Type(Type.TypeEnum.STRING)); + certainContained = new BitSet(); + maybeContained = allCharactersBitSet(); + } + + /** + * Constructor for StringCharInclValue with exact information. + * @param value the string value. + */ + public StringCharInclValue(@Nullable String value) { + super(new Type(Type.TypeEnum.STRING)); + maybeContained = new BitSet(); + if (value == null) { + certainContained = null; + return; + } + certainContained = new BitSet(); + for (char c : value.toCharArray()) { + certainContained.set(c); + } + } + + /** + * Constructor for StringCharInclValue with possible values. + * @param possibleValues the set of possible string values. + */ + public StringCharInclValue(@NotNull Set possibleValues) { + super(new Type(Type.TypeEnum.STRING)); + certainContained = new BitSet(); + maybeContained = new BitSet(); + for (String value : possibleValues) { + for (char c : value.toCharArray()) { + maybeContained.set(c); + } + } + } + + private StringCharInclValue(@Nullable BitSet certainContained, BitSet maybeContained) { + super(new Type(Type.TypeEnum.STRING)); + this.certainContained = certainContained; + this.maybeContained = maybeContained; + } + + @Override + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method, @NotNull Type expectedType) { + switch (methodName) { + case "length" -> { + assert paramVars == null || paramVars.isEmpty(); + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case "parseInt" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof IStringValue; + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + case "parseDouble" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof IStringValue; + return Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + } + case "startsWith" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof IStringValue; + return Value.valueFactory(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "toUpperCase" -> { + BitSet newCertain = new BitSet(); + if (this.certainContained != null) { + for (int c = certainContained.nextSetBit(0); c >= 0; c = certainContained.nextSetBit(c + 1)) { + newCertain.set(Character.toUpperCase((char) c)); + } + } + BitSet newMaybe = new BitSet(); + for (int c = maybeContained.nextSetBit(0); c >= 0; c = maybeContained.nextSetBit(c + 1)) { + newMaybe.set(Character.toUpperCase((char) c)); + } + return new StringCharInclValue(newCertain, newMaybe); + } + case "charAt" -> { + assert paramVars.size() == 1; + assert paramVars.getFirst() instanceof INumberValue : "charAt parameter must be a number value but was " + + paramVars.getFirst().getType(); + return Value.valueFactory(new Type(Type.TypeEnum.CHAR)); + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @Override + public Value accessField(@NotNull String fieldName, @NotNull Type expectedType) { + 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()) { + BitSet newCertain = certainContained == null ? null : (BitSet) certainContained.clone(); + assert newCertain != null; + newCertain.or(doubleToCharBitSet(inumbervalue.getValue())); + return new StringCharInclValue(newCertain, (BitSet) maybeContained.clone()); + } else { + return new StringCharInclValue(); + } + } else if (operator.equals("+") && other instanceof StringCharInclValue stringValue) { + assert !(this.certainContained == null || stringValue.certainContained == null); + BitSet newCertain = (BitSet) this.certainContained.clone(); + newCertain.or(stringValue.certainContained); + BitSet newMaybe = (BitSet) this.maybeContained.clone(); + newMaybe.or(stringValue.maybeContained); + return new StringCharInclValue(newCertain, newMaybe); + } + return new VoidValue(); + } + + @NotNull + @Override + public JavaObject copy() { + return new StringCharInclValue(certainContained == null ? null : (BitSet) certainContained.clone(), (BitSet) maybeContained.clone()); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + return copy(); + } + + @Override + public void merge(@NotNull IValue other, Set visited) { + merge(other); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue) { + this.certainContained = new BitSet(); + this.maybeContained = allCharactersBitSet(); + return; + } + assert other instanceof StringCharInclValue : "Cannot merge " + getType() + " with " + other.getType(); + StringCharInclValue otherString = (StringCharInclValue) other; + if (this.certainContained == null || otherString.certainContained == null) { + this.certainContained = null; + } else { + // Characters that were certain in this but not in both -> move to maybe + BitSet removed = (BitSet) this.certainContained.clone(); + this.certainContained.and(otherString.certainContained); // Keep only common certain + removed.andNot(this.certainContained); // Characters removed from certain + this.maybeContained.or(removed); + // Characters certain in other but not in this -> add to maybe + BitSet otherCertainNotInThis = (BitSet) otherString.certainContained.clone(); + otherCertainNotInThis.andNot(this.certainContained); + this.maybeContained.or(otherCertainNotInThis); + this.maybeContained.or(otherString.maybeContained); + } + } + + @Override + public void setToUnknown() { + this.certainContained = new BitSet(); + this.maybeContained = allCharactersBitSet(); + } + + @Override + public void setToUnknown(Set visited) { + setToUnknown(); + } + + @Override + public void setInitialValue(Set visited) { + setInitialValue(); + } + + @Override + public void setInitialValue() { + this.certainContained = null; + this.maybeContained = new BitSet(); + } + + @NotNull + private BitSet allCharactersBitSet() { + BitSet allChars = new BitSet(Character.MAX_VALUE + 1); + allChars.set(0, Character.MAX_VALUE + 1); + return allChars; + } + + @NotNull + private BitSet doubleToCharBitSet(double value) { + BitSet charBitSet = new BitSet(); + String str = Double.toString(value); + for (char c : str.toCharArray()) { + charBitSet.set(c); + } + return charBitSet; + } + + /** + * @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() { + if (this.certainContained == null) { + return null; + } + return bitSetToCharSet(this.certainContained); + } + + /** + * Only for testing purposes. + * @return set of maybe contained characters. + */ + @TestOnly + public Set getMaybeContained() { + return bitSetToCharSet(this.maybeContained); + } + + @NotNull + @TestOnly + private Set bitSetToCharSet(@NotNull BitSet bitSet) { + Set charSet = new HashSet<>(); + for (int c = bitSet.nextSetBit(0); c >= 0; c = bitSet.nextSetBit(c + 1)) { + charSet.add((char) c); + } + return charSet; + } + + @Override + public boolean isNull() { + return certainContained == null; + } + +} 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..b5c1cce15f --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringRegexValue.java @@ -0,0 +1,528 @@ +package de.jplag.java_cpg.ai.variables.values.string; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +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(new Type(Type.TypeEnum.STRING)); + unknown = true; + } + + /** + * A string value with exact information. + * @param value The exact string value, null for null. + */ + public StringRegexValue(@Nullable String value) { + super(new Type(Type.TypeEnum.STRING)); + if (value == null) { + contentRegex = null; + unknown = false; + 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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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, @NotNull Type expectedType) { + switch (methodName) { + case "length" -> { + assert paramVars == null || paramVars.isEmpty(); + if (unknown || contentRegex == null) { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + if ((contentRegex.getLast() instanceof RegexChars chars && chars.canBeEmpty())) { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); // ToDo could return int interval + } + return Value.valueFactory(contentRegex.size()); + } + case "parseInt" -> { + assert paramVars.size() == 1; + if (unknown || contentRegex == null) { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + List possibleChars = new ArrayList<>(); + for (RegexItem item : contentRegex) { + if (item instanceof RegexChars) { + return Value.valueFactory(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.BOOLEAN)); + } + List possibleChars = new ArrayList<>(); + for (RegexItem item : contentRegex) { + if (item instanceof RegexChars) { + return Value.valueFactory(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "parseDouble" -> { + assert paramVars.size() == 1; + if (unknown || contentRegex == null) { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + List possibleChars = new ArrayList<>(); + for (RegexItem item : contentRegex) { + if (item instanceof RegexChars) { + return Value.valueFactory(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.BOOLEAN)); + } else { + return Value.valueFactory(true); + } + } + case "equals" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(new Type(Type.TypeEnum.STRING))); + } + StringRegexValue other = (StringRegexValue) paramVars.getFirst(); + if (this.unknown || other.unknown) { + return Value.valueFactory(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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 -> { + return Value.valueFactory(expectedType); + } + } + } + + @Override + public Value accessField(@NotNull String fieldName, @NotNull Type expectedType) { + 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) { + if (this.unknown || !inumbervalue.getInformation()) { + this.unknown = true; + return new StringRegexValue(null, true); + } + assert this.contentRegex != null; + 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.unknown || stringValue.unknown) { + this.unknown = true; + return new StringRegexValue(null, true); + } + if (this.contentRegex == null && stringValue.contentRegex == null) { + return new StringRegexValue(null, false); + } else if (this.contentRegex == null || stringValue.contentRegex == null) { + 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(new Type(Type.TypeEnum.BOOLEAN)); + } + } + return new VoidValue(); + } + + @NotNull + @Override + public JavaObject copy() { + return new StringRegexValue(contentRegex == null ? null : new ArrayList<>(contentRegex), unknown); + } + + @NotNull + @Override + public JavaObject copy(Map copiedObjects) { + // StringValue doesn't have fields that could create cycles, + // so we can just return a simple copy + return copy(); + } + + @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 setToUnknown(Set visited) { + setToUnknown(); + } + + @Override + public void setInitialValue(Set visited) { + setInitialValue(); + } + + @Override + public void setInitialValue() { + this.contentRegex = null; + unknown = false; + } + + @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..333c0a3c50 --- /dev/null +++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringValue.java @@ -0,0 +1,492 @@ +package de.jplag.java_cpg.ai.variables.values.string; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; + +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; +import de.jplag.java_cpg.ai.JavaLanguageFeatureNotSupportedException; +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.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(new Type(Type.TypeEnum.STRING)); + information = false; + } + + /** + * A string value with exact information. + * @param value the string value + */ + public StringValue(String value) { + super(new Type(Type.TypeEnum.STRING)); + this.value = value; + information = true; + } + + private StringValue(String value, boolean information) { + super(new Type(Type.TypeEnum.STRING)); + this.value = value; + this.information = information; + } + + @Override + public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method, @NotNull Type expectedType) { + switch (methodName) { + case "length" -> { + assert paramVars == null || paramVars.isEmpty(); + if (information) { + return Value.valueFactory(value.length()); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + } + case "parseInt" -> { + assert paramVars.size() == 1; + StringValue str = (StringValue) paramVars.getFirst(); + if (str.getInformation()) { + return Value.valueFactory(Integer.parseInt(Objects.requireNonNull(str.getValue()))); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + } + case "parseDouble" -> { + assert paramVars.size() == 1; + StringValue str = (StringValue) paramVars.getFirst(); + if (str.getInformation()) { + return Value.valueFactory(Double.parseDouble(Objects.requireNonNull(str.getValue()))); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.FLOAT)); + } + } + case "startsWith" -> { + assert paramVars.size() == 1; + StringValue prefix = (StringValue) paramVars.getFirst(); + if (information && prefix.getInformation()) { + return Value.valueFactory(value.startsWith(Objects.requireNonNull(prefix.getValue()))); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "equals" -> { + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(new Type(Type.TypeEnum.STRING))); + } + StringValue other = (StringValue) paramVars.getFirst(); + if (information && other.getInformation()) { + return Value.valueFactory(value.equals(other.getValue())); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "split" -> { // public String[] split(String regex) + assert paramVars.size() == 1 || paramVars.size() == 2; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(new Type(Type.TypeEnum.STRING))); + } + StringValue regexValue = (StringValue) paramVars.getFirst(); + if (!information || !regexValue.getInformation()) { + return Value.getNewArayValue(new Type(Type.TypeEnum.STRING)); + } + assert regexValue.getValue() != null && value != null; + String[] parts; + if (paramVars.size() == 1) { + parts = value.split(regexValue.getValue()); + } else { + INumberValue limitValue = (INumberValue) paramVars.get(1); + if (!limitValue.getInformation()) { + return Value.getNewArayValue(new Type(Type.TypeEnum.STRING)); + } + parts = value.split(regexValue.getValue(), (int) limitValue.getValue()); + } + IJavaArray array = Value.getNewArayValue(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.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 Value.getNewArayValue(new Type(Type.TypeEnum.CHAR)); + } + assert value != null; + char[] chars = value.toCharArray(); + IJavaArray array = Value.getNewArayValue(new Type(Type.TypeEnum.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; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(new Type(Type.TypeEnum.STRING))); + } + 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) || public String substring(int beginIndex) + assert paramVars.size() == 2 || paramVars.size() == 1; + INumberValue beginIndexValue = (INumberValue) paramVars.get(0); + INumberValue endIndexValue; + if (paramVars.size() == 1) { + if (value == null) { + endIndexValue = Value.getNewIntValue(); + } else { + endIndexValue = Value.getNewIntValue(value.length()); + } + } else { + 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) { + assert false; + 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(); + } + } + case "matches" -> { // public boolean matches(String regex) + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(new Type(Type.TypeEnum.STRING))); + } + StringValue regexValue = (StringValue) paramVars.getFirst(); + if (information && regexValue.getInformation()) { + return Value.valueFactory(this.value.matches(Objects.requireNonNull(regexValue.getValue()))); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "repeat" -> { // public String repeat(int count) + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(new Type(Type.TypeEnum.STRING))); + } + INumberValue countValue = (INumberValue) paramVars.getFirst(); + if (information && countValue.getInformation()) { + int count = (int) countValue.getValue(); + assert count >= 0; + return new StringValue(this.value.repeat(count)); + } else { + return new StringValue(); + } + } + case "toUpperCase" -> { // public String toUpperCase() + assert paramVars == null || paramVars.isEmpty(); + if (information) { + return new StringValue(this.value.toUpperCase()); + } else { + return new StringValue(); + } + } + case "stripTrailing" -> { // public String stripTrailing() + assert paramVars == null || paramVars.isEmpty(); + if (information) { + return new StringValue(this.value.stripTrailing()); + } else { + return new StringValue(); + } + } + case "equalsIgnoreCase" -> { // public boolean equalsIgnoreCase(String anotherString) + assert paramVars.size() == 1; + StringValue other = (StringValue) paramVars.getFirst(); + if (information && other.getInformation()) { + return Value.valueFactory(this.value.equalsIgnoreCase(other.getValue())); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "replace" -> { // public String replace(CharSequence target, CharSequence replacement) + assert paramVars.size() == 2; + StringValue target = (StringValue) paramVars.getFirst(); + StringValue replacement = (StringValue) paramVars.getLast(); + if (information && target.getInformation() && replacement.getInformation()) { + return new StringValue( + this.value.replace(Objects.requireNonNull(target.getValue()), Objects.requireNonNull(replacement.getValue()))); + } else { + return new StringValue(); + } + } + case "format", "formatted", "replaceAll" -> { + assert paramVars.size() >= 1; + return new StringValue(); + } + case "contains" -> { // public boolean contains(CharSequence s) + assert paramVars.size() == 1; + if (paramVars.getFirst() instanceof VoidValue) { + paramVars.set(0, Value.valueFactory(new Type(Type.TypeEnum.STRING))); + } + StringValue s = (StringValue) paramVars.getFirst(); + if (information && s.getInformation()) { + return Value.valueFactory(this.value.contains(Objects.requireNonNull(s.getValue()))); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "isBlank" -> { // public boolean isBlank() + assert paramVars == null || paramVars.isEmpty(); + if (information) { + return Value.valueFactory(this.value.isBlank()); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "isEmpty" -> { // public boolean isEmpty() + assert paramVars == null || paramVars.isEmpty(); + if (information) { + return Value.valueFactory(this.value.isEmpty()); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "indexOf" -> { // public int indexOf(String str) || public int indexOf(String str, int fromIndex) + assert paramVars.size() == 1 || paramVars.size() == 2; + StringValue str = (StringValue) paramVars.getFirst(); + INumberValue fromIndexValue; + if (paramVars.size() == 1) { + fromIndexValue = Value.getNewIntValue(0); + } else { + fromIndexValue = (INumberValue) paramVars.get(1); + } + if (information && str.getInformation() && fromIndexValue.getInformation()) { + int fromIndex = (int) fromIndexValue.getValue(); + if (fromIndex < 0 || value == null || fromIndex > value.length()) { + return new VoidValue(); + } + return Value.valueFactory(this.value.indexOf(Objects.requireNonNull(str.getValue()), fromIndex)); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.INT)); + } + } + case "endsWith" -> { // public boolean endsWith(String suffix) + assert paramVars.size() == 1; + StringValue suffix = (StringValue) paramVars.getFirst(); + if (information && suffix.getInformation()) { + return Value.valueFactory(this.value.endsWith(Objects.requireNonNull(suffix.getValue()))); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "valueOf" -> { // public static String valueOf(Object obj) + assert paramVars.size() == 1; + return new StringValue(); + } + case "chars" -> { // public IntStream chars() + assert paramVars == null || paramVars.isEmpty(); + throw new JavaLanguageFeatureNotSupportedException("IntStream not supported"); + } + case "contentsEquals" -> { // public boolean contentsEquals(CharSequence cs) + assert paramVars.size() == 1; + StringValue cs = (StringValue) paramVars.getFirst(); + if (information && cs.getInformation()) { + return Value.valueFactory(this.value.contentEquals(Objects.requireNonNull(cs.getValue()))); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.BOOLEAN)); + } + } + case "toLowerCase" -> { // public String toLowerCase() + assert paramVars == null || paramVars.isEmpty(); + if (information) { + return new StringValue(this.value.toLowerCase()); + } else { + return new StringValue(); + } + } + default -> { + return Value.valueFactory(expectedType); + } + } + } + + @Override + public Value accessField(@NotNull String fieldName, @NotNull Type expectedType) { + throw new UnsupportedOperationException("Access field not supported in StringValue (" + fieldName + ")"); + } + + @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(new Type(Type.TypeEnum.BOOLEAN)); + } + } else if (operator.equals("!=") && other instanceof NullValue) { + if (information) { + return Value.valueFactory(this.value != null); + } else { + return Value.valueFactory(new Type(Type.TypeEnum.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(new Type(Type.TypeEnum.BOOLEAN)); + } + } else if (operator.equals("+") && other instanceof IJavaObject javaObject) { + // case: JavaObject with toString method + IValue toStringResult = javaObject.callMethod("toString", List.of(), null, new Type(Type.TypeEnum.STRING)); + if (toStringResult instanceof IStringValue otherStringFromObject && information && otherStringFromObject.getInformation()) { + return new StringValue(this.value + otherStringFromObject.getValue()); + } + return new StringValue(); + } + return new VoidValue(); + } + + @NotNull + @Override + public JavaObject copy() { + return new StringValue(value, information); + } + + @NotNull + @Override + public StringValue copy(Map copiedObjects) { + // StringValue doesn't have fields that could create cycles, + // so we can just return a simple copy + return new StringValue(value, information); + } + + @Override + public void merge(@NotNull IValue other) { + if (other instanceof VoidValue || other instanceof NullValue) { + this.information = false; + this.value = null; + return; + } + if (other instanceof IJavaObject javaObject && javaObject.isNull()) { + other = new StringValue(null); + } + assert other instanceof StringValue : "Cannot merge " + this.getClass() + " with " + other.getClass(); + 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 setToUnknown(Set visited) { + setToUnknown(); + } + + @Override + public void setInitialValue(Set visited) { + setInitialValue(); + } + + @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 index cd4e203cf5..3063197911 100644 --- 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 @@ -21,6 +21,7 @@ abstract class ATransformationPass(ctx: TranslationContext) : TranslationResultP val logger: Logger = LoggerFactory.getLogger(this::class.java) override fun accept(t: TranslationResult) { + val detector = CpgIsomorphismDetector() val transformations = getPhaseSpecificTransformations() for (transformation: GraphTransformation in transformations) { @@ -81,4 +82,4 @@ abstract class ATransformationPass(ctx: TranslationContext) : TranslationResultP } } -} \ No newline at end of file +} 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 index 458e11ee70..198135d3a1 100644 --- 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 @@ -2,14 +2,12 @@ package de.jplag.java_cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext 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 CPG. */ @DependsOn(FixAstPass::class) -@ExecuteBefore(TokenizationPass::class) class CpgTransformationPass(ctx: TranslationContext) : ATransformationPass(ctx) { override fun getPhaseSpecificTransformations(): List { 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 index 172c2cdc6a..823f0cfc9d 100644 --- 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 @@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory import java.util.* /** - * This pass sorts independent statements, removes statement that can be (conservatively) determined as useless, and builds + * This pass sorts independent statements, removes statements that can be (conservatively) determined as useless, and builds * the DFG. The original DFG of the CPG library is reset. */ @DependsOn(EvaluationOrderGraphPass::class) @@ -97,7 +97,6 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { // 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 @@ -118,7 +117,6 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { } reorderStatements(essentialNodesOut, relevantStatements, parentInfo, root.body as Block) - } private fun extractTransitiveDependencies( @@ -297,6 +295,7 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { statements: List, parentInfo: MutableMap, ) { + if (!removeDeadCode) return val irrelevantStatements = statements.filter { it !in relevantStatements } .filter { parentInfo[it]?.parent is Block } .distinct() @@ -395,7 +394,6 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { .map { it as Statement } .toList() worklist.addAll(newElements) - } assert(done.size == relevantStatementsInThisBlock.size && done.containsAll(relevantStatementsInThisBlock)) @@ -485,12 +483,10 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { else -> false } - } private fun isLocal(node: Node, reference: Node): Boolean { return node.location != null && node.location!!.artifactLocation == reference.location?.artifactLocation - } private fun extractEssentialNodes( @@ -722,7 +718,6 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { return if (tokens1.hasNext()) 1 else if (tokens2.hasNext()) -1 else 0 - } @@ -746,7 +741,7 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { statement.statements.flatMap { getMovableStatements(it, statement, parentInfo, depth + 1) } .toMutableList() if (parent is Block) { - // inner block without control statement that would enforce it + // inner block without a control statement that would enforce it result.add(statement) } result @@ -823,10 +818,8 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { listOf(statement) } - // only here to create entry in parentInfo else -> listOf() - } if (parent != null) { parentInfo[statement] = ParentInfo(parent, depth) @@ -852,7 +845,6 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { // 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 @@ -904,7 +896,6 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { state.registerAssignment(assignment, node) } - override fun cleanup() { } @@ -916,7 +907,6 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { constructor(copyMap: Map) : super(copyMap) - override fun get(key: Declaration): VariableData { return super.computeIfAbsent(key) { VariableData() } } @@ -1017,6 +1007,10 @@ class DfgSortPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { override fun hashCode(): Int { return javaClass.hashCode() } + } + companion object DfgSortPassCompanion { + var removeDeadCode: Boolean = true } + } 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 index 1f39b5a2ce..36c8dc745e 100644 --- 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 @@ -39,6 +39,8 @@ class TokenizationPass(ctx: TranslationContext) : TranslationResultPass(ctx) { private val strategy = NodeOrderStrategy() override fun accept(translationResult: TranslationResult) { + print("Current File: " + translationResult.translationUnits.first().name) + logger.info("Current File: " + translationResult.translationUnits.first().name.toString()) tokenList.clear() val listener = CpgNodeListener(consumer) val walker: SubgraphWalker.IterativeGraphWalker = SubgraphWalker.IterativeGraphWalker() @@ -79,4 +81,3 @@ class TokenizationPass(ctx: TranslationContext) : TranslationResultPass(ctx) { val logger: Logger = LoggerFactory.getLogger(TokenizationPass::class.java) } } - 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 index efd18c5c31..b98ada4ac7 100644 --- 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 @@ -553,6 +553,11 @@ public CpgMultiEdge edge() { return edge; } + @Override + public int hashCode() { + return Objects.hash(edge, cClass, modifications); + } + @Override public boolean equals(Object obj) { if (obj == this) @@ -564,11 +569,6 @@ public boolean equals(Object obj) { && Objects.equals(this.modifications, that.modifications); } - @Override - public int hashCode() { - return Objects.hash(edge, cClass, modifications); - } - @Override public String toString() { return "AddConsecutive[" + "edge=" + edge + ", " + "cClass=" + cClass + ", " + "modifications=" + modifications + ']'; 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 index ac31068b36..4aca91ab67 100644 --- 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 @@ -207,26 +207,6 @@ public NodePattern deepCopy() { return copy; } - @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 List> getCandidateClasses() { return List.of(getRootClass()); @@ -277,32 +257,6 @@ public void handleRelationships(NodePattern target, RelationComparisonFunctio } } - @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; - } - - /** - * 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; - } - @Override public void markStopRecursion() { annotations.add(NodeAnnotation.STOP_RECURSION); @@ -348,11 +302,57 @@ 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.getName()); } + /** + * 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, 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 index 1b100d2c79..26c0945796 100644 --- 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 @@ -249,7 +249,6 @@ private List traverseCallGraph(MethodDeclaration mainMethod) newFunctions.clear(); } return callGraphIndex; - } } diff --git a/languages/java-cpg/src/main/resources/simplelogger.properties b/languages/java-cpg/src/main/resources/simplelogger.properties new file mode 100644 index 0000000000..246ee6e36e --- /dev/null +++ b/languages/java-cpg/src/main/resources/simplelogger.properties @@ -0,0 +1,6 @@ +# SLF4J Simple Logger configuration +# See https://www.slf4j.org/api/org/slf4j/simple/SimpleLogger.html +# Default log level for all loggers (trace, debug, info, warn, error, off) +org.slf4j.simpleLogger.defaultLogLevel=warn +# Disable CPG library logging (on/warn/off) +org.slf4j.simpleLogger.log.de.fraunhofer.aisec.cpg=warn 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 index be6f7ef872..180f4d7089 100644 --- 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 @@ -21,7 +21,10 @@ */ public abstract class AbstractJavaCpgLanguageTest { - protected static final Path BASE_PATH = Path.of("src", "test", "resources", "java"); + /** + * The base path for the test resources. The test files are located in {@code src/test/resources/java}. + */ + public 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; @@ -52,4 +55,4 @@ protected List parseJavaFile(String fileName, boolean transform) thro 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..69475b74ea --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/CombinedPassTest.java @@ -0,0 +1,99 @@ +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 + * @throws ParsingException if parsing fails. + */ + @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/MatchingTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/MatchingTest.java index 18e710250f..24df42af7d 100644 --- 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 @@ -13,6 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import de.fraunhofer.aisec.cpg.ConfigurationException; import de.fraunhofer.aisec.cpg.TranslationResult; import de.fraunhofer.aisec.cpg.graph.Node; import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration; @@ -37,26 +38,24 @@ private static Stream providePairs() { @ParameterizedTest @MethodSource("providePairs") - void testMatch(String filename, Class rootType, SimpleGraphPattern pattern) throws InterruptedException { + void testMatch(String filename, Class rootType, SimpleGraphPattern pattern) + throws InterruptedException, ParsingException, ConfigurationException { File file = new File(baseDirectory, filename); - try { - CpgAdapter cpgAdapter = new CpgAdapter(); - 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); - } + + CpgAdapter cpgAdapter = new CpgAdapter(false, false, true, 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; + })); } } 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..73a19698aa --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/AbstractInterpretationTest.java @@ -0,0 +1,295 @@ +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.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.Type; +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.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 kotlin.jvm.JvmClassMappingKt; +import kotlin.reflect.KClass; + +/** + * Test that uses the CPG library and the existing java-cpg code. + * @author ujiqk + * @version 1.0 + */ +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", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(100, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); + assertFalse(((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); + assertEquals(2, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); + } + + /** + * simple switch test + */ + @Test + @Disabled("test contains a switch statement, which is currently not supported") + void testSwitch() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/switch"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertFalse(((IntValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getInformation()); // z + assertEquals(100, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); // z + assertEquals(100, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); + assertFalse(((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); // z + assertEquals(100, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); // 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, ((IntValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); // y + } + + /** + * simple enum test + */ + @Test + void testEnum() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/enum"); + JavaObject main = getMainObject(interpretation); + assertEquals(400, ((IntValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getValue()); + assertFalse(((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getInformation()); + } + + /** + * simple hashmap test + */ + @Test + void testHashMap() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/map"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + @Test + @Disabled("error in the dfg sort pass") + 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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(101, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(200, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(200, ((IntValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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..fa6bed1e90 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionIntervalTest.java @@ -0,0 +1,196 @@ +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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import de.jplag.ParsingException; +import de.jplag.java_cpg.ai.variables.Type; +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", new Type(Type.TypeEnum.INT))).getValue()); + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); + assertEquals(2, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); // y + } + + /** + * Test the programming course final project: QueensFarming. + */ + @Test + @Disabled("Disabled due to containing break statements not yet supported") + 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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getInformation()); + assertFalse(((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getInformation()); + assertFalse(((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(110, ((IntSetValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound()); + assertEquals(210, ((IntSetValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound()); + assertEquals(161, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); + assertFalse(((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(150, ((IntSetValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound()); + assertEquals(450, ((IntSetValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound()); + assertFalse(((INumberValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(501, ((IntSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound()); + assertEquals(2501, ((IntSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(0, ((IntSetValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound()); + assertEquals(Integer.MAX_VALUE, + ((IntSetValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound()); + assertFalse(((INumberValue) main.accessField("result2, new Type(Type.TypeEnum.INT)", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(2, ((IntSetValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getIntervals().size()); // y + assertEquals(10, ((IntSetValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound()); + assertEquals(50, ((IntSetValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound()); + assertEquals(200, ((IntSetValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getIntervals().getLast().getLowerBound()); + assertEquals(300, ((IntSetValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getIntervals().getLast().getUpperBound()); + assertFalse(((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(11, ((IntSetValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound()); + assertEquals(Integer.MAX_VALUE, + ((IntSetValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound()); + assertFalse(((INumberValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(2, ((IntSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getIntervals().size()); + assertEquals(20, ((IntSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound()); + assertEquals(100, ((IntSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound()); + assertEquals(400, ((IntSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getIntervals().getLast().getLowerBound()); + assertEquals(600, ((IntSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(60.5f, ((FloatSetValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound(), + 0.0001); + assertEquals(110.5, ((FloatSetValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound(), + 0.0001); + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(20.65f, ((FloatSetValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound(), + 0.0001); + assertEquals(20.9f, ((FloatSetValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound(), + 0.0001); + assertFalse(((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(37.5f, ((FloatSetValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound(), + 0.0001); + assertEquals(112.5f, ((FloatSetValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getUpperBound(), + 0.0001); + assertFalse(((INumberValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(10.3f, ((FloatSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).getIntervals().getFirst().getLowerBound(), + 0.0001); + assertEquals(1010.3f, ((FloatSetValue) main.accessField("result4", new Type(Type.TypeEnum.INT))).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..9bc411aaaf --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionStringTest.java @@ -0,0 +1,83 @@ +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.Type; +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); + final AbstractInterpretation interpretation = interpretFromResource("java/ai/string"); + JavaObject main = getMainObject(interpretation); + assertEquals(new HashSet<>(Set.of(' ', '!', 'D', 'e', 'H', 'h', 'J', 'l', ',', 'n', 'o')), + ((StringCharInclValue) main.accessField("result", new Type(Type.TypeEnum.STRING))).getCertainContained()); + assertEquals(new HashSet<>(Set.of()), ((StringCharInclValue) main.accessField("result", new Type(Type.TypeEnum.STRING))).getMaybeContained()); + assertEquals(new HashSet<>(Set.of('J', 'O', 'H', 'N', ' ', 'D', 'E')), + ((StringCharInclValue) main.accessField("result2", new Type(Type.TypeEnum.STRING))).getCertainContained()); + assertEquals(new HashSet<>(Set.of()), + ((StringCharInclValue) main.accessField("result2", new Type(Type.TypeEnum.STRING))).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", new Type(Type.TypeEnum.STRING))).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", new Type(Type.TypeEnum.STRING))).getContentRegex()); + } + + @Test + @Disabled("Disabled due to containing break statements not yet supported") + 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..f03b5d8280 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/DeadCodeDetectionTest.java @@ -0,0 +1,574 @@ +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.Type; +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) Objects.requireNonNull(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", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); + assertEquals(2, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); + } + + /** + * simple switch test + */ + @Disabled("Disabled due to containing break statements not yet supported") + @Test + void testSwitch() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/switch"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + assertFalse(((INumberValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getInformation()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getInformation()); + assertFalse(((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); // z + assertTrue(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(50, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(100, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getInformation()); + } + + /** + * simple hashmap test + */ + @Test + void testHashMap() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/map"); + JavaObject main = getMainObject(interpretation); + assertNotNull(main); + } + + /** + * 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 + @Disabled("Disabled due to containing break statements not yet supported") + 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. + */ + @Disabled("Disabled due to containing break statements not yet supported") + @Test + void testBreak() throws ParsingException, InterruptedException { + AbstractInterpretation interpretation = interpretFromResource("java/ai/break"); + 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, ((INumberValue) main.accessField("result", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(101, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(200, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // z + assertEquals(200, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getValue()); // y + } + + /** + * simple test for ConditionalExpressions (a?b:c). + */ + @Test + @Disabled("Disabled due to ConditionalExpressions not yet supported") + 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", new Type(Type.TypeEnum.INT))).getValue()); + } + + /** + * a simple test for a 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", new Type(Type.TypeEnum.INT))).getInformation()); + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); + assertEquals(1, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); // x + assertEquals(200, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); // 2*y + assertEquals(500, ((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); // x + assertEquals(200, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); // 2*y + assertEquals(500, ((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); // x + assertEquals(700, ((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getValue()); // 2*y + assertEquals(500, ((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).getInformation()); // x + assertFalse(((INumberValue) main.accessField("result2", new Type(Type.TypeEnum.INT))).getInformation()); // y + assertEquals(25, ((INumberValue) main.accessField("result3", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).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", new Type(Type.TypeEnum.INT))).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..685c1dae5c --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/ai/ProgpediaTests.java @@ -0,0 +1,115 @@ +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; + } + + /** + * The Progpedia data set contains 15 problems, each with two categories of submissions (ACCEPTED and WRONG_ANSWER), and + * each category has multiple submissions. This method generates the resource directories for all these submissions. + */ + @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))); + } + + /** + * Generates the resource paths for all Java files in the Progpedia data set. This is used for the parameterized test. + */ + @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("takes too long") + @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("only for debugging a single file, not a real test") + void testSingle1() throws ParsingException, InterruptedException { + String fileName = "java/progpedia/00000006/ACCEPTED/00130_00001"; + Value.setUsedIntAiType(IntAiType.DEFAULT); + Value.setUsedFloatAiType(FloatAiType.DEFAULT); + Value.setUsedStringAiType(StringAiType.DEFAULT); + AbstractInterpretation interpretation = interpretFromResource(fileName); + VariableStore variableStore = interpretation.getVariables(); + assertNotNull(variableStore); + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/AiDeadCodeEvalTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/AiDeadCodeEvalTest.java new file mode 100644 index 0000000000..26e6a2237b --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/AiDeadCodeEvalTest.java @@ -0,0 +1,328 @@ +package de.jplag.java_cpg.evaluation; + +import static de.jplag.java_cpg.AbstractJavaCpgLanguageTest.BASE_PATH; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.checkNonDeadCodeNotRemoved; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.getTokensFromFileWithoutDeadCode; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.similarity; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.testFiles; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import de.jplag.ParsingException; +import de.jplag.Token; +import de.jplag.java_cpg.JavaCpgLanguage; +import de.jplag.java_cpg.ai.ArrayAiType; +import de.jplag.java_cpg.ai.CharAiType; +import de.jplag.java_cpg.ai.CpgErrorException; +import de.jplag.java_cpg.ai.FloatAiType; +import de.jplag.java_cpg.ai.IntAiType; +import de.jplag.java_cpg.ai.JavaLanguageFeatureNotSupportedException; +import de.jplag.java_cpg.ai.StringAiType; + +class AiDeadCodeEvalTest { + + @Test + @Disabled("Only for evaluation purposes, not a real test") + void AiDeadCodeEvaluationAll() { + testFiles().forEach(fileName -> { + try { + long startTime = System.nanoTime(); + List tokens = getTokensFromFileStandard(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFileStandard(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + List tokensWithoutDeadCode; + try { + tokensWithoutDeadCode = getTokensFromFileStandard(fileName, true, true, false, true, false); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + File csvFile = new File("Ai_deadcode_results_Standard.csv"); + boolean fileExists = csvFile.exists(); + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write( + "FileName,NoRemoval,SimpleRemoval,FullRemoval,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, + timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, + javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, tokensSound, simpleRemovalSound, removalSound)); + } 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)"); + } catch (ParsingException e) { + System.out.println(e.getMessage()); + } + assertTrue(true); + }); + + testFiles().forEach(fileName -> { + try { + long startTime = System.nanoTime(); + List tokens = getTokensFromFileLevel1(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFileLevel1(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + List tokensWithoutDeadCode; + try { + tokensWithoutDeadCode = getTokensFromFileLevel1(fileName, true, true, false, true, false); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + File csvFile = new File("Ai_deadcode_results_Level1.csv"); + boolean fileExists = csvFile.exists(); + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write( + "FileName,NoRemoval,SimpleRemoval,FullRemoval,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, + timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, + javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, tokensSound, simpleRemovalSound, removalSound)); + } 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)"); + } catch (ParsingException e) { + System.out.println(e.getMessage()); + } + assertTrue(true); + }); + + testFiles().forEach(fileName -> { + try { + long startTime = System.nanoTime(); + List tokens = getTokensFromFileLevel2(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFileLevel2(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + List tokensWithoutDeadCode; + try { + tokensWithoutDeadCode = getTokensFromFileLevel2(fileName, true, true, false, true, false); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + File csvFile = new File("Ai_deadcode_results_Level2.csv"); + boolean fileExists = csvFile.exists(); + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write( + "FileName,NoRemoval,SimpleRemoval,FullRemoval,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, + timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, + javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, tokensSound, simpleRemovalSound, removalSound)); + } 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)"); + } catch (ParsingException e) { + System.out.println(e.getMessage()); + } + assertTrue(true); + }); + } + + @NotNull + private static List getTokensFromFileStandard(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + assert normalize || !reorder; + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, + JavaCpgLanguage.deadCodeRemovalTransformations(), IntAiType.DEFAULT, FloatAiType.DEFAULT, StringAiType.DEFAULT, CharAiType.DEFAULT, + ArrayAiType.DEFAULT); + 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 getTokensFromFileLevel1(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + assert normalize || !reorder; + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, + JavaCpgLanguage.deadCodeRemovalTransformations(), IntAiType.INTERVALS, FloatAiType.DEFAULT, StringAiType.CHAR_INCLUSION, + CharAiType.DEFAULT, 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 getTokensFromFileLevel2(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + assert normalize || !reorder; + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, + JavaCpgLanguage.deadCodeRemovalTransformations(), IntAiType.SET, FloatAiType.SET, StringAiType.REGEX, CharAiType.SET, + ArrayAiType.DEFAULT); + 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; + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/DceLlmEvalTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/DceLlmEvalTest.java new file mode 100644 index 0000000000..8260b4b00e --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/DceLlmEvalTest.java @@ -0,0 +1,237 @@ +package de.jplag.java_cpg.evaluation; + +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.checkNonDeadCodeNotRemoved; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.similarity; +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.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +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.jplag.ParsingException; +import de.jplag.Token; +import de.jplag.java_cpg.JavaCpgLanguage; +import de.jplag.java_cpg.ai.ArrayAiType; +import de.jplag.java_cpg.ai.CharAiType; +import de.jplag.java_cpg.ai.CpgErrorException; +import de.jplag.java_cpg.ai.FloatAiType; +import de.jplag.java_cpg.ai.IntAiType; +import de.jplag.java_cpg.ai.JavaLanguageFeatureNotSupportedException; +import de.jplag.java_cpg.ai.StringAiType; +import de.jplag.java_cpg.transformation.GraphTransformation; + +/** + * Evaluation on the DCE-LLM dataset, which contains Java files with manually annotated dead code. We compare the + * results of our dead code removal with the manual annotations and measure the similarity and runtime. + */ +public class DceLlmEvalTest { + + private static @NotNull Stream dceLlmFiles() { + // String basePath = "/home/alpaka/PycharmProjects/baplots/testFiles"; + String basePath = "/home/alpaka/IdeaProjects/DCE-LLM/testFiles"; + Path baseDir = Path.of(basePath); + if (!Files.exists(baseDir)) { + return Stream.empty(); + } + try (Stream paths = Files.walk(baseDir)) { + List files = paths.filter(Files::isRegularFile).map(Path::toString).filter(path -> path.endsWith(".java")).toList(); + return files.stream(); + } catch (IOException _) { + return Stream.empty(); + } + } + + @NotNull + private static List getTokensFromFile(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + assert normalize || !reorder; + GraphTransformation[] transformations = JavaCpgLanguage.deadCodeRemovalTransformations(); + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, transformations, + // IntAiType.DEFAULT, FloatAiType.DEFAULT, StringAiType.DEFAULT, CharAiType.DEFAULT, ArrayAiType.DEFAULT); + // IntAiType.INTERVALS, FloatAiType.DEFAULT, StringAiType.CHAR_INCLUSION, CharAiType.DEFAULT, ArrayAiType.LENGTH); + IntAiType.SET, FloatAiType.SET, StringAiType.REGEX, CharAiType.SET, ArrayAiType.DEFAULT); + File file = new File(fileName); + Set files = Set.of(file); + List result = language.parse(files, normalize); + if (result.isEmpty()) { + throw new CpgErrorException("CPG could not parse file " + fileName); + } + result.removeLast(); // remove EOF token + return result; + } + + @NotNull + private static List getTokensFromFileWithoutDeadCode(@NotNull String fileName, boolean reorder, boolean removeSimpleDeadCode) + throws ParsingException { + try { + File originalFile = new File(fileName); + File tempFile = File.createTempFile("jplag_temp_", ".java"); + tempFile.deleteOnExit(); + if (originalFile.isDirectory()) { + // If it's a directory, find and process Java files inside + File[] javaFiles = originalFile.listFiles((_, name) -> name.endsWith(".java")); + if (javaFiles == null || javaFiles.length == 0) { + throw new ParsingException(originalFile, "No Java files found in directory"); + } + originalFile = javaFiles[0]; // Use the first Java file + } + 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, removeSimpleDeadCode); + List result = language.parse(Set.of(tempFile), false); + if (!result.isEmpty()) { + result.removeLast(); // remove EOF token + } + return result; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @ParameterizedTest + @Disabled("Only for evaluation purposes, not a real test") + @MethodSource("dceLlmFiles") + void DceLlmDeadCodeEvaluation(String fileName) throws ParsingException { + System.out.println(fileName); + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + List tokensWithoutDeadCode; + try { + tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true, false); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + throw new RuntimeException(e); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + File csvFile = new File("DceLlm_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,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, timeNoRemoval / 1_000_000.0, + timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, + tokensSound, simpleRemovalSound, removalSound)); + } 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("Only for evaluation purposes, not a real test") + void DceLlmDeadCodeEvaluationSingle() throws ParsingException { + // String fileName = "/home/alpaka/PycharmProjects/baplots/testFiles/p02723/s919988520.java"; + String fileName = "/home/alpaka/PycharmProjects/baplots/testFiles/p02766/s036883984.java"; + + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true, false); + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + // Assert we don't remove non-dead code + assertTrue(checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens)); + assertTrue(checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode)); + assertTrue(checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode)); + + 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); + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/EvaluationEngineTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/EvaluationEngineTest.java new file mode 100644 index 0000000000..d0576606d1 --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/EvaluationEngineTest.java @@ -0,0 +1,899 @@ +package de.jplag.java_cpg.evaluation; + +import static de.jplag.java_cpg.AbstractJavaCpgLanguageTest.BASE_PATH; +import static de.jplag.java_cpg.evaluation.PmdTest.getPmdDeadLinesInFile; +import static de.jplag.java_cpg.evaluation.PmdTest.runPmdForFile; +import static de.jplag.options.JPlagOptions.DEFAULT_SHOWN_COMPARISONS; +import static de.jplag.options.JPlagOptions.DEFAULT_SIMILARITY_METRIC; +import static de.jplag.options.JPlagOptions.DEFAULT_SIMILARITY_THRESHOLD; +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.Collectors; +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.jplag.JPlag; +import de.jplag.JPlagComparison; +import de.jplag.JPlagResult; +import de.jplag.Language; +import de.jplag.ParsingException; +import de.jplag.Token; +import de.jplag.clustering.ClusteringOptions; +import de.jplag.exceptions.ExitException; +import de.jplag.highlightextraction.FrequencyAnalysisOptions; +import de.jplag.java_cpg.JavaCpgLanguage; +import de.jplag.java_cpg.ai.ArrayAiType; +import de.jplag.java_cpg.ai.CharAiType; +import de.jplag.java_cpg.ai.CpgErrorException; +import de.jplag.java_cpg.ai.FloatAiType; +import de.jplag.java_cpg.ai.IntAiType; +import de.jplag.java_cpg.ai.JavaLanguageFeatureNotSupportedException; +import de.jplag.java_cpg.ai.ProgpediaTests; +import de.jplag.java_cpg.ai.StringAiType; +import de.jplag.java_cpg.transformation.GraphTransformation; +import de.jplag.merging.MergingOptions; +import de.jplag.options.JPlagOptions; +import de.jplag.reporting.reportobject.ReportObjectFactory; + +import kotlin.Pair; + +@Disabled("Only for manual evaluation of dead code removal and plagiarism detection") +class EvaluationEngineTest { + + static @NotNull Stream testFiles() { + return Stream.of( + // the first block is jvm warmup files + "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/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", "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", + // + "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"); + } + + private static @NotNull Stream> testPlagFiles() { + return Stream.of( + // JVM Warmup: + 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/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"), // break + new Pair<>("aiGenerated/geminiPlag/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"), // break + new Pair<>("aiGenerated/grok/project6.java", "aiGenerated/grok/project8.java"), + new Pair<>("aiGenerated/grok/project7.java", "aiGenerated/grok/project8.java") // break + ); + } + + private static @NotNull Stream> testPlagFilesUnrelated() { + return Stream.of( + // JVM Warmup: + 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/gemini/ProjectA.java", "aiGenerated/gemini/Project3.java"), + new Pair<>("aiGenerated/gemini/ProjectB.java", "aiGenerated/gemini/ProjectI.java"), + new Pair<>("aiGenerated/gemini/ProjectF.java", "aiGenerated/grok/project4.java"), + new Pair<>("aiGenerated/gemini/ProjectH.java", "aiGenerated/claude/Project1.java"), + new Pair<>("aiGenerated/gemini/ProjectJ.java", "aiGenerated/grok/project5.java"), + new Pair<>("aiGenerated/gemini/ProjectL.java", "aiGenerated/gemini/Project2.java"), + new Pair<>("aiGenerated/gemini/ProjectN.java", "aiGenerated/gemini/ProjectU.java"), + new Pair<>("aiGenerated/gemini/ProjectP.java", "aiGenerated/gemini/Project7.java"), + new Pair<>("aiGenerated/gemini/ProjectT.java", "aiGenerated/perplexityLabs/Project4.java"), + new Pair<>("aiGenerated/gemini/Project1.java", "aiGenerated/geminiPlag/NetworkController.java"), + new Pair<>("aiGenerated/gemini/Project1.java", "aiGenerated/gemini/Project8.java"), + new Pair<>("aiGenerated/gemini/Project7.java", "aiGenerated/gemini/ProjectG.java"), + new Pair<>("aiGenerated/gemini/Project4.java", "aiGenerated/gemini/ProjectC.java"), + new Pair<>("aiGenerated/gemini/Project4.java", "aiGenerated/gemini/ProjectQ.java"), + new Pair<>("aiGenerated/gemini/ProjectH.java", "aiGenerated/perplexityLabs/Project3.java"), + new Pair<>("aiGenerated/geminiPlag/NetworkController.java", "aiGenerated/grok/project2.java"), + new Pair<>("aiGenerated/claude/Project1.java", "aiGenerated/gemini/ProjectO.java"), + new Pair<>("aiGenerated/claude/Project4.java", "aiGenerated/claude/Project8.java"), + new Pair<>("aiGenerated/claude/Project4.java", "aiGenerated/grok/project8.java"), + new Pair<>("aiGenerated/claude/Project5.java", "aiGenerated/claude/Project2.java"), + new Pair<>("aiGenerated/claude/Project7.java", "aiGenerated/claude/Project3.java"), + new Pair<>("aiGenerated/claude/Project9.java", "aiGenerated/geminiPlag/GridOverseer.java"), + new Pair<>("aiGenerated/perplexityLabs/Project1.java", "aiGenerated/gemini/Project8.java"), + new Pair<>("aiGenerated/perplexityLabs/Project1.java", "aiGenerated/gemini/ProjectE.java"), + new Pair<>("aiGenerated/grok/project1.java", "aiGenerated/claude/Project10.java"), + new Pair<>("aiGenerated/grok/project3.java", "aiGenerated/gemini/ProjectM.java"), + new Pair<>("aiGenerated/grok/project3.java", "aiGenerated/geminiPlag/NetworkController.java"), + new Pair<>("aiGenerated/grok/project4.java", "aiGenerated/gemini/ProjectK.java"), + new Pair<>("aiGenerated/grok/project6.java", "aiGenerated/claude/Project6.java")); + } + + public 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()]; + } + + static @NotNull List getTokensFromFile(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + return getTokensFromFilePair(fileName, removeDeadCode, detectDeadCode, reorder, normalize, removeSimpleDeadCode).getFirst(); + } + + static @NotNull Pair, Integer> getTokensFromFilePair(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, + boolean reorder, boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + assert normalize || !reorder; + GraphTransformation[] transformations = JavaCpgLanguage.deadCodeRemovalTransformations(); + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, transformations, + IntAiType.DEFAULT, FloatAiType.DEFAULT, StringAiType.DEFAULT, CharAiType.DEFAULT, ArrayAiType.DEFAULT); + // IntAiType.INTERVALS, FloatAiType.DEFAULT, StringAiType.CHAR_INCLUSION, CharAiType.DEFAULT, ArrayAiType.LENGTH); + // IntAiType.SET, FloatAiType.SET, StringAiType.REGEX, CharAiType.SET, ArrayAiType.DEFAULT); + File file = new File(BASE_PATH.toFile().getAbsolutePath(), fileName); + Set files = Set.of(file); + Pair, Integer> result = language.parse2(files, normalize); + result.getFirst().removeLast(); // remove EOF token + return result; + } + + public static @NotNull List getTokensFromFileWithoutDeadCode(@NotNull String fileName, boolean reorder, boolean removeSimpleDeadCode) + throws ParsingException { + try { + File originalFile = new File(BASE_PATH.toFile().getAbsolutePath(), fileName); + File tempFile = File.createTempFile("jplag_temp_", ".java"); + tempFile.deleteOnExit(); + if (originalFile.isDirectory()) { + // If it's a directory, find and process Java files inside + File[] javaFiles = originalFile.listFiles((_, name) -> name.endsWith(".java")); + if (javaFiles == null || javaFiles.length == 0) { + throw new ParsingException(originalFile, "No Java files found in directory"); + } + originalFile = javaFiles[0]; // Use the first Java file + } + 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, removeSimpleDeadCode); + List result = language.parse(Set.of(tempFile), false); + if (!result.isEmpty()) { + result.removeLast(); // remove EOF token + } + return result; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static @NotNull List getTokensFromFileWithoutPmdDeadCode(@NotNull String fileName) throws ParsingException, IOException { + File tempFile = runPmdForFile(new File(BASE_PATH.toFile(), fileName)); + JavaCpgLanguage language = new JavaCpgLanguage(false, false, false, false); + List result = language.parse(Set.of(tempFile), false); + result.removeLast(); // remove EOF token + return result; + } + + private static double getJPlagCpgPlagScore(@NotNull String fileNameA, @NotNull String fileNameB, boolean removeDeadCode, boolean detectDeadCode, + boolean reorder, boolean normalize, boolean removeSimpleDeadCode) throws ExitException, IOException { + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, + JavaCpgLanguage.allTransformations(), IntAiType.DEFAULT, FloatAiType.DEFAULT, StringAiType.DEFAULT, CharAiType.DEFAULT, + ArrayAiType.DEFAULT); + // IntAiType.INTERVALS, FloatAiType.DEFAULT, StringAiType.CHAR_INCLUSION, CharAiType.DEFAULT, ArrayAiType.LENGTH); + // IntAiType.SET, FloatAiType.SET, StringAiType.REGEX, CharAiType.SET, ArrayAiType.DEFAULT); + return getJPlagScore(fileNameA, fileNameB, normalize, language); + } + + private static @NotNull JPlagResult getJPlagCpgPlagScore(@NotNull Set files, boolean removeDeadCode, boolean detectDeadCode, + boolean reorder, boolean normalize, boolean removeSimpleDeadCode) throws ExitException { + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, + JavaCpgLanguage.allTransformations(), IntAiType.DEFAULT, FloatAiType.DEFAULT, StringAiType.DEFAULT, CharAiType.DEFAULT, + ArrayAiType.DEFAULT); + // IntAiType.INTERVALS, FloatAiType.DEFAULT, StringAiType.CHAR_INCLUSION, CharAiType.DEFAULT, ArrayAiType.LENGTH); + // IntAiType.SET, FloatAiType.SET, StringAiType.REGEX, CharAiType.SET, ArrayAiType.DEFAULT); + return getJPlagScore(files, 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 @NotNull JPlagResult getJPlagPlagScore(@NotNull Set files, boolean normalize) throws ExitException { + de.jplag.java.JavaLanguage language = new de.jplag.java.JavaLanguage(); + return getJPlagScore(files, 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(); + return similarity; + } + + private static @NotNull JPlagResult getJPlagScore(@NotNull Set submissionDirectories, boolean normalize, Language language) + throws ExitException { + 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()); + return JPlag.run(options); + } + + private static @NotNull File createTempDir() throws IOException { + File tempDir = File.createTempFile("jplag_submission_", ""); + tempDir.delete(); + tempDir.mkdir(); + tempDir.deleteOnExit(); + return tempDir; + } + + public static boolean checkNonDeadCodeNotRemoved(@NotNull List verifiedDeadCode, @NotNull List detectedDeadCode) { + // we check that all tokens in verifiedDeadCode are also in detectedDeadCode + // if (detectedDeadCode.size() < verifiedDeadCode.size()) { + // System.out.println("Detected dead code size: " + detectedDeadCode.size() + ", Verified dead code size: " + + // verifiedDeadCode.size()); + // return false; // Detected dead code is smaller than verified dead code + // } + int detectedIndex = 0; + for (Token verifiedToken : verifiedDeadCode) { + boolean found = false; + while (detectedIndex < detectedDeadCode.size()) { + if (Objects.equals(detectedDeadCode.get(detectedIndex).toString(), verifiedToken.toString())) { + detectedIndex++; + found = true; + break; + } + detectedIndex++; + } + if (!found) { + System.out.println("Token not found: " + verifiedToken); + return false; // Token from verified dead code isn't found in detected dead code + } + } + return true; + } + + public static @NotNull Stream progpediaFiles() { + // warm up with 6 files + List warmups = List.of("progpedia/00000006/ACCEPTED/00218_00001/CigarrasTontas.java", + "progpedia/00000006/ACCEPTED/00076_00001/Main.java", "progpedia/00000006/ACCEPTED/00005_00001/CigarrasTontas.java", + "progpedia/00000006/ACCEPTED/00118_00002/Cigarras.java", "progpedia/00000006/ACCEPTED/00046_00002/cigarras.java", + "progpedia/00000006/ACCEPTED/00133_00001/Cigarras.java", "progpedia/00000021/WRONG_ANSWER/00168_00002/encomenda.java"); + return Stream.concat(warmups.stream(), ProgpediaTests.progpediaFiles()); + } + + public static @NotNull Stream kitGenFiles() { + List baseDirs = List.of("kit_DONT_COMMIT/BoardGame/insert", "kit_DONT_COMMIT/BoardGame/refactor", "kit_DONT_COMMIT/TicTacToe/insert", + "kit_DONT_COMMIT/TicTacToe/refactor", "kit_DONT_COMMIT/TicTacToe/gpt", "kit_DONT_COMMIT/TicTacToe/gptobf"); + return baseDirs.stream().map(baseDir -> new File(BASE_PATH.toFile(), baseDir)).filter(File::exists).filter(File::isDirectory) + .flatMap(dir -> Stream.ofNullable(dir.listFiles(File::isDirectory))).flatMap(Stream::of) + .map(file -> BASE_PATH.toFile().toURI().relativize(file.toURI()).getPath()); + } + + public static @NotNull Stream kitHumanFiles() { + List baseDirs = List.of("kit_DONT_COMMIT/BoardGame/human", "kit_DONT_COMMIT/TicTacToe/human" + // "kit_DONT_COMMIT/ws2223-Sheet4TaskA-perseverance", "kit_DONT_COMMIT/ws2425-Sheet3TaskA-dotsandboxes" + ); + return baseDirs.stream().map(baseDir -> new File(BASE_PATH.toFile(), baseDir)).filter(File::exists).filter(File::isDirectory) + .flatMap(dir -> Stream.ofNullable(dir.listFiles(File::isDirectory))).flatMap(Stream::of) + .map(file -> BASE_PATH.toFile().toURI().relativize(file.toURI()).getPath()); + } + + public static @NotNull Set kitBoardGamePlag() { + List baseDirs = List.of("kit_DONT_COMMIT/BoardGame/human", "kit_DONT_COMMIT/BoardGame/insert", "kit_DONT_COMMIT/BoardGame/refactor"); + return baseDirs.stream().map(baseDir -> new File(BASE_PATH.toFile(), baseDir)).filter(File::exists).filter(File::isDirectory) + .flatMap(dir -> Stream.ofNullable(dir.listFiles(File::isDirectory))).flatMap(Stream::of) + .map(file -> BASE_PATH.toFile().toURI().relativize(file.toURI()).getPath()).collect(Collectors.toSet()); + } + + public static @NotNull Set kitTicTocToePlag() { + List baseDirs = List.of("kit_DONT_COMMIT/TicTacToe/human", "kit_DONT_COMMIT/TicTacToe/insert", "kit_DONT_COMMIT/TicTacToe/refactor", + "kit_DONT_COMMIT/TicTacToe/gpt", "kit_DONT_COMMIT/TicTacToe/gptobf"); + return baseDirs.stream().map(baseDir -> new File(BASE_PATH.toFile(), baseDir)).filter(File::exists).filter(File::isDirectory) + .flatMap(dir -> Stream.ofNullable(dir.listFiles(File::isDirectory))).flatMap(Stream::of) + .map(file -> BASE_PATH.toFile().toURI().relativize(file.toURI()).getPath()).collect(Collectors.toSet()); + } + + @ParameterizedTest + @Disabled("Only for evaluation purposes, not a real test") + @MethodSource("testFiles") + void AiGeneratedTestDataDeadCodeEvaluation(String fileName) throws ParsingException, IOException { + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + Pair, Integer> result; + List tokensWithoutDeadCode; + int removedDeadLines = 0; + try { + result = getTokensFromFilePair(fileName, true, true, false, true, false); + tokensWithoutDeadCode = result.getFirst(); + removedDeadLines = result.getSecond(); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + // throw new RuntimeException(e); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + startTime = System.nanoTime(); + int pmdRemovedLines = getPmdDeadLinesInFile(new File(BASE_PATH.toFile(), fileName)); + long timePmd = System.nanoTime() - startTime; + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + if (!removalSound || !simpleRemovalSound || !tokensSound) { + System.out.println("Non-dead code was removed in file: " + fileName); + } + + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + File csvFile = new File("Ai_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,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound,removedDeadLines,pmdRemovedLines,timePmd\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b,%d,%d,%.2f%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, timeNoRemoval / 1_000_000.0, + timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, + tokensSound, simpleRemovalSound, removalSound, removedDeadLines, pmdRemovedLines, timePmd / 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)"); + System.out.println("Pmd removed lines: " + pmdRemovedLines + " (took " + timePmd / 1_000_000.0 + " ms)"); + assertTrue(true); + } + + @Test + @Disabled("Only for evaluation purposes, not a real test") + void AiGeneratedTestDataDeadCodeEvaluationSingle() throws ParsingException { + String fileName = "aiGenerated/claude/Project1.java"; + + List tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true, false); + + List tokens = getTokensFromFile(fileName, false, false, false, false, false); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true, false); + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + // Assert we don't remove non-dead code + assertTrue(checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens)); + assertTrue(checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode)); + assertTrue(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("Only for evaluation purposes, not a real test") + @MethodSource("kitHumanFiles") + // @MethodSource("kitGenFiles") + void KitDeadCodeEvaluation(String fileName) throws ParsingException { + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + List tokensWithoutDeadCode; + try { + tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true, false); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + // throw new RuntimeException(e); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + double simSimpleRemoval = similarity(tokens, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokens, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + + File csvFile = new File("Kit_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,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b%n", fileName, simSimpleRemoval, simFullRemoval, + removedSimpleTokens, removedFullTokens, timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, + timeFullRemoval / 1_000_000.0, javaLanguageFeatureNotSupported, cpgErrorException, runtimeError)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + 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("Only for evaluation purposes, not a real test") + void KitDeadCodeEvaluationSingle() throws ParsingException { + String fileName = "kit_DONT_COMMIT/BoardGame/human/subm208/"; + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true, true); + long timeSimpleRemoval = System.nanoTime() - startTime; + startTime = System.nanoTime(); + List tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true, true); + long timeFullRemoval = System.nanoTime() - startTime; + double simSimpleRemoval = similarity(tokens, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokens, tokensWithoutDeadCode); + System.out.println("Time no removal: " + timeNoRemoval / 1_000_000.0 + " ms"); + System.out.println("Similarity between no and automatic simple dead code removal: " + simSimpleRemoval + "% (took " + + timeSimpleRemoval / 1_000_000.0 + " ms)"); + System.out.println( + "Similarity between no and automatic dead code removal: " + simFullRemoval + "% (took " + timeFullRemoval / 1_000_000.0 + " ms)"); + assertTrue(true); + } + + @ParameterizedTest + @Disabled("Only for evaluation purposes, not a real test") + @MethodSource("progpediaFiles") + void ProgpediaDeadCodeEvaluation(String fileName) throws ParsingException, IOException { // the first 6 lines are warmup + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + Pair, Integer> result; + List tokensWithoutDeadCode; + int removedDeadLines = 0; + try { + result = getTokensFromFilePair(fileName, true, true, false, true, false); + tokensWithoutDeadCode = result.getFirst(); + removedDeadLines = result.getSecond(); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + // throw new RuntimeException(e); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + startTime = System.nanoTime(); + int pmdRemovedLines = getPmdDeadLinesInFile(new File(BASE_PATH.toFile(), fileName)); + long timePmd = System.nanoTime() - startTime; + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + 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,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound,removedDeadLines,pmdRemovedLines,timePmd\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b,%d,%d,%.2f%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, timeNoRemoval / 1_000_000.0, + timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, + tokensSound, simpleRemovalSound, removalSound, removedDeadLines, pmdRemovedLines, timePmd / 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)"); + System.out.println("Pmd removed lines: " + pmdRemovedLines + " (took " + timePmd / 1_000_000.0 + " ms)"); + assertTrue(true); + } + + @Test + @Disabled("Only for evaluation purposes, not a real test") + void ProgpediaDeadCodeEvaluationSingle() throws ParsingException { + String fileName = "progpedia/00000019/WRONG_ANSWER/00189_00001"; + + long startTime = System.nanoTime(); + List tokens = getTokensFromFile(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFile(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutDeadCode = getTokensFromFile(fileName, true, true, false, true, false); + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + // Assert we don't remove non-dead code + assertTrue(checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens)); + assertTrue(checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode)); + assertTrue(checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode)); + + 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("Only for evaluation purposes, not a real test") + @MethodSource("testPlagFiles") + // @MethodSource("testPlagFilesUnrelated") + 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, false); + long timeMinimalCpg = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + double similarityStandardCpg = getJPlagCpgPlagScore(fileA, fileB, false, false, false, true, false); + long timeStandardCpg = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + double similarityAi = getJPlagCpgPlagScore(fileA, fileB, true, true, false, true, false); + long timeAi = System.nanoTime() - startTime; + + File csvFile = new File("AI_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("Only for evaluation purposes, not a real test") + void AiGeneratedTestDataPlagEvaluationSingle() throws ExitException, IOException { + // new Pair<>("aiGenerated/gemini/ProjectT.java", "aiGenerated/perplexityLabs/Project4.java"), + String fileA = "aiGenerated/gemini/ProjectT.java"; + String fileB = "aiGenerated/perplexityLabs/Project4.java"; + double similarityJPlag = getJPlagPlagScore(fileA, fileB, false); + double similarityMinimalCpg = getJPlagCpgPlagScore(fileA, fileB, false, false, false, false, false); + double similarityStandardCpg = getJPlagCpgPlagScore(fileA, fileB, false, false, false, true, false); + double similarityAi = getJPlagCpgPlagScore(fileA, fileB, true, true, false, true, false); + + 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); + } + + @Test + void KitPlagTicTacToeEval() throws IOException, ExitException { + Set files = kitTicTocToePlag(); + Set fileSet = files.stream().skip(0).map(file -> new File(BASE_PATH.toFile().getAbsolutePath(), file)).collect(Collectors.toSet()); + + JPlagResult resultJPlag = getJPlagPlagScore(fileSet, false); + File outDir = new File("outputTicTacToe.jplag.zip"); + ReportObjectFactory reportObjectFactory = new ReportObjectFactory(outDir); + reportObjectFactory.createAndSaveReport(resultJPlag); + + JPlagResult resultCPG = getJPlagCpgPlagScore(fileSet, false, false, false, true, true); + File outDir2 = new File("outputTicTacToe.cpg.zip"); + ReportObjectFactory reportObjectFactory2 = new ReportObjectFactory(outDir2); + reportObjectFactory2.createAndSaveReport(resultCPG); + + JPlagResult resultAI = getJPlagCpgPlagScore(fileSet, true, true, false, true, true); + File outDir3 = new File("outputTicTacToe.ai.zip"); + ReportObjectFactory reportObjectFactory3 = new ReportObjectFactory(outDir3); + reportObjectFactory3.createAndSaveReport(resultAI); + + assertTrue(true); + } + + @Test + void KitPlagBoardGameEval() throws IOException, ExitException { + Set files = kitBoardGamePlag(); + Set fileSet = files.stream().sorted().skip(0).map(file -> new File(BASE_PATH.toFile().getAbsolutePath(), file)) + .collect(Collectors.toSet()); + + JPlagResult resultJPlag = getJPlagPlagScore(fileSet, false); + File outDir = new File("outputBoardGame.jplag.zip"); + ReportObjectFactory reportObjectFactory = new ReportObjectFactory(outDir); + reportObjectFactory.createAndSaveReport(resultJPlag); + + JPlagResult resultCPG = getJPlagCpgPlagScore(fileSet, false, false, false, true, true); + File outDir2 = new File("outputBoardGame.cpg.zip"); + ReportObjectFactory reportObjectFactory2 = new ReportObjectFactory(outDir2); + reportObjectFactory2.createAndSaveReport(resultCPG); + + JPlagResult resultAI = getJPlagCpgPlagScore(fileSet, true, true, false, true, true); + File outDir3 = new File("outputBoardGame.ai.zip"); + ReportObjectFactory reportObjectFactory3 = new ReportObjectFactory(outDir3); + reportObjectFactory3.createAndSaveReport(resultAI); + + assertTrue(true); + } + + @Test + @Disabled("Only for evaluation purposes, not a real test") + void KitPlagEvaluationSingle() throws ExitException, IOException { + String folderA = "kit_DONT_COMMIT/BoardGame/human/subm208/"; + String folderB = "kit_DONT_COMMIT/TicTacToe/human/24229"; + Set fileSet = Stream.of(folderA, folderB).map(file -> new File(BASE_PATH.toFile().getAbsolutePath(), file)).collect(Collectors.toSet()); + + JPlagResult resultJPlag = getJPlagPlagScore(fileSet, false); + double similarityJPlag = resultJPlag.getAllComparisons().getFirst().similarity(); + System.out.println("JPlag standard: " + similarityJPlag); + + JPlagResult minimalCpg = getJPlagCpgPlagScore(fileSet, false, false, false, false, false); + double similarityMinimalCpg = minimalCpg.getAllComparisons().getFirst().similarity(); + System.out.println("Cpg minimal transformations: " + similarityMinimalCpg); + + JPlagResult StandardCpg = getJPlagCpgPlagScore(fileSet, false, false, false, true, false); + double similarityStandardCpg = StandardCpg.getAllComparisons().getFirst().similarity(); + System.out.println("Cpg standard transformations: " + similarityStandardCpg); + + JPlagResult ai = getJPlagCpgPlagScore(fileSet, true, true, false, false, false); + double similarityAi = ai.getAllComparisons().getFirst().similarity(); + System.out.println("Cpg with AI dead code removal: " + similarityAi); + + System.out.println("Plagiarism scores for " + folderA + " and " + folderB + ":"); + assertTrue(true); + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/PmdTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/PmdTest.java new file mode 100644 index 0000000000..d810d6c7cc --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/PmdTest.java @@ -0,0 +1,251 @@ +package de.jplag.java_cpg.evaluation; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * This tests PMD to compare it to our results. + * @author GitHub Copilot (Claude Sonnet 4.5) + * @version 1.0 + */ +public class PmdTest { + + @Test + @Disabled("Only for testing the setup and PMD integration, not a real test") + void singleTest() throws Exception { + // File file = new File("src/test/resources/java/ai/deadCode5"); + // File file = new File("src/test/resources/java/aiGenerated/claude/Project10.java"); + File file = new File("src/test/resources/java/progpedia/00000022/ACCEPTED/00022_00003/infra_estrutura.java"); + int deadLines = getPmdDeadLinesInFile(file); + // File virtFile = runPmdForFile(file); + // assertNotNull(virtFile); + System.out.println("Dead lines: " + deadLines); + } + + /** + * Runs PMD on the given file or directory and counts the number of lines that PMD considers dead code. + * @param file The Java file or directory to analyze with PMD. + * @return The number of lines that PMD considers dead code. + * @throws IOException If there is an error running PMD or processing files. + */ + public static int getPmdDeadLinesInFile(@NotNull File file) throws IOException { + if (!file.exists()) { + throw new IOException("File does not exist: " + file.getAbsolutePath()); + } + if (file.isDirectory()) { + // Find the actual Java file in the directory that has violations + File virtFile = runPmdForFile(file); + + // Count total lines in all Java files in the directory + int totalOriginalLines = 0; + List javaFiles = findJavaFiles(file); + for (File javaFile : javaFiles) { + totalOriginalLines += Files.readAllLines(javaFile.toPath()).size(); + } + + int virtualLines = Files.readAllLines(virtFile.toPath()).size(); + return totalOriginalLines - virtualLines; + } else { + File virtFile = runPmdForFile(file); + List originalLines = Files.readAllLines(file.toPath()); + List virtualLines = Files.readAllLines(virtFile.toPath()); + return originalLines.size() - virtualLines.size(); + } + } + + private static @NotNull List findJavaFiles(@NotNull File directory) { + List javaFiles = new ArrayList<>(); + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + javaFiles.addAll(findJavaFiles(file)); + } else if (file.getName().endsWith(".java")) { + javaFiles.add(file); + } + } + } + return javaFiles; + } + + /** + * Runs PMD on the given file or directory and returns a virtual file with violation lines removed. + * @param file The Java file or directory to analyze with PMD. + * @return A virtual file with PMD violation lines removed. + * @throws IOException If there is an error running PMD or processing files. + */ + public static @NotNull File runPmdForFile(@NotNull File file) throws IOException { + String pmdPath = System.getProperty("user.home") + "/pmd-bin-7.21.0/bin/pmd"; + String folder = file.getAbsolutePath(); + ProcessBuilder processBuilder = new ProcessBuilder(pmdPath, "check", "-d", folder, "-R", "src/test/resources/pmdDeadCodeRules.xml", "-f", + "xml"); + processBuilder.redirectErrorStream(true); + Process process = processBuilder.start(); + + // Read the output as XML + StringBuilder xmlOutput = new StringBuilder(); + try (var reader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + if (!(line.trim().startsWith("[WARN]"))) { + xmlOutput.append(line).append("\n"); + } + } + } + Document doc; + try { + int exitCode = process.waitFor(); + if (exitCode == 1) { // PMD returns 0 if no violations, 4 if violations found, 1 for errors + throw new RuntimeException("PMD command failed with exit code: " + exitCode); + } + System.out.println("PMD exited with code: " + exitCode); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + doc = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xmlOutput.toString()))); + System.out.println("\n=== PMD XML Report Successfully Parsed ==="); + } catch (ParserConfigurationException | InterruptedException | SAXException e) { + throw new IOException("Failed to parse PMD XML output: " + e + "| Output was:\n" + xmlOutput); + } + + // Extract violations + List violationsList = extractViolations(doc); + System.out.println("Number of violations found: " + violationsList.size()); + + // Create virtual files with violations removed + return createVirtualFilesWithViolationsRemoved(violationsList, file); + } + + /** + * Represents information about a PMD violation. + * @author GitHub Copilot (Claude Sonnet 4.5) + */ + static class ViolationInfo { + private String filePath; + private int beginLine; + private int endLine; + private int beginColumn; + private int endColumn; + private String rule; + private String message; + + @Override + public String toString() { + return String.format("Violation[%s:%d-%d, rule=%s, message=%s]", filePath, beginLine, endLine, rule, message); + } + } + + /** + * Extracts violation information from the PMD XML document. + */ + private static @NotNull List extractViolations(@NotNull Document doc) { + List violations = new ArrayList<>(); + NodeList fileNodes = doc.getElementsByTagName("file"); + + for (int i = 0; i < fileNodes.getLength(); i++) { + Element fileElement = (Element) fileNodes.item(i); + String filePath = fileElement.getAttribute("name"); + + NodeList violationNodes = fileElement.getElementsByTagName("violation"); + for (int j = 0; j < violationNodes.getLength(); j++) { + Element violationElement = (Element) violationNodes.item(j); + + ViolationInfo info = new ViolationInfo(); + info.filePath = filePath; + info.beginLine = Integer.parseInt(violationElement.getAttribute("beginline")); + info.endLine = Integer.parseInt(violationElement.getAttribute("endline")); + info.beginColumn = Integer.parseInt(violationElement.getAttribute("begincolumn")); + info.endColumn = Integer.parseInt(violationElement.getAttribute("endcolumn")); + info.rule = violationElement.getAttribute("rule"); + info.message = violationElement.getTextContent().trim(); + + violations.add(info); + System.out.println(" - " + info); + } + } + return violations; + } + + /** + * Creates a virtual file with violation lines removed. + */ + private static @NotNull File createVirtualFilesWithViolationsRemoved(@NotNull List violations, @NotNull File originalFile) + throws IOException { + // Group violations by file + Map> violationsByFile = new HashMap<>(); + for (ViolationInfo violation : violations) { + violationsByFile.computeIfAbsent(violation.filePath, _ -> new ArrayList<>()).add(violation); + } + assert violationsByFile.size() == 1 || violationsByFile.isEmpty() : "Expected violations for exactly one file, but found: " + + violationsByFile.keySet(); + + String originalFilePath; + List fileViolations; + if (violationsByFile.size() == 1) { + Map.Entry> entry = violationsByFile.entrySet().iterator().next(); + originalFilePath = entry.getKey(); + fileViolations = entry.getValue(); + } else { + // Handle empty case + originalFilePath = originalFile.getAbsolutePath(); + fileViolations = new ArrayList<>(); + } + + // Sort violations by line number in descending order to remove from bottom to top + fileViolations.sort((v1, v2) -> Integer.compare(v2.beginLine, v1.beginLine)); + + // Read the original file + Path originalPath = Path.of(originalFilePath); + if (!Files.exists(originalPath)) { + throw new IOException("File not found: " + originalFilePath); + } + + List lines = Files.readAllLines(originalPath); + + // Collect line numbers to remove (convert to set for efficient lookup) + List virtualFileLines = getStrings(fileViolations, lines); + + File tempFile = File.createTempFile("jplag_temp_", ".java"); + tempFile.deleteOnExit(); + Files.write(tempFile.toPath(), virtualFileLines); + return tempFile; + } + + private static @NotNull List getStrings(@NotNull List fileViolations, List lines) { + Set linesToRemove = new HashSet<>(); + for (ViolationInfo violation : fileViolations) { + for (int line = violation.beginLine; line <= violation.endLine; line++) { + linesToRemove.add(line); + } + } + // Create virtual file content with violations removed + List virtualFileLines = new ArrayList<>(); + for (int i = 0; i < lines.size(); i++) { + int lineNumber = i + 1; // 1-based line number + if (!linesToRemove.contains(lineNumber)) { + virtualFileLines.add(lines.get(i)); + } + } + return virtualFileLines; + } + +} diff --git a/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/ProgpediaEvalTest.java b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/ProgpediaEvalTest.java new file mode 100644 index 0000000000..a29deaa14a --- /dev/null +++ b/languages/java-cpg/src/test/java/de/jplag/java_cpg/evaluation/ProgpediaEvalTest.java @@ -0,0 +1,307 @@ +package de.jplag.java_cpg.evaluation; + +import static de.jplag.java_cpg.AbstractJavaCpgLanguageTest.BASE_PATH; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.checkNonDeadCodeNotRemoved; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.getTokensFromFileWithoutDeadCode; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.progpediaFiles; +import static de.jplag.java_cpg.evaluation.EvaluationEngineTest.similarity; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import de.jplag.ParsingException; +import de.jplag.Token; +import de.jplag.java_cpg.JavaCpgLanguage; +import de.jplag.java_cpg.ai.ArrayAiType; +import de.jplag.java_cpg.ai.CharAiType; +import de.jplag.java_cpg.ai.CpgErrorException; +import de.jplag.java_cpg.ai.FloatAiType; +import de.jplag.java_cpg.ai.IntAiType; +import de.jplag.java_cpg.ai.JavaLanguageFeatureNotSupportedException; +import de.jplag.java_cpg.ai.StringAiType; + +class ProgpediaEvalTest { + + @Test + @Disabled("Only for evaluation purposes, not a real test") + void ProgpediaDeadCodeEvaluationAll() { + progpediaFiles().forEach(fileName -> { + try { + long startTime = System.nanoTime(); + List tokens = getTokensFromFileStandard(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFileStandard(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + List tokensWithoutDeadCode; + try { + tokensWithoutDeadCode = getTokensFromFileStandard(fileName, true, true, false, true, false); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + File csvFile = new File("Progpedia_deadcode_results_Standard.csv"); + boolean fileExists = csvFile.exists(); + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write( + "FileName,NoRemoval,SimpleRemoval,FullRemoval,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, + timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, + javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, tokensSound, simpleRemovalSound, removalSound)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } catch (ParsingException e) { + System.out.println(e.getMessage()); + } + assertTrue(true); + }); + + progpediaFiles().forEach(fileName -> { + try { + long startTime = System.nanoTime(); + List tokens = getTokensFromFileLevel1(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFileLevel1(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + List tokensWithoutDeadCode; + try { + tokensWithoutDeadCode = getTokensFromFileLevel1(fileName, true, true, false, true, false); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + File csvFile = new File("Progpedia_deadcode_results_Level1.csv"); + boolean fileExists = csvFile.exists(); + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write( + "FileName,NoRemoval,SimpleRemoval,FullRemoval,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, + timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, + javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, tokensSound, simpleRemovalSound, removalSound)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } catch (ParsingException e) { + System.out.println(e.getMessage()); + } + assertTrue(true); + }); + + progpediaFiles().forEach(fileName -> { + try { + long startTime = System.nanoTime(); + List tokens = getTokensFromFileLevel2(fileName, false, false, false, false, false); + long timeNoRemoval = System.nanoTime() - startTime; + + startTime = System.nanoTime(); + List tokensWithoutSimpleDeadCode = getTokensFromFileLevel2(fileName, false, false, false, true, false); + long timeSimpleRemoval = System.nanoTime() - startTime; + + boolean javaLanguageFeatureNotSupported = false; + boolean cpgErrorException = false; + boolean runtimeError = false; + startTime = System.nanoTime(); + List tokensWithoutDeadCode; + try { + tokensWithoutDeadCode = getTokensFromFileLevel2(fileName, true, true, false, true, false); + } catch (JavaLanguageFeatureNotSupportedException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } catch (CpgErrorException _) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } catch (Exception e) { + Throwable one = e.getCause(); + Throwable two = one.getCause(); + if (two instanceof CpgErrorException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + cpgErrorException = true; + } else if (two instanceof JavaLanguageFeatureNotSupportedException) { + tokensWithoutDeadCode = new ArrayList<>(tokens); + javaLanguageFeatureNotSupported = true; + } else { + runtimeError = true; + tokensWithoutDeadCode = new ArrayList<>(tokens); + } + } + long timeFullRemoval = System.nanoTime() - startTime; + + List tokensWithoutDeadCodeManual = getTokensFromFileWithoutDeadCode(fileName, false, false); + + boolean tokensSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokens); + boolean simpleRemovalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + boolean removalSound = checkNonDeadCodeNotRemoved(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + if (javaLanguageFeatureNotSupported || cpgErrorException || runtimeError) { + tokensSound = true; + simpleRemovalSound = true; + removalSound = true; + } + double simNoRemoval = similarity(tokensWithoutDeadCodeManual, tokens); + double simSimpleRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutSimpleDeadCode); + double simFullRemoval = similarity(tokensWithoutDeadCodeManual, tokensWithoutDeadCode); + double removedSimpleTokens = tokens.size() - tokensWithoutSimpleDeadCode.size(); + double removedFullTokens = tokens.size() - tokensWithoutDeadCode.size(); + double removedManualTokens = tokens.size() - tokensWithoutDeadCodeManual.size(); + + File csvFile = new File("Progpedia_deadcode_results_Level2.csv"); + boolean fileExists = csvFile.exists(); + try (java.io.FileWriter writer = new java.io.FileWriter(csvFile, true)) { + if (!fileExists) { + writer.write( + "FileName,NoRemoval,SimpleRemoval,FullRemoval,RemovedSimpleTokens,RemovedFullTokens,RemovedManualTokens,TimeNoRemoval(ms),TimeSimpleRemoval(ms),TimeFullRemoval(ms),JavaLanguageFeatureNotSupported,CpgErrorException,RuntimeException,tokensSound,simpleRemovalSound,removalSound\n"); + } + writer.write(String.format("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.2f,%.2f,%.2f,%b,%b,%b,%b,%b,%b%n", fileName, simNoRemoval, + simSimpleRemoval, simFullRemoval, removedSimpleTokens, removedFullTokens, removedManualTokens, + timeNoRemoval / 1_000_000.0, timeSimpleRemoval / 1_000_000.0, timeFullRemoval / 1_000_000.0, + javaLanguageFeatureNotSupported, cpgErrorException, runtimeError, tokensSound, simpleRemovalSound, removalSound)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } catch (ParsingException e) { + System.out.println(e.getMessage()); + } + assertTrue(true); + }); + } + + @NotNull + private static List getTokensFromFileStandard(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + assert normalize || !reorder; + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, + JavaCpgLanguage.deadCodeRemovalTransformations(), IntAiType.DEFAULT, FloatAiType.DEFAULT, StringAiType.DEFAULT, CharAiType.DEFAULT, + ArrayAiType.DEFAULT); + 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 getTokensFromFileLevel1(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + assert normalize || !reorder; + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, + JavaCpgLanguage.deadCodeRemovalTransformations(), IntAiType.INTERVALS, FloatAiType.DEFAULT, StringAiType.CHAR_INCLUSION, + CharAiType.DEFAULT, 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 getTokensFromFileLevel2(@NotNull String fileName, boolean removeDeadCode, boolean detectDeadCode, boolean reorder, + boolean normalize, boolean removeSimpleDeadCode) throws ParsingException { + assert normalize || !reorder; + JavaCpgLanguage language = new JavaCpgLanguage(removeDeadCode, detectDeadCode, reorder, removeSimpleDeadCode, + JavaCpgLanguage.deadCodeRemovalTransformations(), IntAiType.SET, FloatAiType.SET, StringAiType.REGEX, CharAiType.SET, + ArrayAiType.DEFAULT); + 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; + } + +} diff --git a/languages/java-cpg/src/test/resources/java/.gitignore b/languages/java-cpg/src/test/resources/java/.gitignore new file mode 100644 index 0000000000..a4c2503f3c --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/.gitignore @@ -0,0 +1 @@ +kit_DONT_COMMIT/ 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..adf81577a1 --- /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<>(); + 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..8f437f3622 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project10.java @@ -0,0 +1,809 @@ +// 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; + } + + //DeadCodeStart + public String getDeptId() { + return deptId; + } + //DeadCodeEnd + + // PLAGIARIZED: Renamed from addDoctor + public void addProfessor(Professor professor) { + professors.add(professor); + } + + //DeadCodeStart + public List getProfessors() { + return professors; + } + + // 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--; + } + + 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; + } + + //DeadCodeStart + public String getProfessorId() { + return professorId; + } + + 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++; + } + + //DeadCodeStart + public int getCourseCount() { + return courseCount; + } + + 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; + } + + //DeadCodeStart + public String getStudentId() { + return studentId; + } + + public int getAge() { + return age; + } + //DeadCodeEnd + + // PLAGIARIZED from Patient.addMedicalRecord() + public void addAcademicRecord(String record) { + academicHistory.add(record); + } + + // PLAGIARIZED from Patient.addAppointment() + public void addEnrollment(Enrollment enrollment) { + enrollments.add(enrollment); + } + + //DeadCodeStart + public String getStatus() { + return currentStatus; + } + + 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; + } + + //DeadCodeStart + public Professor getProfessor() { + return professor; + } + //DeadCodeEnd + + public String getStatus() { + return status; + } + + //DeadCodeStart + public void setStatus(String status) { + this.status = status; + } + + public String getFeedback() { + return feedback; + } + //DeadCodeEnd + + // PLAGIARIZED from Appointment.setDiagnosis() + public void setFeedback(String feedback) { + this.feedback = feedback; + this.status = "Graded"; + } + + //DeadCodeStart + 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); + } + } + + //DeadCodeStart + public List getStudents() { + return students; + } + + public List getProfessors() { + return professors; + } + + public List getDepartments() { + return departments; + } + + 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..d685aebed4 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project3.java @@ -0,0 +1,69 @@ +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) { + //DeadCodeStart + if (balance >= amount) { + //DeadCodeEnd + balance -= amount; + System.out.println("Withdrawn: $" + amount); + //DeadCodeStart + } else { + System.out.println("Insufficient funds"); + } + //DeadCodeEnd + } + + 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..a92ad5b10e --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project6.java @@ -0,0 +1,142 @@ +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; + } + + //DeadCodeStart + public String getName() { + return name; + } + //DeadCodeEnd + + public int getAge() { + return age; + } + + //DeadCodeStart + public String getDepartmentId() { + return departmentId; + } + //DeadCodeEnd + + //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..e1371a1f42 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project7.java @@ -0,0 +1,332 @@ +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, "Available"); + } +} + +// 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; + } + + //DeadCodeStart + public String getMemberId() { + return memberId; + } + + public List getBorrowedBooks() { + return borrowedBooks; + } + //DeadCodeEnd + + public void borrowBook(Book book) { + borrowedBooks.add(book); + borrowingHistory.add(book.getTitle()); + totalBorrows++; + } + + //DeadCodeStart + public void returnBook(Book book) { + borrowedBooks.remove(book); + } + //DeadCodeEnd + + 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()); + } + } + + //DeadCodeStart + public void returnBook(Member member, Book book) { + book.setAvailable(true); + member.returnBook(book); + System.out.println(member.getName() + " returned " + book.getTitle()); + } + //DeadCodeEnd + + public void displayAvailableBooks() { + System.out.println("Available books in " + name + ":"); + for (Book book : books) { + if (book.isAvailable()) { + System.out.println(" " + book); + } + } + } + + //DeadCodeStart + public List getBooks() { + return books; + } + + 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..a81e62b5dc --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project8.java @@ -0,0 +1,401 @@ +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, "In 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++; + } + + //DeadCodeStart + // PLAGIARIZED: Copy of Member.returnBook() + public void cancelOrder(Product product) { + orderedProducts.remove(product); + } + //DeadCodeEnd + + // 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()); + } + } + + //DeadCodeStart + // 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()); + } + //DeadCodeEnd + + // 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); + } + } + } + + //DeadCodeStart + public List getProducts() { + return products; + } + + 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..01f3c2cdfb --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/claude/Project9.java @@ -0,0 +1,721 @@ +// 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; + } + + //DeadCodeStart + public String getDeptId() { + return deptId; + } + //DeadCodeEnd + + public void addDoctor(Doctor doctor) { + doctors.add(doctor); + } + + //DeadCodeStart + public List getDoctors() { + return doctors; + } + + public boolean hasCapacity() { + return currentPatients < capacity; + } + + public void incrementPatientCount() { + currentPatients++; + } + + public void decrementPatientCount() { + if (currentPatients > 0) currentPatients--; + } + + 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; + } + + //DeadCodeStart + public String getDoctorId() { + return doctorId; + } + + public String getSpecialization() { + return specialization; + } + //DeadCodeEnd + + public void setDepartment(Department dept) { + this.assignedDepartment = dept; + } + + public void addAppointment(Appointment appointment) { + appointments.add(appointment); + appointmentCount++; + } + + //DeadCodeStart + public int getAppointmentCount() { + return appointmentCount; + } + + public List getAppointments() { + return appointments; + } + + 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; + } + + //DeadCodeStart + public String getPatientId() { + return patientId; + } + + public int getAge() { + return age; + } + //DeadCodeEnd + + public void addMedicalRecord(String record) { + medicalHistory.add(record); + } + + public void addAppointment(Appointment appointment) { + appointments.add(appointment); + } + + //DeadCodeStart + public String getStatus() { + return currentStatus; + } + + public void setStatus(String status) { + this.currentStatus = status; + } + //DeadCodeEnd + + 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; + } + + //DeadCodeStart + public Doctor getDoctor() { + return doctor; + } + //DeadCodeEnd + + 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); + } + } + + //DeadCodeStart + public List getPatients() { + return patients; + } + + public List getDoctors() { + return doctors; + } + + public List getDepartments() { + return departments; + } + + 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..172221f1b5 --- /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 (ints) instead of ints, but the logic is identical + int[] timestamps = { 162000, 161000, 163000, 160500, 162500 }; + + 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(int[] 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(int[] logs, int start, int end) { + int 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. + int 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 + int temp = logs[marker]; + logs[marker] = logs[scanner]; + logs[scanner] = temp; + } + } + + // Final swap of the reference element + int 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..bf35aab51c --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectC.java @@ -0,0 +1,165 @@ +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."); + } + //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..5012595333 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectL.java @@ -0,0 +1,117 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +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<>(); + private static PriorityQueue sellSide = new PriorityQueue<>(); + + 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..eb84bd4e2b --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectM.java @@ -0,0 +1,121 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Random; +import java.util.stream.Collectors; + +/** + * 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<>(); + private static PriorityQueue listingQueue = new PriorityQueue<>(); + + 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..c7a72a4a81 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectT.java @@ -0,0 +1,100 @@ +/** + * 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 ---"); + + //DeadCodeStart + try { + //DeadCodeEnd + // 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"); + } + } + + } catch (AccessDeniedException e) { + System.err.println("SECURITY ALERT: " + e.getMessage()); + } + //DeadCodeEnd + } + + //DeadCodeStart + private static boolean scanRetina() { + // Stub for hardware interaction + return true; + } + //DeadCodeEnd + + // --- Generics & Inner Classes --- + + //DeadCodeStart + interface Securable { + String getContent(); + } + //DeadCodeEnd + + // 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 { + //DeadCodeStart + if (!this.pinCode.equals(enteredPin)) { + throw new AccessDeniedException("Invalid PIN"); + } + //DeadCodeEnd + this.locked = false; + return item; + } + } + + static class TopSecretDoc implements Securable { + private String filename; + + TopSecretDoc(String f) { + this.filename = f; + } + + @Override + public String getContent() { + return "Content of " + filename; + } + } + + //DeadCodeStart + static class AccessDeniedException extends Exception { + AccessDeniedException(String msg) { + super(msg); + } + } + //DeadCodeEnd +} 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..1c09b931ea --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/aiGenerated/gemini/ProjectU.java @@ -0,0 +1,109 @@ +/** + * 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: 'Securable' interface copied + //DeadCodeStart + interface Edible { + String getFlavor(); // Was 'getContent' + } + //DeadCodeEnd + + // 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 { + //DeadCodeStart + if (!this.secretWord.equals(spokenWord)) { + throw new HandSlapException("Wrong secret word"); + } + //DeadCodeEnd + this.closed = false; + return snack; + } + } + + static class Biscuit implements Edible { + private String type; + + Biscuit(String t) { + this.type = t; + } + + @Override + public String getFlavor() { + return "Taste of " + type; + } + } + + //DeadCodeStart + // PLAGIARISM: Custom Exception copied + static class HandSlapException extends Exception { + HandSlapException(String msg) { + super(msg); + } + } + //DeadCodeEnd +} 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..d5fd7316d7 --- /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."); + return; + } 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/kit_DONT_COMMIT/data.md b/languages/java-cpg/src/test/resources/java/kit_DONT_COMMIT/data.md new file mode 100644 index 0000000000..53bd4102f3 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/kit_DONT_COMMIT/data.md @@ -0,0 +1,19 @@ +Removed because it leads to infinitive runtime in Tokenization Pass: +kit_DONT_COMMIT/BoardGame/human/subm114 +kit_DONT_COMMIT/BoardGame/human/subm119 +kit_DONT_COMMIT/BoardGame/human/subm120 +kit_DONT_COMMIT/BoardGame/human/subm395 + +Removed because cpg failed: +kit_DONT_COMMIT/BoardGame/human/subm121 +lit_DONT_COMMIT/TicTacToe/human/24846 +kit_DONT_COMMIT/TicTacToe/human/24745 +kit_DONT_COMMIT/TicTacToe/human/24570 +kit_DONT_COMMIT/TicTacToe/human/24257 +kit_DONT_COMMIT/BoardGame/human/subm367/ +/kit_DONT_COMMIT/BoardGame/human/subm269 +/kit_DONT_COMMIT/BoardGame/human/subm187/ +kit_DONT_COMMIT/BoardGame/refactor-plag-subm143 + +Removed because it contains do while loops which are not supported yet: +kit_DONT_COMMIT/ws2425-Sheet3TaskA-dotsandboxes/submission019 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..f20f346aae --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/progpedia/00000006/ACCEPTED/00130_00001/A.java @@ -0,0 +1,100 @@ +import java.util.Scanner; + +class Node{ + + int val; Node next; + Node(int v, Node n){ + val=v; next=n; + } +} + +//DeadCodeStart +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..aaad193e87 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/progpedia/00000006/WRONG_ANSWER/00217_00001/cigarras.java @@ -0,0 +1,46 @@ +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/removedData.md b/languages/java-cpg/src/test/resources/java/progpedia/removedData.md new file mode 100644 index 0000000000..37ba66a7e4 --- /dev/null +++ b/languages/java-cpg/src/test/resources/java/progpedia/removedData.md @@ -0,0 +1,11 @@ +**Removed due to errors in cpg:** + +progpedia/00000056/WRONG_ANSWER/00065_* +progpedia/00000056/ACCEPTED/00065_* +progpedia/00000053/ACCEPTED/00005_00001/Proteins.java + + + + +**Removed due to errors in jplag-cpg:** +progpedia/00000039/WRONG_ANSWER/00132_* 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/pmdDeadCodeRules.xml b/languages/java-cpg/src/test/resources/pmdDeadCodeRules.xml new file mode 100644 index 0000000000..9e7ae5f673 --- /dev/null +++ b/languages/java-cpg/src/test/resources/pmdDeadCodeRules.xml @@ -0,0 +1,23 @@ + + + + + + Custom PMD ruleset using all rules that detect dead code. + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 1abfc5ec24..c0584733d7 100644 --- a/pom.xml +++ b/pom.xml @@ -402,6 +402,11 @@ java ${revision} + + de.jplag + java-cpg + ${revision} + de.jplag python-3 @@ -432,11 +437,6 @@ kotlin ${revision} - - de.jplag - java-cpg - ${revision} - de.jplag rlang diff --git a/report-viewer/model/src/Language.ts b/report-viewer/model/src/Language.ts index c613af5dfd..32cca8847c 100644 --- a/report-viewer/model/src/Language.ts +++ b/report-viewer/model/src/Language.ts @@ -3,7 +3,6 @@ */ enum ParserLanguage { JAVA = 'java', - JAVA_CPG = 'java-cpg', PYTHON = 'python3', C = 'c', CPP = 'cpp', @@ -12,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',