paramVars, MethodDeclaration method) {
+ if (abstractInterpretation == null) {
+ return new VoidValue();
+ }
+ return abstractInterpretation.runMethod(methodName, paramVars, method);
+ }
+
+ /**
+ * @param fieldName the name of the field to access.
+ * @return the value of the field or VoidValue if the field does not exist.
+ */
+ public IValue accessField(@NotNull String fieldName) {
+ Variable result = fields.getVariable(new VariableName(fieldName));
+ if (result == null) {
+ return new VoidValue();
+ }
+ return result.getValue();
+ }
+
+ /**
+ * Changes the value of an existing field variable in this object. If the field does not exist, it will be created.
+ * @param fieldName the name of the field to change.
+ * @param value the new value of the field.
+ */
+ public void changeField(@NotNull String fieldName, IValue value) {
+ Variable variable = fields.getVariable(new VariableName(fieldName));
+ if (variable == null) {
+ fields.addVariable(new Variable(fieldName, value));
+ return;
+ }
+ variable.setValue(value);
+ }
+
+ /**
+ * @param field Sets a new field variable in this object.
+ */
+ public void setField(@NotNull Variable field) {
+ this.fields.addVariable(field);
+ }
+
+ /**
+ * Sets the abstract interpretation engine for this object. If you call methods on this object, this engine will be used
+ * for execution.
+ * @param abstractInterpretation the abstract interpretation engine or null.
+ */
+ public void setAbstractInterpretation(@Nullable AbstractInterpretation abstractInterpretation) {
+ this.abstractInterpretation = abstractInterpretation;
+ }
+
+ @Override
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ switch (operator) {
+ case "==" -> {
+ if (this.equals(other)) {
+ return new BooleanValue(true);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "!=" -> {
+ if (this.equals(other)) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "+" -> {
+ if (other instanceof IStringValue stringValue) {
+ // case: JavaObject with toString method
+ IValue toStringResult = this.callMethod("toString", List.of(), null);
+ if (toStringResult instanceof IStringValue stringFromObject && stringValue.getInformation()
+ && stringFromObject.getInformation()) {
+ return new StringValue(stringValue.getValue() + stringFromObject.getValue());
+ }
+ return new StringValue();
+ } else {
+ throw new UnsupportedOperationException(
+ "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType());
+ }
+ }
+ case "instanceof" -> {
+ return new BooleanValue();
+ }
+ default -> throw new UnsupportedOperationException(
+ "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType());
+ }
+ }
+
+ @NotNull
+ @Override
+ public JavaObject copy() {
+ if (fields == null) {
+ return new JavaObject(null, this.abstractInterpretation);
+ }
+ return new JavaObject(new Scope(this.fields), this.abstractInterpretation);
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ if (!(other instanceof JavaObject)) { // cannot merge different types
+ setToUnknown();
+ return;
+ }
+ if (fields == null || ((JavaObject) other).fields == null) {
+ fields = new Scope();
+ return;
+ }
+ this.fields.merge(((JavaObject) other).fields);
+ }
+
+ @Override
+ public void setToUnknown() {
+ if (fields != null) {
+ fields.setEverythingUnknown();
+ }
+ }
+
+ @Override
+ public void setInitialValue() {
+ if (fields != null) {
+ fields.setEverythingUnknown();
+ }
+ fields = null;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/NullValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/NullValue.java
new file mode 100644
index 0000000000..48e75a4628
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/NullValue.java
@@ -0,0 +1,55 @@
+package de.jplag.java_cpg.ai.variables.values;
+
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.Type;
+
+/**
+ * Represents the Java null value.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class NullValue extends Value {
+
+ /**
+ * Constructs a new NullValue.
+ */
+ public NullValue() {
+ super(Type.NULL);
+ }
+
+ @Override
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ switch (operator) {
+ case "==" -> {
+ return new BooleanValue(other instanceof NullValue);
+ }
+ case "!=" -> {
+ return new BooleanValue(!(other instanceof NullValue));
+ }
+ default -> throw new UnsupportedOperationException("Operator " + operator + " not supported for NullValue.");
+ }
+ }
+
+ @NotNull
+ @Override
+ public Value copy() {
+ return new NullValue();
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ // do nothing
+ }
+
+ @Override
+ public void setToUnknown() {
+ // ToDo: replace with JavaObject?
+ }
+
+ @Override
+ public void setInitialValue() {
+ // do nothing
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/Value.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/Value.java
new file mode 100644
index 0000000000..b02bfa7fb8
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/Value.java
@@ -0,0 +1,444 @@
+package de.jplag.java_cpg.ai.variables.values;
+
+import java.util.List;
+import java.util.Set;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import de.jplag.java_cpg.ai.ArrayAiType;
+import de.jplag.java_cpg.ai.CharAiType;
+import de.jplag.java_cpg.ai.FloatAiType;
+import de.jplag.java_cpg.ai.IntAiType;
+import de.jplag.java_cpg.ai.StringAiType;
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.arrays.IJavaArray;
+import de.jplag.java_cpg.ai.variables.values.arrays.JavaArray;
+import de.jplag.java_cpg.ai.variables.values.arrays.JavaLengthArray;
+import de.jplag.java_cpg.ai.variables.values.chars.CharSetValue;
+import de.jplag.java_cpg.ai.variables.values.chars.CharValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.FloatSetValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.FloatValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.IntIntervalValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.IntSetValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.IntValue;
+import de.jplag.java_cpg.ai.variables.values.string.StringCharInclValue;
+import de.jplag.java_cpg.ai.variables.values.string.StringRegexValue;
+import de.jplag.java_cpg.ai.variables.values.string.StringValue;
+
+import kotlin.Pair;
+
+/**
+ * Abstract super class for all values.
+ *
+ * Also contains factory methods to create Value instances based on the configured AI types.
+ * @author ujiqk
+ * @version 1.0
+ */
+public abstract class Value implements IValue {
+
+ private static IntAiType usedIntAiType = IntAiType.DEFAULT;
+ private static FloatAiType usedFloatAiType = FloatAiType.DEFAULT;
+ private static StringAiType usedStringAiType = StringAiType.DEFAULT;
+ private static CharAiType usedCharAiType = CharAiType.DEFAULT;
+ private static ArrayAiType usedArrayAiType = ArrayAiType.DEFAULT;
+
+ @NotNull
+ private final Type type;
+ @Nullable
+ private Pair arrayPosition; // necessary for an array assign to work
+ @Nullable
+ private IJavaObject parentObject; // necessary for some field access
+
+ protected Value(@NotNull Type type) {
+ this.type = type;
+ }
+
+ /**
+ * The default is {@link IntAiType#DEFAULT}.
+ * @param intAiType the type to use for integer values.
+ */
+ public static void setUsedIntAiType(@NotNull IntAiType intAiType) {
+ usedIntAiType = intAiType;
+ }
+
+ /**
+ * The default is {@link FloatAiType#DEFAULT}.
+ * @param floatAiType the type to use for float values.
+ */
+ public static void setUsedFloatAiType(@NotNull FloatAiType floatAiType) {
+ usedFloatAiType = floatAiType;
+ }
+
+ /**
+ * The default is {@link StringAiType#DEFAULT}.
+ * @param stringAiType the type to use for string values.
+ */
+ public static void setUsedStringAiType(@NotNull StringAiType stringAiType) {
+ usedStringAiType = stringAiType;
+ }
+
+ /**
+ * The default is {@link CharAiType#DEFAULT}.
+ * @param charAiType the type to use for char values.
+ */
+ public static void setUsedCharAiType(@NotNull CharAiType charAiType) {
+ usedCharAiType = charAiType;
+ }
+
+ /**
+ * The default is {@link ArrayAiType#DEFAULT}.
+ * @param arrayAiType the type to use for array values.
+ */
+ public static void setUsedArrayAiType(@NotNull ArrayAiType arrayAiType) {
+ usedArrayAiType = arrayAiType;
+ }
+
+ // ------------------ Value Factories ------------------//
+
+ /**
+ * Constructs a Value instance based on the provided type.
+ * @param type the type of the value.
+ * @return a Value instance corresponding to the specified type.
+ * @throws IllegalArgumentException if the type is unsupported.
+ */
+ @NotNull
+ public static IValue valueFactory(@NotNull Type type) {
+ return switch (type) {
+ case INT -> getNewIntValue();
+ case STRING -> getNewStringValue();
+ case BOOLEAN -> new BooleanValue();
+ case OBJECT -> new JavaObject();
+ case VOID -> new VoidValue();
+ case ARRAY, LIST -> getNewArayValue();
+ case NULL -> new NullValue();
+ case FLOAT -> getNewFloatValue();
+ case FUNCTION -> new FunctionValue();
+ case CHAR -> getNewCharValue();
+ default -> throw new IllegalArgumentException("Unsupported type: " + type);
+ };
+ }
+
+ /**
+ * Value factory for when a value is known.
+ * @param value the known value.
+ * @return a {@link Value} instance representing the known value.
+ * @throws IllegalStateException if the value type is unsupported.
+ */
+ @NotNull
+ public static IValue valueFactory(@Nullable Object value) {
+ if (value == null) {
+ return new NullValue();
+ }
+ switch (value) {
+ case String s -> {
+ return getNewStringValue(s);
+ }
+ case Integer i -> {
+ return getNewIntValue(i);
+ }
+ case Boolean b -> {
+ return new BooleanValue(b);
+ }
+ case Double d -> {
+ return getNewFloatValue(d);
+ }
+ case Long l -> { // all integer numbers are treated as int
+ assert l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE;
+ return getNewIntValue(l.intValue());
+ }
+ case Character c -> {
+ return getNewCharValue(c);
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + value);
+ }
+ }
+
+ /**
+ * Value factory for when a value could have multiple possible values.
+ * @param values the possible values.
+ * @param The type of the values.
+ * @return a {@link Value} instance representing the possible values.
+ * @throws IllegalStateException if the set of values contains unsupported types.
+ */
+ @NotNull
+ @SuppressWarnings("unchecked")
+ public static IValue valueFactory(@NotNull Set values) {
+ assert !values.isEmpty();
+ Object first = values.iterator().next();
+ return switch (first) {
+ case String _ -> getNewStringValue((Set) values);
+ case Integer _ -> getNewIntValue((Set) values);
+ case Double _ -> getNewFloatValue((Set) values);
+ case Character _ -> getNewCharValue((Set) values);
+ default -> throw new IllegalStateException("Unexpected value type in list: " + first.getClass());
+ };
+ }
+
+ /**
+ * Value factory for when a value has lower/upper bounds.
+ * @param lowerBound the lower bound.
+ * @param upperBound the upper bound.
+ * @param The type of the bounds.
+ * @return a {@link Value} instance representing the bounded value.
+ * @throws IllegalStateException if the bound types are unsupported.
+ */
+ @NotNull
+ public static IValue valueFactory(@NotNull T lowerBound, @NotNull T upperBound) {
+ return switch (lowerBound) {
+ case String _ -> throw new IllegalStateException("Strings dont have bounds");
+ case Integer _ -> getNewIntValue((int) lowerBound, (int) upperBound);
+ case Double _ -> getNewFloatValue((double) lowerBound, (double) upperBound);
+ default -> throw new IllegalStateException("Unexpected value type in bound : " + lowerBound.getClass());
+ };
+ }
+
+ /**
+ * Creates a new integer value based on the configured AI type.
+ * @return a new integer value instance.
+ */
+ @NotNull
+ public static INumberValue getNewIntValue() {
+ return switch (usedIntAiType) {
+ case INTERVALS -> new IntIntervalValue();
+ case DEFAULT -> new IntValue();
+ case SET -> new IntSetValue();
+ };
+ }
+
+ @NotNull
+ private static Value getNewIntValue(int number) {
+ return switch (usedIntAiType) {
+ case INTERVALS -> new IntIntervalValue(number);
+ case DEFAULT -> new IntValue(number);
+ case SET -> new IntSetValue(number);
+ };
+ }
+
+ @NotNull
+ private static Value getNewIntValue(@NotNull Set possibleNumbers) {
+ return switch (usedIntAiType) {
+ case INTERVALS -> new IntIntervalValue(possibleNumbers);
+ case DEFAULT -> new IntValue(possibleNumbers);
+ case SET -> new IntSetValue(possibleNumbers);
+ };
+ }
+
+ @NotNull
+ private static Value getNewIntValue(int lowerBound, int upperBound) {
+ return switch (usedIntAiType) {
+ case INTERVALS -> new IntIntervalValue(lowerBound, upperBound);
+ case DEFAULT -> new IntValue(lowerBound, upperBound);
+ case SET -> new IntSetValue(lowerBound, upperBound);
+ };
+ }
+
+ @NotNull
+ private static Value getNewFloatValue() {
+ return switch (usedFloatAiType) {
+ case DEFAULT -> new FloatValue();
+ case SET -> new FloatSetValue();
+ };
+ }
+
+ /**
+ * Creates a new float value based on the configured AI type.
+ * @param number the float number.
+ * @return a new float value instance.
+ */
+ @NotNull
+ public static Value getNewFloatValue(double number) {
+ return switch (usedFloatAiType) {
+ case DEFAULT -> new FloatValue(number);
+ case SET -> new FloatSetValue(number);
+ };
+ }
+
+ @NotNull
+ private static Value getNewFloatValue(@NotNull Set possibleNumbers) {
+ return switch (usedFloatAiType) {
+ case DEFAULT -> new FloatValue(possibleNumbers);
+ case SET -> new FloatSetValue(possibleNumbers);
+ };
+ }
+
+ @NotNull
+ private static Value getNewFloatValue(double lowerBound, double upperBound) {
+ return switch (usedFloatAiType) {
+ case DEFAULT -> new FloatValue(lowerBound, upperBound);
+ case SET -> new FloatSetValue(lowerBound, upperBound);
+ };
+ }
+
+ @NotNull
+ private static Value getNewStringValue() {
+ return switch (usedStringAiType) {
+ case DEFAULT -> new StringValue();
+ case CHAR_INCLUSION -> new StringCharInclValue();
+ case REGEX -> new StringRegexValue();
+ };
+ }
+
+ @NotNull
+ private static Value getNewStringValue(String value) {
+ return switch (usedStringAiType) {
+ case DEFAULT -> new StringValue(value);
+ case CHAR_INCLUSION -> new StringCharInclValue(value);
+ case REGEX -> new StringRegexValue(value);
+ };
+ }
+
+ @NotNull
+ private static Value getNewStringValue(@NotNull Set values) {
+ return switch (usedStringAiType) {
+ case DEFAULT -> new StringValue();
+ case CHAR_INCLUSION -> new StringCharInclValue(values);
+ case REGEX -> new StringRegexValue(values);
+ };
+ }
+
+ @NotNull
+ private static Value getNewCharValue() {
+ return switch (usedCharAiType) {
+ case DEFAULT -> new CharValue();
+ case SET -> new CharSetValue();
+ };
+ }
+
+ @NotNull
+ private static Value getNewCharValue(char character) {
+ return switch (usedCharAiType) {
+ case DEFAULT -> new CharValue(character);
+ case SET -> new CharSetValue(character);
+ };
+ }
+
+ @NotNull
+ private static Value getNewCharValue(@NotNull Set characters) {
+ return switch (usedCharAiType) {
+ case DEFAULT -> new CharValue(characters);
+ case SET -> new CharSetValue(characters);
+ };
+ }
+
+ @NotNull
+ private static Value getNewArayValue() {
+ return switch (usedArrayAiType) {
+ case DEFAULT -> new JavaArray();
+ case LENGTH -> new JavaLengthArray();
+ };
+ }
+
+ /**
+ * Creates a new array value with the specified inner type.
+ * @param innerType the type of the elements in the array.
+ * @return a new array value instance.
+ */
+ @NotNull
+ public static IJavaArray getNewArayValue(Type innerType) {
+ return switch (usedArrayAiType) {
+ case DEFAULT -> new JavaArray(innerType);
+ case LENGTH -> new JavaLengthArray(innerType);
+ };
+ }
+
+ /**
+ * Creates a new array value with the specified values.
+ * @param values the values to initialize the array with.
+ * @return a new array value instance.
+ */
+ @NotNull
+ public static IJavaArray getNewArayValue(List values) {
+ return switch (usedArrayAiType) {
+ case DEFAULT -> new JavaArray(values);
+ case LENGTH -> new JavaLengthArray(values);
+ };
+ }
+
+ /**
+ * Creates a new array value with the specified inner type and length.
+ * @param innerType the type of the elements in the array.
+ * @param length the length of the array.
+ * @return a new array value instance.
+ */
+ @NotNull
+ public static IJavaArray getNewArayValue(Type innerType, INumberValue length) {
+ return switch (usedArrayAiType) {
+ case DEFAULT -> new JavaArray(length, innerType);
+ case LENGTH -> new JavaLengthArray(innerType, length);
+ };
+ }
+
+ // ------------------ End of Value Factories ------------------//
+
+ /**
+ * @return the type of this value.
+ */
+ @NotNull
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Performs a binary operation between this value and another value.
+ * @param operator the operator.
+ * @param other the other value.
+ * @return the result value. VoidValue if the operation does not return a value.
+ * @throws UnsupportedOperationException if the operation is not supported between the two value types.
+ */
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ throw new UnsupportedOperationException("Binary operation " + operator + " not supported between " + getType() + " and " + other.getType());
+ }
+
+ /**
+ * Performs a unary operation on this value.
+ * @param operator the operator.
+ * @return the result value. VoidValue if the operation does not return a value.
+ * @throws IllegalArgumentException if the operation is not supported for this value type.
+ */
+ public IValue unaryOperation(@NotNull String operator) {
+ switch (operator) {
+ case "throw" -> {
+ return new VoidValue();
+ }
+ default -> throw new IllegalArgumentException("Unary operation " + operator + " not supported for " + getType());
+ }
+ }
+
+ /**
+ * {@link #setParentObject(IJavaObject)} must be called before to use this method.
+ * @return the parent object of this value. Can be null.
+ */
+ @Nullable
+ public IJavaObject getParentObject() {
+ return parentObject;
+ }
+
+ /**
+ * Sets the parent object of this value. Must be called before some filed accesses.
+ * @param parentObject the parent object. Can be null.
+ */
+ public void setParentObject(@Nullable IJavaObject parentObject) {
+ this.parentObject = parentObject;
+ }
+
+ /**
+ * {@link #setArrayPosition(IJavaArray, INumberValue)} must be called before to use this method.
+ * @return the position of this value in the array that contains it.
+ */
+ public Pair getArrayPosition() {
+ assert arrayPosition != null;
+ return arrayPosition;
+ }
+
+ /**
+ * Sets the position of this value in the array that contains it. Necessary to set before array assignments.
+ * @param array the array that contains this value.
+ * @param index the index of this value in the array.
+ */
+ public void setArrayPosition(@NotNull IJavaArray array, @NotNull INumberValue index) {
+ this.arrayPosition = new Pair<>(array, index);
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/VoidValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/VoidValue.java
new file mode 100644
index 0000000000..6fb8616e7d
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/VoidValue.java
@@ -0,0 +1,79 @@
+package de.jplag.java_cpg.ai.variables.values;
+
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.numbers.FloatValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.IntValue;
+import de.jplag.java_cpg.ai.variables.values.string.StringValue;
+
+/**
+ * Void typed value. Represents no value or completely unknown value.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class VoidValue extends Value {
+
+ /**
+ * Creates a new Void typed value. Represents no value or completely unknown value.
+ */
+ public VoidValue() {
+ super(Type.VOID);
+ }
+
+ @Override
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ switch (operator) {
+ case "==", ">", "<", ">=", "<=", "!=" -> {
+ return new BooleanValue();
+ }
+ case "+", "-", "*", "/" -> {
+ return switch (other) {
+ case IntValue ignored -> new IntValue();
+ case FloatValue ignored -> new FloatValue();
+ case StringValue ignored -> new StringValue();
+ default -> new VoidValue();
+ };
+ }
+ case "&", "^" -> {
+ return new VoidValue();
+ }
+ default -> throw new UnsupportedOperationException("Operator " + operator + " not supported for VoidValue.");
+ }
+ }
+
+ @Override
+ public IValue unaryOperation(@NotNull String operator) {
+ switch (operator) {
+ case "!" -> {
+ return new BooleanValue();
+ }
+ case "--", "++", "abs", "+", "-" -> {
+ return new VoidValue();
+ }
+ default -> throw new IllegalArgumentException("Unary operation " + operator + " not supported for " + getType());
+ }
+ }
+
+ @NotNull
+ @Override
+ public Value copy() {
+ return new VoidValue();
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ // do nothing
+ }
+
+ @Override
+ public void setToUnknown() {
+ // do nothing
+ }
+
+ @Override
+ public void setInitialValue() {
+ // nothing
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/IJavaArray.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/IJavaArray.java
new file mode 100644
index 0000000000..73284d5a0d
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/IJavaArray.java
@@ -0,0 +1,27 @@
+package de.jplag.java_cpg.ai.variables.values.arrays;
+
+import de.jplag.java_cpg.ai.variables.values.IJavaObject;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue;
+
+/**
+ * Also needs to extend JavaObject because Java arrays are objects.
+ * @author ujiqk
+ */
+public interface IJavaArray extends IJavaObject {
+
+ /**
+ * Access an array element at the given index.
+ * @param index The index to access.
+ * @return The value at the given index.
+ */
+ IValue arrayAccess(INumberValue index);
+
+ /**
+ * Assign a value to an array element at the given index.
+ * @param index The index to assign to.
+ * @param value The value to assign.
+ */
+ void arrayAssign(INumberValue index, IValue value);
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaArray.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaArray.java
new file mode 100644
index 0000000000..e8eb4d4080
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaArray.java
@@ -0,0 +1,534 @@
+package de.jplag.java_cpg.ai.variables.values.arrays;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration;
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+import de.jplag.java_cpg.ai.variables.values.IJavaObject;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.JavaObject;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.VoidValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue;
+import de.jplag.java_cpg.ai.variables.values.string.StringValue;
+
+/**
+ * A Java Array representation. Java arrays are objects. Lists are modeled as Java arrays.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class JavaArray extends JavaObject implements IJavaArray {
+
+ private Type innerType;
+ @Nullable
+ private List values; // values = null: no information about the array
+
+ /**
+ * a Java Array with no information and undefined size.
+ * @param innerType the type of the array elements.
+ */
+ public JavaArray(Type innerType) {
+ super(Type.ARRAY);
+ this.innerType = innerType;
+ }
+
+ /**
+ * a Java Array with exact information.
+ * @param values the values of the array in the correct order.
+ */
+ public JavaArray(@NotNull List values) {
+ super(Type.ARRAY);
+ if (values.isEmpty()) {
+ this.innerType = null;
+ this.values = values;
+ return;
+ }
+ assert values.stream().map(IValue::getType).distinct().count() == 1;
+ this.innerType = values.getFirst().getType();
+ this.values = values;
+ }
+
+ /**
+ * a Java Array with no information and undefined size.
+ */
+ public JavaArray() {
+ super(Type.ARRAY);
+ this.innerType = null;
+ }
+
+ /**
+ * a Java Array with exact length and type information.
+ * @param length the length of the array; must contain information.
+ * @param innerType the type of the array elements.
+ */
+ public JavaArray(@NotNull INumberValue length, Type innerType) {
+ super(Type.ARRAY);
+ this.innerType = innerType;
+ if (length.getInformation()) {
+ int len = Math.max(0, (int) length.getValue());
+ values = new ArrayList<>(len);
+ Value placeholder = new VoidValue();
+ for (int i = 0; i < len; i++) {
+ values.add(placeholder.copy());
+ }
+ }
+ }
+
+ private JavaArray(@Nullable Type innerType, @Nullable List values) {
+ super(Type.ARRAY);
+ this.innerType = innerType;
+ this.values = values;
+ }
+
+ /**
+ * Access an element of the array.
+ * @param index the index to access; does not have to contain information.
+ * @return the superset of possible values at the given indexes.
+ * @throws UnsupportedOperationException if the inner type is not supported.
+ */
+ public IValue arrayAccess(INumberValue index) {
+ if (values != null && index.getInformation()) {
+ int idx = (int) index.getValue();
+ if (idx >= 0 && idx < values.size()) {
+ return values.get(idx);
+ }
+ }
+ // if no information, return an unknown value of the inner type
+ if (innerType == null) {
+ return new VoidValue();
+ }
+ return switch (innerType) {
+ case INT -> Value.valueFactory(Type.INT);
+ case BOOLEAN -> new BooleanValue();
+ case STRING -> Value.valueFactory(Type.STRING);
+ case OBJECT -> new JavaObject();
+ case ARRAY, LIST -> new JavaArray();
+ case FLOAT -> Value.valueFactory(Type.FLOAT);
+ case CHAR -> Value.valueFactory(Type.CHAR);
+ default -> throw new UnsupportedOperationException("Array of type " + innerType + " not supported");
+ };
+ }
+
+ /**
+ * Assign a value to a position in the array.
+ * @param index the index to assign to; does not have to contain information.
+ * @param value the value to assign.
+ */
+ public void arrayAssign(INumberValue index, IValue value) {
+ if (values != null && index.getInformation()) {
+ int idx = (int) index.getValue();
+ if (idx >= 0 && idx < values.size()) {
+ values.set(idx, value);
+ }
+ } else {
+ // no information about the array, set to unknown
+ values = null;
+ }
+ }
+
+ @Override
+ public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) {
+ switch (methodName) {
+ case "toString" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ return new StringValue();
+ }
+ case "add" -> {
+ if (paramVars.size() == 1) {
+ if (values != null) {
+ assert paramVars.getFirst().getType().equals(innerType);
+ values.add(paramVars.getFirst());
+ }
+ return new VoidValue();
+ } else if (paramVars.size() == 2) { // index, element
+ if (values != null) {
+ assert paramVars.getFirst() instanceof INumberValue;
+ INumberValue index = (INumberValue) paramVars.getFirst();
+ if (index.getInformation()) {
+ int idx = (int) index.getValue();
+ if (idx >= 0 && idx <= values.size()) {
+ assert paramVars.getLast().getType().equals(innerType);
+ values.add(idx, paramVars.getLast());
+ } else {
+ values = null; // no information
+ }
+ } else {
+ values = null; // no information
+ }
+ }
+ return new VoidValue();
+ } else {
+ throw new UnsupportedOperationException("add with " + paramVars.size() + " parameters is not supported");
+ }
+ }
+ case "stream" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ return this;
+ }
+ case "size" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ if (values != null) {
+ return Value.valueFactory(values.size());
+ }
+ return Value.valueFactory(Type.INT);
+ }
+ case "map" -> {
+ // ToDo
+ return this;
+ }
+ case "max" -> {
+ // ToDo
+ if (innerType == Type.INT) {
+ return Value.valueFactory(Type.INT);
+ } else if (innerType == Type.FLOAT) {
+ return Value.valueFactory(Type.FLOAT);
+ } else {
+ return new VoidValue();
+ }
+ }
+ case "indexOf" -> {
+ assert paramVars.size() == 1;
+ if (values != null) {
+ for (int i = 0; i < values.size(); i++) {
+ if (values.get(i).equals(paramVars.getFirst())) {
+ return Value.valueFactory(i);
+ }
+ }
+ return Value.valueFactory(-1);
+ }
+ return Value.valueFactory(Type.INT);
+ }
+ case "remove" -> {
+ if (paramVars == null || paramVars.isEmpty()) { // remove head
+ return this.callMethod("removeFirst", null, method);
+ }
+ assert paramVars.size() == 1;
+ if (values == null) {
+ return new VoidValue();
+ }
+ // either remove(int index) or remove(Object o) -> ToDo: cannot distinguish with Integer parameter
+ if (paramVars.getFirst() instanceof INumberValue number) {
+ if (number.getInformation()) {
+ return values.remove((int) number.getValue());
+ }
+ return new VoidValue();
+ } else {
+ for (int i = 0; i < values.size(); i++) {
+ if (values.get(i).equals(paramVars.getFirst())) {
+ values.remove(i);
+ return Value.valueFactory(true);
+ }
+ }
+ return Value.valueFactory(false);
+ }
+ }
+ case "get", "elementAt" -> {
+ assert paramVars.size() == 1;
+ if (paramVars.getFirst() instanceof VoidValue) {
+ paramVars.set(0, Value.valueFactory(Type.INT));
+ }
+ assert paramVars.getFirst() instanceof INumberValue;
+ return arrayAccess((INumberValue) paramVars.getFirst());
+ }
+ case "contains" -> {
+ assert paramVars.size() == 1;
+ if (values != null) {
+ for (IValue value : values) {
+ if (value.equals(paramVars.getFirst())) {
+ return Value.valueFactory(true);
+ }
+ }
+ return Value.valueFactory(false);
+ }
+ return Value.valueFactory(Type.BOOLEAN);
+ }
+ case "lastIndexOf" -> {
+ assert paramVars.size() == 1;
+ if (values != null) {
+ for (int i = values.size() - 1; i >= 0; i--) {
+ if (values.get(i).equals(paramVars.getFirst())) {
+ return Value.valueFactory(i);
+ }
+ }
+ return Value.valueFactory(-1);
+ }
+ return Value.valueFactory(Type.INT);
+ }
+ case "getLast", "peek" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ if (values != null && !values.isEmpty()) {
+ return values.getLast();
+ }
+ // no information
+ if (innerType == null) {
+ return new VoidValue();
+ }
+ return arrayAccess((INumberValue) Value.valueFactory(1));
+ }
+ case "removeLast", "pop" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ if (values != null && !values.isEmpty()) {
+ return values.removeLast();
+ }
+ // no information
+ values = null;
+ return new VoidValue();
+ }
+ case "addLast", "push" -> {
+ assert paramVars.size() == 1;
+ if (values != null) {
+ assert paramVars.getFirst().getType().equals(innerType);
+ values.add(paramVars.getFirst());
+ }
+ return new VoidValue();
+ }
+ case "removeFirst", "poll" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ if (values != null && !values.isEmpty()) {
+ return values.removeFirst();
+ }
+ // no information
+ values = null;
+ return new VoidValue();
+ }
+ case "isEmpty" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ if (values != null) {
+ return Value.valueFactory(values.isEmpty());
+ }
+ return Value.valueFactory(Type.BOOLEAN);
+ }
+ case "fill" -> { // void fill(int[] a, int val) or void fill(int[] a, int fromIndex, int toIndex, int val)
+ assert paramVars.size() == 1 || paramVars.size() == 3;
+ if (values != null) {
+ if (paramVars.size() == 1) {
+ IValue val = paramVars.getFirst();
+ for (int i = 0; i < values.size(); i++) {
+ values.set(i, val);
+ }
+ } else {
+ assert paramVars.getFirst() instanceof INumberValue;
+ assert paramVars.get(1) instanceof INumberValue;
+ INumberValue fromIndex = (INumberValue) paramVars.getFirst();
+ INumberValue toIndex = (INumberValue) paramVars.get(1);
+ if (fromIndex.getInformation() && toIndex.getInformation()) {
+ int fromIdx = (int) fromIndex.getValue();
+ int toIdx = (int) toIndex.getValue();
+ if (fromIdx >= 0 && toIdx <= values.size() && fromIdx <= toIdx) {
+ IValue val = paramVars.get(2);
+ for (int i = fromIdx; i < toIdx; i++) {
+ values.set(i, val);
+ }
+ } else {
+ values = null; // no information
+ }
+ } else {
+ values = null; // no information
+ }
+ }
+ }
+ return new VoidValue();
+ }
+ case "sort" -> { // void// sort(int[] a) or void sort(int[] a, int fromIndex, int toIndex)
+ if (paramVars.size() == 1) { // with Comparator
+ this.values = null; // ToDo
+ return new VoidValue();
+ }
+ assert paramVars.size() == 0 || paramVars.size() == 2;
+ if (values != null) {
+ if (paramVars.size() == 0) {
+ values.sort(null);
+ } else {
+ assert paramVars.getFirst() instanceof INumberValue;
+ assert paramVars.get(1) instanceof INumberValue;
+ INumberValue fromIndex = (INumberValue) paramVars.getFirst();
+ INumberValue toIndex = (INumberValue) paramVars.get(1);
+ if (fromIndex.getInformation() && toIndex.getInformation()) {
+ int fromIdx = (int) fromIndex.getValue();
+ int toIdx = (int) toIndex.getValue();
+ if (fromIdx >= 0 && toIdx <= values.size() && fromIdx <= toIdx) {
+ List sublist = values.subList(fromIdx, toIdx);
+ sublist.sort(null);
+ for (int i = fromIdx; i < toIdx; i++) {
+ values.set(i, sublist.get(i - fromIdx));
+ }
+ } else {
+ values = null; // no information
+ }
+ } else {
+ values = null; // no information
+ }
+ }
+ }
+ return new VoidValue();
+ }
+ case "copyOfRange" -> { // int[] copyOfRange(int[] original, int from, int to)
+ assert paramVars.size() == 2;
+ if (values != null) {
+ assert paramVars.getFirst() instanceof INumberValue;
+ assert paramVars.get(1) instanceof INumberValue;
+ INumberValue fromIndex = (INumberValue) paramVars.getFirst();
+ INumberValue toIndex = (INumberValue) paramVars.get(1);
+ if (fromIndex.getInformation() && toIndex.getInformation()) {
+ int fromIdx = (int) fromIndex.getValue();
+ int toIdx = (int) toIndex.getValue();
+ if (fromIdx >= 0 && toIdx <= values.size() && fromIdx <= toIdx) {
+ List sublist = new ArrayList<>(values.subList(fromIdx, toIdx));
+ return new JavaArray(sublist);
+ }
+ }
+ }
+ return new JavaArray(innerType);
+ }
+ case "clear" -> {
+ if (values != null) {
+ values.clear();
+ }
+ return new VoidValue();
+ }
+ case "getFirst", "peekFirst" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ if (values != null && !values.isEmpty()) {
+ return values.getFirst();
+ }
+ // no information
+ if (innerType == null) {
+ return new VoidValue();
+ }
+ return arrayAccess((INumberValue) Value.valueFactory(0));
+ }
+ case "removeFirstOccurrence" -> {
+ assert paramVars.size() == 1;
+ if (values == null) {
+ return Value.valueFactory(false);
+ }
+ for (int i = 0; i < values.size(); i++) {
+ if (values.get(i).equals(paramVars.getFirst())) {
+ values.remove(i);
+ return Value.valueFactory(true);
+ }
+ }
+ return Value.valueFactory(false);
+ }
+ case "addAll" -> {
+ assert paramVars.size() == 1;
+ if (paramVars.getFirst() instanceof JavaArray otherArray) {
+ if (this.values != null && otherArray.values != null) {
+ for (IValue val : otherArray.values) {
+ assert val.getType().equals(this.innerType);
+ this.values.add(val);
+ }
+ } else {
+ this.values = null;
+ }
+ } else {
+ this.values = null;
+ }
+ return new VoidValue();
+ }
+ case "addFirst" -> {
+ assert paramVars.size() == 1;
+ if (values != null) {
+ assert paramVars.getFirst().getType().equals(innerType);
+ values.addFirst(paramVars.getFirst());
+ }
+ return new VoidValue();
+ }
+ case "iterator" -> {
+ throw new UnsupportedOperationException("Iterators are not supported");
+ }
+ case "clone" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ return this.copy();
+ }
+ case "filter" -> {
+ this.values = null;
+ return Value.valueFactory(Type.LIST);
+ }
+ case "findFirst" -> {
+ if (values != null) {
+ if (!values.isEmpty()) {
+ return values.getFirst();
+ }
+ }
+ return new VoidValue();
+ }
+ case "forEach" -> {
+ this.values = null;
+ this.innerType = Type.VOID;
+ return Value.valueFactory(Type.LIST);
+ }
+ case "collect" -> {
+ this.values = null;
+ return Value.valueFactory(Type.LIST);
+ }
+ default -> throw new UnsupportedOperationException(methodName);
+ }
+ }
+
+ @Override
+ public IValue accessField(@NotNull String fieldName) {
+ switch (fieldName) {
+ case "length" -> {
+ if (values != null) {
+ return Value.valueFactory(values.size());
+ }
+ return Value.valueFactory(Type.INT);
+ }
+ default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported for JavaArray");
+ }
+ }
+
+ @NotNull
+ @Override
+ public JavaArray copy() {
+ List newValues = new ArrayList<>();
+ if (values == null) {
+ return new JavaArray(innerType);
+ }
+ for (IValue value : values) {
+ newValues.add(value.copy());
+ }
+ return new JavaArray(innerType, newValues);
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ if (other instanceof VoidValue || other instanceof IJavaObject) { // cannot merge different types
+ other = new JavaArray();
+ }
+ JavaArray otherArray = (JavaArray) other;
+ if (this.innerType == null && otherArray.innerType != null) {
+ this.innerType = otherArray.innerType;
+ }
+ if (!(Objects.equals(this.innerType, otherArray.innerType))) {
+ this.values = null;
+ return;
+ }
+ assert Objects.equals(this.innerType, otherArray.innerType);
+ if (this.values == null || otherArray.values == null || this.values.size() != otherArray.values.size()) {
+ this.values = null;
+ } else {
+ for (int i = 0; i < this.values.size(); i++) {
+ this.values.get(i).merge(otherArray.values.get(i));
+ }
+ }
+ }
+
+ @Override
+ public void setToUnknown() {
+ values = null;
+ }
+
+ @Override
+ public void setInitialValue() {
+ values = null;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaLengthArray.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaLengthArray.java
new file mode 100644
index 0000000000..deb5ad7386
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/arrays/JavaLengthArray.java
@@ -0,0 +1,205 @@
+package de.jplag.java_cpg.ai.variables.values.arrays;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.jetbrains.annotations.NotNull;
+
+import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration;
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.JavaObject;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.VoidValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue;
+import de.jplag.java_cpg.ai.variables.values.string.StringValue;
+
+/**
+ * Represents a Java array by its length and inner type.
+ * @author ujiqk
+ */
+public class JavaLengthArray extends JavaObject implements IJavaArray {
+
+ private final Type innerType;
+ private INumberValue length;
+
+ /**
+ * Creates a new JavaLengthArray with an unknown inner type and length.
+ */
+ public JavaLengthArray() {
+ super(Type.ARRAY);
+ this.innerType = Type.UNKNOWN;
+ }
+
+ /**
+ * Creates a new JavaLengthArray with the given inner type and unknown length.
+ * @param innerType The inner type of the array.
+ */
+ public JavaLengthArray(@NotNull Type innerType) {
+ super(Type.ARRAY);
+ this.innerType = innerType;
+ }
+
+ /**
+ * Creates a new JavaLengthArray with the given inner type and length.
+ * @param innerType The inner type of the array.
+ * @param length The length of the array.
+ */
+ public JavaLengthArray(Type innerType, @NotNull INumberValue length) {
+ super(Type.ARRAY);
+ this.innerType = innerType;
+ this.length = length;
+ }
+
+ /**
+ * Creates a new JavaLengthArray with the inner type and length derived from the given values.
+ * @param values The values to derive the inner type and length from.
+ */
+ public JavaLengthArray(@NotNull List values) {
+ super(Type.ARRAY);
+ this.innerType = values.getFirst().getType();
+ this.length = (INumberValue) Value.valueFactory(values.size());
+ }
+
+ @Override
+ public IValue arrayAccess(INumberValue index) {
+ // if no information, return an unknown value of the inner type
+ if (innerType == null || innerType == Type.UNKNOWN) {
+ return new VoidValue();
+ }
+ return switch (innerType) {
+ case INT -> Value.valueFactory(Type.INT);
+ case BOOLEAN -> new BooleanValue();
+ case STRING -> Value.valueFactory(Type.STRING);
+ case OBJECT -> new JavaObject();
+ case ARRAY, LIST -> new JavaArray();
+ case FLOAT -> Value.valueFactory(Type.FLOAT);
+ case CHAR -> Value.valueFactory(Type.CHAR);
+ default -> throw new UnsupportedOperationException("Array of type " + innerType + " not supported");
+ };
+ }
+
+ @Override
+ public void arrayAssign(INumberValue index, IValue value) {
+ // do nothing
+ }
+
+ @Override
+ public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) {
+ switch (methodName) {
+ case "toString" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ return new StringValue();
+ }
+ case "add" -> {
+ if (paramVars.size() == 1) {
+ length.unaryOperation("++");
+ } else if (paramVars.size() == 2) { // index, element
+ // do nothing, length stays the same
+ } else {
+ throw new UnsupportedOperationException("add with " + paramVars.size() + " parameters is not supported");
+ }
+ return new VoidValue();
+ }
+ case "stream" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ return this;
+ }
+ case "size" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ return this.length;
+ }
+ case "map" -> {
+ // ToDo
+ return this;
+ }
+ case "max" -> {
+ return arrayAccess((INumberValue) Value.valueFactory(1));
+ }
+ case "indexOf", "lastIndexOf" -> {
+ assert paramVars.size() == 1;
+ return Value.valueFactory(Type.INT);
+ }
+ case "remove" -> {
+ assert paramVars.size() == 1;
+ // information lost
+ length.setToUnknown();
+ return new VoidValue();
+ }
+ case "get", "elementAt" -> {
+ assert paramVars.size() == 1;
+ assert paramVars.getFirst() instanceof INumberValue;
+ return arrayAccess((INumberValue) paramVars.getFirst());
+ }
+ case "contains" -> {
+ assert paramVars.size() == 1;
+ return Value.valueFactory(Type.BOOLEAN);
+ }
+ case "getLast", "findFirst", "findAny" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ return arrayAccess((INumberValue) Value.valueFactory(1));
+ }
+ case "removeLast", "removeFirst" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ this.length.unaryOperation("--");
+ return new VoidValue();
+ }
+ case "addLast" -> {
+ assert paramVars.size() == 1;
+ this.length.unaryOperation("++");
+ return new VoidValue();
+ }
+ case "isEmpty" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ if (length.getInformation()) {
+ return Value.valueFactory(length.getValue() == 0);
+ }
+ return Value.valueFactory(Type.BOOLEAN);
+ }
+ default -> throw new UnsupportedOperationException(methodName);
+ }
+ }
+
+ @Override
+ public IValue accessField(@NotNull String fieldName) {
+ switch (fieldName) {
+ case "length" -> {
+ return this.length;
+ }
+ default -> throw new UnsupportedOperationException("Field " + fieldName + " is not supported for JavaArray");
+ }
+ }
+
+ @NotNull
+ @Override
+ public JavaObject copy() {
+ if (this.length == null) {
+ return new JavaLengthArray(this.innerType);
+ }
+ INumberValue newLength = (INumberValue) this.length.copy();
+ return new JavaLengthArray(this.innerType, newLength);
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ assert other instanceof JavaLengthArray;
+ assert Objects.equals(this.innerType, ((JavaLengthArray) other).innerType);
+ if (this.length == null || ((JavaLengthArray) other).length == null) {
+ this.length = null;
+ } else {
+ this.length.merge(((JavaLengthArray) other).length);
+ }
+ }
+
+ @Override
+ public void setToUnknown() {
+ length = Value.getNewIntValue();
+ }
+
+ @Override
+ public void setInitialValue() {
+ length = null;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharSetValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharSetValue.java
new file mode 100644
index 0000000000..0c4f08a662
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharSetValue.java
@@ -0,0 +1,96 @@
+package de.jplag.java_cpg.ai.variables.values.chars;
+
+import java.util.Set;
+
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.Value;
+
+/**
+ * Char value representation that can hold a set of possible char values or be unknown.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class CharSetValue extends Value implements ICharValue {
+
+ private Set maybeContained;
+ private boolean information;
+
+ /**
+ * an unknown char value.
+ */
+ public CharSetValue() {
+ super(Type.CHAR);
+ this.information = false;
+ }
+
+ /**
+ * an exactly known char value.
+ * @param character the known character
+ */
+ public CharSetValue(char character) {
+ super(Type.CHAR);
+ this.information = true;
+ this.maybeContained = Set.of(character);
+ }
+
+ /**
+ * a char value that can be one of the given characters.
+ * @param characters the possible characters
+ */
+ public CharSetValue(@NotNull Set characters) {
+ super(Type.CHAR);
+ this.information = true;
+ this.maybeContained = characters;
+ }
+
+ /**
+ * Copy constructor.
+ */
+ private CharSetValue(Set maybeContained, boolean information) {
+ super(Type.CHAR);
+ this.maybeContained = maybeContained;
+ this.information = information;
+ }
+
+ @NotNull
+ @Override
+ public Value copy() {
+ return new CharSetValue(this.maybeContained, this.information);
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ CharSetValue otherCharValue = (CharSetValue) other;
+ if (!otherCharValue.information) {
+ this.information = false;
+ } else if (this.information) {
+ this.maybeContained.addAll(otherCharValue.maybeContained);
+ }
+ }
+
+ @Override
+ public void setToUnknown() {
+ this.information = false;
+ }
+
+ @Override
+ public void setInitialValue() {
+ this.information = true;
+ this.maybeContained = Set.of(DEFAULT_VALUE);
+ }
+
+ @Override
+ public boolean getInformation() {
+ return this.information && this.maybeContained.size() == 1;
+ }
+
+ @Override
+ public double getValue() {
+ assert this.information;
+ return this.maybeContained.iterator().next();
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharValue.java
new file mode 100644
index 0000000000..73b3ffbc25
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/CharValue.java
@@ -0,0 +1,173 @@
+package de.jplag.java_cpg.ai.variables.values.chars;
+
+import java.util.Set;
+
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue;
+import de.jplag.java_cpg.ai.variables.values.string.IStringValue;
+
+/**
+ * Char value representation that can hold a single char value or be unknown.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class CharValue extends Value implements ICharValue {
+
+ private char value;
+ private boolean information;
+
+ /**
+ * an unknown char value.
+ */
+ public CharValue() {
+ super(Type.CHAR);
+ this.information = false;
+ }
+
+ /**
+ * an exactly known char value.
+ * @param value the known character
+ */
+ public CharValue(char value) {
+ super(Type.CHAR);
+ this.information = true;
+ this.value = value;
+ }
+
+ /**
+ * a char value that can be one of the given characters.
+ * @param values the possible characters
+ */
+ public CharValue(@NotNull Set values) {
+ super(Type.CHAR);
+ if (values.size() == 1) {
+ this.information = true;
+ this.value = values.iterator().next();
+ } else {
+ this.information = false;
+ }
+ }
+
+ /**
+ * Copy constructor.
+ */
+ private CharValue(char value, boolean information) {
+ super(Type.CHAR);
+ this.information = information;
+ this.value = value;
+ }
+
+ @Override
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ switch (operator) {
+ case "==" -> {
+ CharValue otherCharValue = (CharValue) other;
+ if (this.information && otherCharValue.information) {
+ return new BooleanValue(this.value == otherCharValue.value);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "!=" -> {
+ CharValue otherCharValue = (CharValue) other;
+ if (this.information && otherCharValue.information) {
+ return new BooleanValue(this.value != otherCharValue.value);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "+" -> {
+ if (other instanceof CharValue otherCharValue) {
+ if (this.information && otherCharValue.information) {
+ return new CharValue((char) (this.value + otherCharValue.value));
+ } else {
+ return new CharValue();
+ }
+ }
+ IStringValue otherStringValue = (IStringValue) other;
+ if (this.information && otherStringValue.getInformation()) {
+ return Value.valueFactory(this.value + otherStringValue.getValue());
+ } else {
+ return Value.valueFactory(Type.STRING);
+ }
+ }
+ case "-" -> {
+ CharValue otherCharValue = (CharValue) other;
+ if (this.information && otherCharValue.information) {
+ return new CharValue((char) (this.value - otherCharValue.value));
+ } else {
+ return new CharValue();
+ }
+ }
+ default -> throw new IllegalArgumentException("Unknown binary operator: " + operator + " for " + getType());
+ }
+ }
+
+ @Override
+ public IValue unaryOperation(@NotNull String operator) {
+ switch (operator) {
+ default -> throw new IllegalArgumentException("Unary operation " + operator + " not supported for " + getType());
+ }
+ }
+
+ @NotNull
+ @Override
+ public Value copy() {
+ return new CharValue(this.value, this.information);
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ switch (other) {
+ case CharValue otherCharValue -> {
+ if (!otherCharValue.information) {
+ this.information = false;
+ } else if (this.information) {
+ if (this.value != otherCharValue.value) {
+ this.information = false;
+ }
+ }
+ }
+ case INumberValue otherNumberValue -> {
+ if (!otherNumberValue.getInformation()) {
+ this.information = false;
+ } else if (this.information) {
+ if (this.value != (char) otherNumberValue.getValue()) {
+ this.information = false;
+ }
+ }
+ }
+ default -> {
+ throw new IllegalArgumentException("Cannot merge " + getType() + " with " + other.getType());
+ }
+ }
+ }
+
+ @Override
+ public void setToUnknown() {
+ this.information = false;
+ }
+
+ @Override
+ public void setInitialValue() {
+ this.information = true;
+ this.value = DEFAULT_VALUE;
+ }
+
+ @Override
+ public boolean getInformation() {
+ return this.information;
+ }
+
+ @Override
+ public double getValue() {
+ assert getInformation();
+ return this.value;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/ICharValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/ICharValue.java
new file mode 100644
index 0000000000..00bbc6dc5a
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/chars/ICharValue.java
@@ -0,0 +1,27 @@
+package de.jplag.java_cpg.ai.variables.values.chars;
+
+import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue;
+
+/**
+ * Interface for all java char value representations.
+ * @author ujiqk
+ * @version 1.0
+ */
+public interface ICharValue extends INumberValue {
+
+ /**
+ * The initial default value for char values in java.
+ */
+ char DEFAULT_VALUE = '\u0000';
+
+ /**
+ * @return if exact information about the char value is known.
+ */
+ boolean getInformation();
+
+ /**
+ * @return the exact char value, if known.
+ */
+ double getValue();
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatSetValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatSetValue.java
new file mode 100644
index 0000000000..b208d85d6e
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatSetValue.java
@@ -0,0 +1,99 @@
+package de.jplag.java_cpg.ai.variables.values.numbers;
+
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.TestOnly;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.numbers.helpers.DoubleInterval;
+
+/**
+ * Float value represented as a set of intervals.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class FloatSetValue extends NumberSetValue {
+
+ /**
+ * Default constructor c Float value represented as a set of intervals with no information.
+ */
+ public FloatSetValue() {
+ super(Type.FLOAT);
+ values.add(new DoubleInterval());
+ }
+
+ private FloatSetValue(TreeSet values) {
+ super(Type.FLOAT, values);
+ }
+
+ /**
+ * Constructor for FloatSetValue that is known to be a single number.
+ * @param number the single float number
+ */
+ public FloatSetValue(double number) {
+ super(Type.FLOAT);
+ values.add(new DoubleInterval(number));
+ }
+
+ /**
+ * Constructor for FloatSetValue that is known to be one of the possible numbers.
+ * @param possibleNumbers the possible float numbers
+ */
+ public FloatSetValue(@NotNull Set possibleNumbers) {
+ super(Type.INT);
+ values = new TreeSet<>();
+ // ToDo: slice into intervals
+ values.add(new DoubleInterval());
+ }
+
+ /**
+ * Constructor for FloatSetValue that is known to be within a certain range.
+ * @param lowerBound the lower bound of the range
+ * @param upperBound the upper bound of the range
+ */
+ public FloatSetValue(double lowerBound, double upperBound) {
+ super(Type.INT);
+ values.add(new DoubleInterval(lowerBound, upperBound));
+ }
+
+ @Override
+ protected DoubleInterval createFullInterval() {
+ return new DoubleInterval();
+ }
+
+ @Override
+ protected DoubleInterval createInterval(Double lowerBound, Double upperBound) {
+ return new DoubleInterval(lowerBound, upperBound);
+ }
+
+ @Override
+ protected NumberSetValue createInstance(TreeSet values) {
+ return new FloatSetValue(values);
+ }
+
+ @NotNull
+ @Override
+ public Value copy() {
+ return new FloatSetValue(new TreeSet<>(values));
+ }
+
+ @Override
+ public void setInitialValue() {
+ values = new TreeSet<>();
+ values.add(createInterval(0d, 0d));
+ }
+
+ /**
+ * Use for testing purposes only.
+ * @return the set of intervals representing the float value.
+ */
+ @TestOnly
+ public SortedSet getIntervals() {
+ return values;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatValue.java
new file mode 100644
index 0000000000..6f9a6e728c
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/FloatValue.java
@@ -0,0 +1,243 @@
+package de.jplag.java_cpg.ai.variables.values.numbers;
+
+import java.util.Set;
+
+import org.checkerframework.dataflow.qual.Impure;
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.VoidValue;
+
+/**
+ * Represents a floating point value with optional exact information.
+ */
+public class FloatValue extends Value implements INumberValue {
+
+ private double value;
+ private boolean information; // whether exact information is available
+
+ /**
+ * a IntValue with no information.
+ */
+ public FloatValue() {
+ super(Type.FLOAT);
+ information = false;
+ }
+
+ /**
+ * Constructor for FloatValue with exact information.
+ * @param value the float value.
+ */
+ public FloatValue(double value) {
+ super(Type.FLOAT);
+ this.value = value;
+ information = true;
+ }
+
+ /**
+ * Constructor for FloatValue with a range.
+ * @param lowerBound the lower bound of the range.
+ * @param upperBound the upper bound of the range.
+ */
+ public FloatValue(double lowerBound, double upperBound) {
+ super(Type.FLOAT);
+ assert lowerBound <= upperBound;
+ if (lowerBound == upperBound) {
+ this.value = lowerBound;
+ this.information = true;
+ } else {
+ this.information = false;
+ }
+ }
+
+ /**
+ * Constructor for FloatValue with a set of possible values.
+ * @param values the set of possible float values.
+ */
+ public FloatValue(@NotNull Set values) {
+ super(Type.FLOAT);
+ if (values.size() == 1) {
+ this.value = values.iterator().next();
+ this.information = true;
+ } else {
+ this.information = false;
+ }
+ }
+
+ private FloatValue(double value, boolean information) {
+ super(Type.FLOAT);
+ this.value = value;
+ this.information = information;
+ }
+
+ /**
+ * @return whether exact information is available.
+ */
+ public boolean getInformation() {
+ return information;
+ }
+
+ /**
+ * @return the value. Only call if information is true.
+ */
+ public double getValue() {
+ assert information;
+ return value;
+ }
+
+ @Override
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ if (other instanceof VoidValue) {
+ return new VoidValue();
+ }
+ assert other instanceof INumberValue;
+ switch (operator) {
+ case "+" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new FloatValue(this.value + ((INumberValue) other).getValue());
+ } else {
+ return new FloatValue();
+ }
+ }
+ case "<" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new BooleanValue(this.value < ((INumberValue) other).getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case ">" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new BooleanValue(this.value > ((INumberValue) other).getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "-" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new FloatValue(this.value - ((INumberValue) other).getValue());
+ } else {
+ return new FloatValue();
+ }
+ }
+ case "==" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new BooleanValue(this.value == ((INumberValue) other).getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "!=" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new BooleanValue(this.value != ((INumberValue) other).getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "*" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new FloatValue(this.value * ((INumberValue) other).getValue());
+ } else {
+ return new FloatValue();
+ }
+ }
+ case "/" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new FloatValue(this.value / ((INumberValue) other).getValue());
+ } else {
+ return new FloatValue();
+ }
+ }
+ case "pow" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new FloatValue(Math.pow(this.value, ((INumberValue) other).getValue()));
+ } else {
+ return new FloatValue();
+ }
+ }
+ case "<=" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new BooleanValue(this.value <= ((INumberValue) other).getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case ">=" -> {
+ if (information && ((INumberValue) other).getInformation()) {
+ return new BooleanValue(this.value >= ((INumberValue) other).getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ default -> throw new UnsupportedOperationException(
+ "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType());
+ }
+ }
+
+ @Override
+ @Impure
+ public IValue unaryOperation(@NotNull String operator) {
+ switch (operator) {
+ case "++" -> {
+ if (information) {
+ this.value += 1;
+ return new FloatValue(this.value);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "-" -> {
+ if (information) {
+ this.value = -this.value;
+ return new FloatValue(this.value);
+ } else {
+ return new FloatValue();
+ }
+ }
+ case "sqrt" -> {
+ if (information) {
+ return new FloatValue(Math.sqrt(this.value));
+ } else {
+ return new FloatValue();
+ }
+ }
+ default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType());
+ }
+ }
+
+ @NotNull
+ @Override
+ public Value copy() {
+ return new FloatValue(value, information);
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ if (other instanceof VoidValue) {
+ this.information = false;
+ return;
+ }
+ assert other instanceof FloatValue;
+ FloatValue otherFloat = (FloatValue) other;
+ if (this.information && otherFloat.information && this.value == otherFloat.value) {
+ // keep information
+ } else {
+ this.information = false;
+ }
+ }
+
+ @Override
+ public void setToUnknown() {
+ this.information = false;
+ }
+
+ @Override
+ public void setInitialValue() {
+ value = 0.0;
+ information = true;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/INumberValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/INumberValue.java
new file mode 100644
index 0000000000..c0a767edf6
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/INumberValue.java
@@ -0,0 +1,22 @@
+package de.jplag.java_cpg.ai.variables.values.numbers;
+
+import de.jplag.java_cpg.ai.variables.values.IValue;
+
+/**
+ * Interface for number values.
+ * @author ujiqk
+ * @version 1.0
+ */
+public interface INumberValue extends IValue {
+
+ /**
+ * @return if exact information is available.
+ */
+ boolean getInformation();
+
+ /**
+ * @return the exact value. Only valid if getInformation() returns true.
+ */
+ double getValue();
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntIntervalValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntIntervalValue.java
new file mode 100644
index 0000000000..c6a9b9b662
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntIntervalValue.java
@@ -0,0 +1,171 @@
+package de.jplag.java_cpg.ai.variables.values.numbers;
+
+import java.util.Set;
+
+import org.checkerframework.dataflow.qual.Impure;
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.VoidValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.helpers.IntInterval;
+
+/**
+ * Represents integer values as intervals.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class IntIntervalValue extends Value implements INumberValue {
+
+ private final IntInterval interval;
+
+ /**
+ * a IntIntervalValue with no information.
+ */
+ public IntIntervalValue() {
+ super(Type.INT);
+ interval = new IntInterval();
+ }
+
+ /**
+ * Constructor for IntIntervalValue which value is between given bounds.
+ * @param lowerBound the lower bound.
+ * @param upperBound the upper bound.
+ */
+ public IntIntervalValue(int lowerBound, int upperBound) {
+ super(Type.INT);
+ interval = new IntInterval(lowerBound, upperBound);
+ }
+
+ /**
+ * Constructor for IntIntervalValue with exact information.
+ * @param number the integer value.
+ */
+ public IntIntervalValue(int number) {
+ super(Type.INT);
+ interval = new IntInterval(number);
+ }
+
+ /**
+ * Constructor for IntIntervalValue which value is a set of possible values.
+ * @param possibleValues the set of possible integer values.
+ */
+ public IntIntervalValue(@NotNull Set possibleValues) {
+ super(Type.INT);
+ java.util.List values = possibleValues.stream().toList();
+ interval = new IntInterval(values.getFirst(), values.getLast());
+ }
+
+ private IntIntervalValue(IntInterval interval) {
+ super(Type.INT);
+ this.interval = interval;
+ }
+
+ @Override
+ public boolean getInformation() {
+ return interval.getInformation();
+ }
+
+ @Override
+ public double getValue() {
+ return interval.getValue();
+ }
+
+ @Override
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ if (!(other instanceof INumberValue)) {
+ other = new IntIntervalValue();
+ }
+ IntIntervalValue otherValue = (IntIntervalValue) other; // ToDo: what if other float?
+ switch (operator) {
+ case "+" -> {
+ IntInterval newInterval = this.interval.copy().plus(otherValue.interval);
+ return new IntIntervalValue(newInterval);
+ }
+ case "-" -> {
+ IntInterval newInterval = this.interval.copy().minus(otherValue.interval);
+ return new IntIntervalValue(newInterval);
+ }
+ case "<" -> {
+ return this.interval.copy().smaller(otherValue.interval);
+ }
+ case ">" -> {
+ return this.interval.copy().bigger(otherValue.interval);
+ }
+ case "<=" -> {
+ return this.interval.copy().smallerEqual(otherValue.interval);
+ }
+ case ">=" -> {
+ return this.interval.copy().biggerEqual(otherValue.interval);
+ }
+ case "==" -> {
+ return this.interval.copy().equal(otherValue.interval);
+ }
+ case "!=" -> {
+ return this.interval.copy().notEqual(otherValue.interval);
+ }
+ case "*" -> {
+ IntInterval newInterval = this.interval.copy().times(otherValue.interval);
+ return new IntIntervalValue(newInterval);
+ }
+ case "/" -> {
+ IntInterval newInterval = this.interval.copy().divided(otherValue.interval);
+ return new IntIntervalValue(newInterval);
+ }
+ default -> throw new UnsupportedOperationException(
+ "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType());
+ }
+ }
+
+ @Override
+ @Impure
+ public IValue unaryOperation(@NotNull String operator) {
+ switch (operator) {
+ case "++" -> {
+ this.interval.plusPlus();
+ return this.copy();
+ }
+ case "--" -> {
+ this.interval.minusMinus();
+ return this.copy();
+ }
+ case "-" -> {
+ IntInterval newInterval = this.interval.copy().unaryMinus();
+ return new IntIntervalValue(newInterval);
+ }
+ case "abs" -> {
+ IntInterval newInterval = this.interval.copy().abs();
+ return new IntIntervalValue(newInterval);
+ }
+ default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType());
+ }
+ }
+
+ @NotNull
+ @Override
+ public IValue copy() {
+ return new IntIntervalValue(interval.copy());
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ if (other instanceof VoidValue) {
+ other = new IntIntervalValue();
+ }
+ assert other instanceof IntIntervalValue;
+ this.interval.merge(((IntIntervalValue) other).interval);
+ }
+
+ @Override
+ public void setToUnknown() {
+ interval.setToUnknown();
+ }
+
+ @Override
+ public void setInitialValue() {
+ interval.setUpperBound(0);
+ interval.setLowerBound(0);
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntSetValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntSetValue.java
new file mode 100644
index 0000000000..fe2446eb10
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntSetValue.java
@@ -0,0 +1,99 @@
+package de.jplag.java_cpg.ai.variables.values.numbers;
+
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.TestOnly;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.numbers.helpers.IntInterval;
+
+/**
+ * Integer value represented as a set of intervals.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class IntSetValue extends NumberSetValue {
+
+ /**
+ * Integer value represented as a set of intervals with no information.
+ */
+ public IntSetValue() {
+ super(Type.INT);
+ values.add(new IntInterval());
+ }
+
+ private IntSetValue(TreeSet values) {
+ super(Type.INT, values);
+ }
+
+ /**
+ * Constructor for IntSetValue that is known to be a single number.
+ * @param number the single integer number
+ */
+ public IntSetValue(int number) {
+ super(Type.INT);
+ values.add(new IntInterval(number));
+ }
+
+ /**
+ * Constructor for IntSetValue that is known to be one of the possible numbers.
+ * @param possibleNumbers the possible integer numbers
+ */
+ public IntSetValue(@NotNull Set possibleNumbers) {
+ super(Type.INT);
+ values = new TreeSet<>();
+ // ToDo: slice into intervals
+ values.add(new IntInterval());
+ }
+
+ /**
+ * Constructor for IntSetValue that is known to be within a certain range.
+ * @param lowerBound the lower bound of the range
+ * @param upperBound the upper bound of the range
+ */
+ public IntSetValue(int lowerBound, int upperBound) {
+ super(Type.INT);
+ values.add(new IntInterval(lowerBound, upperBound));
+ }
+
+ @Override
+ protected IntInterval createFullInterval() {
+ return new IntInterval();
+ }
+
+ @Override
+ protected IntInterval createInterval(Integer lowerBound, Integer upperBound) {
+ return new IntInterval(lowerBound, upperBound);
+ }
+
+ @Override
+ protected NumberSetValue createInstance(TreeSet values) {
+ return new IntSetValue(values);
+ }
+
+ @NotNull
+ @Override
+ public Value copy() {
+ return new IntSetValue(new TreeSet<>(values));
+ }
+
+ @Override
+ public void setInitialValue() {
+ values = new TreeSet<>();
+ values.add(createInterval(0, 0));
+ }
+
+ /**
+ * Use for testing only!
+ * @return the set of intervals representing the integer value.
+ */
+ @TestOnly
+ public SortedSet getIntervals() {
+ return values;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntValue.java
new file mode 100644
index 0000000000..29abcd7246
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/IntValue.java
@@ -0,0 +1,316 @@
+package de.jplag.java_cpg.ai.variables.values.numbers;
+
+import java.util.Set;
+
+import org.checkerframework.dataflow.qual.Impure;
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.JavaObject;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.VoidValue;
+import de.jplag.java_cpg.ai.variables.values.chars.CharValue;
+
+/**
+ * Represents an integer value with optional exact information.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class IntValue extends Value implements INumberValue {
+
+ private int value;
+ private boolean information; // whether exact information is available
+
+ /**
+ * a IntValue with no information.
+ */
+ public IntValue() {
+ super(Type.INT);
+ information = false;
+ }
+
+ /**
+ * Constructor for IntValue with exact information.
+ * @param value the integer value.
+ */
+ public IntValue(int value) {
+ super(Type.INT);
+ this.value = value;
+ information = true;
+ }
+
+ /**
+ * Constructor for IntValue with exact information from a double value.
+ * @param value the integer value as double.
+ */
+ public IntValue(double value) {
+ super(Type.INT);
+ this.value = (int) value;
+ information = true;
+ }
+
+ /**
+ * Constructor for IntValue with a set of possible values.
+ * @param possibleValues the set of possible integer values.
+ */
+ public IntValue(@NotNull Set possibleValues) {
+ super(Type.INT);
+ if (possibleValues.size() == 1) {
+ this.value = possibleValues.iterator().next();
+ this.information = true;
+ } else {
+ this.information = false;
+ }
+ }
+
+ /**
+ * Constructor for IntValue with a range.
+ * @param lowerBound the lower bound of the range.
+ * @param upperBound the upper bound of the range.
+ */
+ public IntValue(int lowerBound, int upperBound) {
+ super(Type.INT);
+ assert lowerBound <= upperBound;
+ if (lowerBound == upperBound) {
+ this.value = lowerBound;
+ this.information = true;
+ } else {
+ this.information = false;
+ }
+ }
+
+ private IntValue(int value, boolean information) {
+ super(Type.INT);
+ this.value = value;
+ this.information = information;
+ }
+
+ /**
+ * @return whether exact information is available
+ */
+ public boolean getInformation() {
+ return information;
+ }
+
+ /**
+ * @return the exact value. Only valid if getInformation() returns true.
+ */
+ public double getValue() {
+ assert information;
+ return value;
+ }
+
+ @Override
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ if (!(other instanceof INumberValue)) {
+ other = new IntValue();
+ }
+ INumberValue otherNumber = (INumberValue) other;
+ switch (operator) {
+ case "+" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(this.value + otherNumber.getValue());
+ } else {
+ return new IntValue();
+ }
+ }
+ case "<" -> {
+ if (information && otherNumber.getInformation()) {
+ return new BooleanValue(this.value < otherNumber.getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case ">" -> {
+ if (information && otherNumber.getInformation()) {
+ return new BooleanValue(this.value > otherNumber.getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "-" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(this.value - otherNumber.getValue());
+ } else {
+ return new IntValue();
+ }
+ }
+ case "!=" -> {
+ if (information && otherNumber.getInformation()) {
+ return new BooleanValue(this.value != otherNumber.getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "==" -> {
+ if (information && otherNumber.getInformation()) {
+ return new BooleanValue(this.value == otherNumber.getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "*" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(this.value * otherNumber.getValue());
+ } else {
+ return new IntValue();
+ }
+ }
+ case "/" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(this.value / otherNumber.getValue());
+ } else {
+ return new IntValue();
+ }
+ }
+ case "<=" -> {
+ if (information && otherNumber.getInformation()) {
+ return new BooleanValue(this.value <= otherNumber.getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case ">=" -> {
+ if (information && otherNumber.getInformation()) {
+ return new BooleanValue(this.value >= otherNumber.getValue());
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "max" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(Math.max(this.value, otherNumber.getValue()));
+ } else {
+ return new IntValue();
+ }
+ }
+ case "min" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(Math.min(this.value, otherNumber.getValue()));
+ } else {
+ return new IntValue();
+ }
+ }
+ case "%" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(this.value % otherNumber.getValue());
+ } else {
+ return new IntValue();
+ }
+ }
+ case ">>" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(this.value >> (int) otherNumber.getValue());
+ } else {
+ return new IntValue();
+ }
+ }
+ case "<<" -> {
+ if (information && otherNumber.getInformation()) {
+ return new IntValue(this.value << (int) otherNumber.getValue());
+ } else {
+ return new IntValue();
+ }
+ }
+ default -> throw new UnsupportedOperationException(
+ "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType());
+ }
+ }
+
+ @Override
+ @Impure
+ public IValue unaryOperation(@NotNull String operator) {
+ switch (operator) {
+ case "++" -> {
+ if (information) {
+ this.value += 1;
+ return new IntValue(this.value);
+ } else {
+ return new IntValue();
+ }
+ }
+ case "--" -> {
+ if (information) {
+ this.value -= 1;
+ return new IntValue(this.value);
+ } else {
+ return new IntValue();
+ }
+ }
+ case "-" -> {
+ if (information) {
+ this.value = -this.value;
+ return new IntValue(this.value);
+ } else {
+ return new IntValue();
+ }
+ }
+ case "abs" -> {
+ if (information) {
+ return new IntValue(Math.abs(this.value));
+ } else {
+ return new IntValue();
+ }
+ }
+ case "sin" -> {
+ if (information) {
+ return Value.valueFactory(Math.sin(this.value));
+ } else {
+ return Value.valueFactory(Type.FLOAT);
+ }
+ }
+ default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType());
+ }
+ }
+
+ @NotNull
+ @Override
+ public Value copy() {
+ return new IntValue(value, information);
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ if (other instanceof VoidValue) {
+ this.information = false;
+ return;
+ }
+ if (other instanceof JavaObject javaObject) { // could be an Integer object
+ if (javaObject.accessField("value") instanceof IntValue intValue) {
+ other = intValue;
+ } else {
+ this.information = false;
+ return;
+ }
+ }
+ if (other instanceof CharValue charValue) { // cannot merge different types
+ if (information && charValue.getInformation() && this.value == charValue.getValue()) {
+ // keep information
+ } else {
+ this.information = false;
+ }
+ return;
+ }
+ assert other instanceof INumberValue;
+ INumberValue otherInt = (INumberValue) other;
+ if (this.information && otherInt.getInformation() && this.value == otherInt.getValue()) {
+ // keep information
+ } else {
+ this.information = false;
+ }
+ }
+
+ @Override
+ public void setToUnknown() {
+ this.information = false;
+ }
+
+ @Override
+ public void setInitialValue() {
+ value = 0;
+ information = true;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/NumberSetValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/NumberSetValue.java
new file mode 100644
index 0000000000..ffe55f52f5
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/NumberSetValue.java
@@ -0,0 +1,282 @@
+package de.jplag.java_cpg.ai.variables.values.numbers;
+
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import org.checkerframework.dataflow.qual.Impure;
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.numbers.helpers.Interval;
+
+/**
+ * Abstract base class for numeric values represented as sets of intervals.
+ * @param The type of number (Integer, Double, etc.)
+ * @param The interval type for this number
+ */
+public abstract class NumberSetValue, I extends Interval> extends Value implements INumberValue {
+
+ protected TreeSet values;
+
+ protected NumberSetValue(Type type) {
+ super(type);
+ values = new TreeSet<>();
+ }
+
+ protected NumberSetValue(Type type, TreeSet values) {
+ super(type);
+ this.values = values;
+ }
+
+ protected abstract I createFullInterval();
+
+ protected abstract I createInterval(T lowerBound, T upperBound);
+
+ protected abstract NumberSetValue createInstance(TreeSet values);
+
+ @Override
+ public boolean getInformation() {
+ return values.size() == 1 && values.getFirst().getInformation();
+ }
+
+ @Override
+ public double getValue() {
+ assert getInformation();
+ return values.getFirst().getValue().doubleValue();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ if (!(other instanceof NumberSetValue)) {
+ other = createInstance(new TreeSet<>());
+ }
+ NumberSetValue otherValue = (NumberSetValue) other;
+ switch (operator) {
+ case "+" -> {
+ TreeSet newValues = new TreeSet<>();
+ for (I interval : otherValue.values) {
+ for (I value : values) {
+ newValues.add((I) interval.plus(value));
+ }
+ }
+ NumberSetValue newValue = createInstance(newValues);
+ newValue.mergeOverlappingIntervals();
+ return newValue;
+ }
+ case "-" -> {
+ TreeSet newValues = new TreeSet<>();
+ for (I interval : otherValue.values) {
+ for (I value : values) {
+ newValues.add((I) value.minus(interval));
+ }
+ }
+ NumberSetValue newValue = createInstance(newValues);
+ newValue.mergeOverlappingIntervals();
+ return newValue;
+ }
+ case "<" -> {
+ if (values.getLast().getUpperBound().compareTo(otherValue.values.getFirst().getLowerBound()) < 0) {
+ return new BooleanValue(true);
+ } else if (values.getFirst().getLowerBound().compareTo(otherValue.values.getLast().getUpperBound()) >= 0) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case ">" -> {
+ if (values.getFirst().getLowerBound().compareTo(otherValue.values.getLast().getUpperBound()) > 0) {
+ return new BooleanValue(true);
+ } else if (values.getLast().getUpperBound().compareTo(otherValue.values.getFirst().getLowerBound()) <= 0) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "<=" -> {
+ if (values.getLast().getUpperBound().compareTo(otherValue.values.getFirst().getLowerBound()) <= 0) {
+ return new BooleanValue(true);
+ } else if (values.getFirst().getLowerBound().compareTo(otherValue.values.getLast().getUpperBound()) > 0) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case ">=" -> {
+ if (values.getFirst().getLowerBound().compareTo(otherValue.values.getLast().getUpperBound()) >= 0) {
+ return new BooleanValue(true);
+ } else if (values.getLast().getUpperBound().compareTo(otherValue.values.getFirst().getLowerBound()) < 0) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+ case "==" -> {
+ if (values.size() != otherValue.values.size()) {
+ return new BooleanValue(false);
+ }
+ BooleanValue equal = null;
+ Iterator otherInterval = otherValue.values.iterator();
+ for (I interval : values) {
+ BooleanValue result = interval.equal(otherInterval.next());
+ if (!result.getInformation()) {
+ return new BooleanValue();
+ }
+ if (equal == null) {
+ equal = result;
+ } else if (equal.getValue() != result.getValue()) {
+ return new BooleanValue();
+ }
+ }
+ return equal;
+ }
+ case "!=" -> {
+ BooleanValue result = (BooleanValue) this.binaryOperation("==", other);
+ if (result.getInformation()) {
+ return new BooleanValue(!result.getValue());
+ }
+ return new BooleanValue();
+ }
+ case "*" -> {
+ TreeSet newValues = new TreeSet<>();
+ for (I interval : otherValue.values) {
+ for (I value : values) {
+ newValues.add((I) interval.times(value));
+ }
+ }
+ NumberSetValue newValue = createInstance(newValues);
+ newValue.mergeOverlappingIntervals();
+ return newValue;
+ }
+ case "/" -> {
+ TreeSet newValues = new TreeSet<>();
+ for (I interval : otherValue.values) {
+ for (I value : values) {
+ newValues.add((I) value.divided(interval));
+ }
+ }
+ NumberSetValue newValue = createInstance(newValues);
+ newValue.mergeOverlappingIntervals();
+ return newValue;
+ }
+ case "min" -> {
+ T upper = values.getLast().getUpperBound().compareTo(otherValue.values.getLast().getUpperBound()) < 0
+ ? values.getLast().getUpperBound()
+ : otherValue.values.getLast().getUpperBound();
+ // include all intervals in the result but all are capped at upper
+ TreeSet newValues = new TreeSet<>();
+ for (I interval : otherValue.values) {
+ T upperBound = interval.getUpperBound().compareTo(upper) < 0 ? interval.getUpperBound() : upper;
+ T lowerBound = interval.getLowerBound().compareTo(upperBound) < 0 ? interval.getLowerBound() : upperBound;
+ newValues.add(createInterval(lowerBound, upperBound));
+ }
+ for (I interval : values) {
+ T upperBound = interval.getUpperBound().compareTo(upper) < 0 ? interval.getUpperBound() : upper;
+ T lowerBound = interval.getLowerBound().compareTo(upperBound) < 0 ? interval.getLowerBound() : upperBound;
+ newValues.add(createInterval(lowerBound, upperBound));
+ }
+ NumberSetValue newValue = createInstance(newValues);
+ newValue.mergeOverlappingIntervals();
+ return newValue;
+ }
+ case "max" -> {
+ T lower = values.getFirst().getLowerBound().compareTo(otherValue.values.getFirst().getLowerBound()) > 0
+ ? values.getFirst().getLowerBound()
+ : otherValue.values.getFirst().getLowerBound();
+ // include all intervals in the result, but all are floored at lower
+ TreeSet newValues = new TreeSet<>();
+ try {
+ for (I interval : otherValue.values) {
+ T lowerBound = interval.getLowerBound().compareTo(lower) > 0 ? interval.getLowerBound() : lower;
+ T upperBound = interval.getUpperBound().compareTo(lowerBound) > 0 ? interval.getUpperBound() : lowerBound;
+ newValues.add(createInterval(lowerBound, upperBound));
+ }
+ for (I interval : values) {
+ T lowerBound = interval.getLowerBound().compareTo(lower) > 0 ? interval.getLowerBound() : lower;
+ T upperBound = interval.getUpperBound().compareTo(lowerBound) > 0 ? interval.getUpperBound() : lowerBound;
+ newValues.add(createInterval(lowerBound, upperBound));
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to create interval instance", e);
+ }
+ NumberSetValue newValue = createInstance(newValues);
+ newValue.mergeOverlappingIntervals();
+ return newValue;
+ }
+ default -> throw new UnsupportedOperationException(
+ "Binary operation " + operator + " not supported between " + getType() + " and " + other.getType());
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void merge(@NotNull IValue other) {
+ assert other.getClass().equals(this.getClass());
+ TreeSet otherValues = ((NumberSetValue) other).values;
+ this.values.addAll(otherValues);
+ mergeOverlappingIntervals();
+ }
+
+ @Override
+ public void setToUnknown() {
+ values = new TreeSet<>();
+ values.add(createFullInterval());
+ }
+
+ @Override
+ @Impure
+ @SuppressWarnings("unchecked")
+ public IValue unaryOperation(@NotNull String operator) {
+ switch (operator) {
+ case "++" -> {
+ values.forEach(Interval::plusPlus);
+ return this.copy();
+ }
+ case "--" -> {
+ values.forEach(Interval::minusMinus);
+ return this.copy();
+ }
+ case "-" -> {
+ TreeSet newValues = new TreeSet<>();
+ for (I interval : values) {
+ newValues.add((I) interval.unaryMinus());
+ }
+ return createInstance(newValues);
+ }
+ case "abs" -> {
+ TreeSet newValues = new TreeSet<>();
+ for (I interval : values) {
+ newValues.add((I) interval.abs());
+ }
+ NumberSetValue newValue = createInstance(newValues);
+ newValue.mergeOverlappingIntervals();
+ return newValue;
+ }
+ default -> throw new UnsupportedOperationException("Unary operation " + operator + " not supported for " + getType());
+ }
+ }
+
+ protected void mergeOverlappingIntervals() {
+ if (values.size() < 2) {
+ return;
+ }
+ TreeSet newValues = new TreeSet<>();
+ newValues.add(values.first());
+ values.remove(values.first());
+ for (I interval : values) {
+ I lastInterval = newValues.last();
+ if (lastInterval.getUpperBound().compareTo(interval.getLowerBound()) >= 0) {
+ T maxUpper = lastInterval.getUpperBound().compareTo(interval.getUpperBound()) > 0 ? lastInterval.getUpperBound()
+ : interval.getUpperBound();
+ lastInterval.setUpperBound(maxUpper);
+ } else {
+ newValues.add(interval);
+ }
+ }
+ this.values = newValues;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/DoubleInterval.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/DoubleInterval.java
new file mode 100644
index 0000000000..621bf66404
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/DoubleInterval.java
@@ -0,0 +1,244 @@
+package de.jplag.java_cpg.ai.variables.values.numbers.helpers;
+
+import org.checkerframework.dataflow.qual.Impure;
+import org.checkerframework.dataflow.qual.Pure;
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+
+/**
+ * Interval implementation for Double values.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class DoubleInterval extends Interval {
+
+ /**
+ * The maximum value for Double intervals.
+ */
+ public static final double MAX_VALUE = Double.MAX_VALUE;
+ /**
+ * The minimum value for Double intervals.
+ */
+ public static final double MIN_VALUE = -Double.MAX_VALUE;
+
+ /**
+ * Creates a new Double interval representing the whole range of Double values.
+ */
+ public DoubleInterval() {
+ this.lowerBound = MIN_VALUE;
+ this.upperBound = MAX_VALUE;
+ }
+
+ /**
+ * Creates a new Double interval representing a single number.
+ * @param number the number
+ */
+ public DoubleInterval(double number) {
+ this.lowerBound = number;
+ this.upperBound = number;
+ }
+
+ /**
+ * Creates a new Double interval with the given bounds.
+ * @param lowerBound the lower bound.
+ * @param upperBound the upper bound.
+ */
+ public DoubleInterval(double lowerBound, double upperBound) {
+ assert lowerBound <= upperBound;
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+ }
+
+ @Override
+ public boolean getInformation() {
+ return lowerBound.equals(upperBound);
+ }
+
+ @Override
+ public Double getValue() {
+ assert lowerBound.equals(upperBound);
+ return lowerBound;
+ }
+
+ @Override
+ public DoubleInterval copy() {
+ return new DoubleInterval(lowerBound, upperBound);
+ }
+
+ @Override
+ public void setToUnknown() {
+ this.lowerBound = MIN_VALUE;
+ this.upperBound = MAX_VALUE;
+ }
+
+ @Pure
+ @Override
+ public DoubleInterval plus(@NotNull Interval other) {
+ double lo = lowerBound + other.lowerBound;
+ double hi = upperBound + other.upperBound;
+ return new DoubleInterval(lo, hi);
+ }
+
+ @Pure
+ @Override
+ public DoubleInterval minus(@NotNull Interval other) {
+ double lo = lowerBound - other.upperBound;
+ double hi = upperBound - other.lowerBound;
+ return new DoubleInterval(lo, hi);
+ }
+
+ @Pure
+ @Override
+ public DoubleInterval times(@NotNull Interval other) {
+ double p1 = lowerBound * other.lowerBound;
+ double p2 = lowerBound * other.upperBound;
+ double p3 = upperBound * other.lowerBound;
+ double p4 = upperBound * other.upperBound;
+ double lo = Math.min(Math.min(p1, p2), Math.min(p3, p4));
+ double hi = Math.max(Math.max(p1, p2), Math.max(p3, p4));
+ return new DoubleInterval(lo, hi);
+ }
+
+ @Pure
+ @Override
+ public DoubleInterval divided(@NotNull Interval other) {
+ if (other.lowerBound <= 0 && other.upperBound >= 0) {
+ return new DoubleInterval(MIN_VALUE, MAX_VALUE);
+ }
+ double p1 = lowerBound / other.lowerBound;
+ double p2 = lowerBound / other.upperBound;
+ double p3 = upperBound / other.lowerBound;
+ double p4 = upperBound / other.upperBound;
+ double lo = Math.min(Math.min(p1, p2), Math.min(p3, p4));
+ double hi = Math.max(Math.max(p1, p2), Math.max(p3, p4));
+ return new DoubleInterval(lo, hi);
+ }
+
+ @Pure
+ @Override
+ public BooleanValue equal(@NotNull Interval other) {
+ if (lowerBound.equals(upperBound) && other.lowerBound.equals(other.upperBound) && lowerBound.equals(other.lowerBound)) {
+ return new BooleanValue(true);
+ } else if (upperBound < other.lowerBound || lowerBound > other.upperBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue notEqual(@NotNull Interval other) {
+ if (upperBound < other.lowerBound || lowerBound > other.upperBound) {
+ return new BooleanValue(true);
+ } else if (lowerBound.equals(upperBound) && other.lowerBound.equals(other.upperBound) && lowerBound.equals(other.lowerBound)) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue smaller(@NotNull Interval other) {
+ if (upperBound < other.lowerBound) {
+ return new BooleanValue(true);
+ } else if (lowerBound >= other.upperBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue smallerEqual(@NotNull Interval other) {
+ if (upperBound <= other.lowerBound) {
+ return new BooleanValue(true);
+ } else if (lowerBound > other.upperBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue bigger(@NotNull Interval other) {
+ if (lowerBound > other.upperBound) {
+ return new BooleanValue(true);
+ } else if (upperBound <= other.lowerBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue biggerEqual(@NotNull Interval other) {
+ if (lowerBound >= other.upperBound) {
+ return new BooleanValue(true);
+ } else if (upperBound < other.lowerBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Impure
+ @Override
+ public DoubleInterval plusPlus() {
+ lowerBound += 1.0;
+ upperBound += 1.0;
+ return this.copy();
+ }
+
+ @Impure
+ @Override
+ public DoubleInterval minusMinus() {
+ lowerBound -= 1.0;
+ upperBound -= 1.0;
+ return this.copy();
+ }
+
+ @Pure
+ @Override
+ public DoubleInterval unaryMinus() {
+ return new DoubleInterval(-upperBound, -lowerBound);
+ }
+
+ @Pure
+ @Override
+ public DoubleInterval abs() {
+ if (upperBound < 0) {
+ return new DoubleInterval(Math.abs(upperBound), Math.abs(lowerBound));
+ } else if (lowerBound >= 0) {
+ return new DoubleInterval(lowerBound, upperBound);
+ } else {
+ double max = Math.max(Math.abs(lowerBound), Math.abs(upperBound));
+ return new DoubleInterval(0, max);
+ }
+ }
+
+ @Impure
+ @Override
+ public void merge(@NotNull Interval other) {
+ double smallerLowerBound = Math.min(this.lowerBound, other.lowerBound);
+ double largerUpperBound = Math.max(this.upperBound, other.upperBound);
+ assert smallerLowerBound <= largerUpperBound;
+ this.lowerBound = smallerLowerBound;
+ this.upperBound = largerUpperBound;
+ }
+
+ @Override
+ public int compareTo(@NotNull Interval o) {
+ if (!this.lowerBound.equals(o.lowerBound)) {
+ return Double.compare(this.lowerBound, o.lowerBound);
+ } else {
+ return Double.compare(this.upperBound, o.upperBound);
+ }
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/IntInterval.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/IntInterval.java
new file mode 100644
index 0000000000..9c38628c1a
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/IntInterval.java
@@ -0,0 +1,265 @@
+package de.jplag.java_cpg.ai.variables.values.numbers.helpers;
+
+import org.checkerframework.dataflow.qual.Impure;
+import org.checkerframework.dataflow.qual.Pure;
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+
+/**
+ * Interval implementation for Integer values.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class IntInterval extends Interval {
+
+ /**
+ * The maximum value for Integer intervals.
+ */
+ public static final int MAX_VALUE = Integer.MAX_VALUE;
+ /**
+ * The minimum value for Integer intervals.
+ */
+ public static final int MIN_VALUE = Integer.MIN_VALUE;
+
+ /**
+ * Creates a new Integer interval representing the whole range of Integer values.
+ */
+ public IntInterval() {
+ this.lowerBound = MIN_VALUE;
+ this.upperBound = MAX_VALUE;
+ }
+
+ /**
+ * Creates a new Integer interval representing a single number.
+ * @param number the number
+ */
+ public IntInterval(int number) {
+ this.lowerBound = number;
+ this.upperBound = number;
+ }
+
+ /**
+ * Creates a new Integer interval with the given bounds.
+ * @param lowerBound the lower bound.
+ * @param upperBound the upper bound.
+ */
+ public IntInterval(int lowerBound, int upperBound) {
+ assert lowerBound <= upperBound;
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+ }
+
+ private static int safeAbs(int x) {
+ if (x == Integer.MIN_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ return Math.abs(x);
+ }
+
+ @Override
+ public boolean getInformation() {
+ return lowerBound.equals(upperBound);
+ }
+
+ @Override
+ public Integer getValue() {
+ assert lowerBound.equals(upperBound);
+ return lowerBound;
+ }
+
+ @Override
+ public IntInterval copy() {
+ return new IntInterval(lowerBound, upperBound);
+ }
+
+ @Override
+ public void setToUnknown() {
+ this.lowerBound = MIN_VALUE;
+ this.upperBound = MAX_VALUE;
+ }
+
+ @Pure
+ @Override
+ public IntInterval plus(@NotNull Interval other) {
+ long loSum = (long) lowerBound + (long) other.lowerBound;
+ long hiSum = (long) upperBound + (long) other.upperBound;
+ int lo = loSum > MAX_VALUE ? MAX_VALUE : (loSum < MIN_VALUE ? MIN_VALUE : (int) loSum);
+ int hi = hiSum > MAX_VALUE ? MAX_VALUE : (hiSum < MIN_VALUE ? MIN_VALUE : (int) hiSum);
+ return new IntInterval(lo, hi);
+ }
+
+ @Pure
+ @Override
+ public IntInterval minus(@NotNull Interval other) {
+ long loSum = (long) lowerBound - (long) other.lowerBound;
+ long hiSum = (long) upperBound - (long) other.upperBound;
+ int lo = loSum > MAX_VALUE ? MAX_VALUE : (loSum < MIN_VALUE ? MIN_VALUE : (int) loSum);
+ int hi = hiSum > MAX_VALUE ? MAX_VALUE : (hiSum < MIN_VALUE ? MIN_VALUE : (int) hiSum);
+ return new IntInterval(lo, hi);
+ }
+
+ @Pure
+ @Override
+ public IntInterval times(@NotNull Interval other) {
+ long p1 = (long) lowerBound * other.lowerBound;
+ long p2 = (long) lowerBound * other.upperBound;
+ long p3 = (long) upperBound * other.lowerBound;
+ long p4 = (long) upperBound * other.upperBound;
+ long loLong = Math.min(Math.min(p1, p2), Math.min(p3, p4));
+ long hiLong = Math.max(Math.max(p1, p2), Math.max(p3, p4));
+ int lo = loLong > MAX_VALUE ? MAX_VALUE : (loLong < MIN_VALUE ? MIN_VALUE : (int) loLong);
+ int hi = hiLong > MAX_VALUE ? MAX_VALUE : (hiLong < MIN_VALUE ? MIN_VALUE : (int) hiLong);
+ return new IntInterval(lo, hi);
+ }
+
+ @Pure
+ @Override
+ public IntInterval divided(@NotNull Interval other) {
+ if (other.lowerBound <= 0 && other.upperBound >= 0) {
+ return new IntInterval(MIN_VALUE, MAX_VALUE);
+ }
+ long p1 = (lowerBound == MIN_VALUE && other.lowerBound == -1) ? (long) MAX_VALUE : (long) lowerBound / (long) other.lowerBound;
+ long p2 = (lowerBound == MIN_VALUE && other.upperBound == -1) ? (long) MAX_VALUE : (long) lowerBound / (long) other.upperBound;
+ long p3 = (upperBound == MIN_VALUE && other.lowerBound == -1) ? (long) MAX_VALUE : (long) upperBound / (long) other.lowerBound;
+ long p4 = (upperBound == MIN_VALUE && other.upperBound == -1) ? (long) MAX_VALUE : (long) upperBound / (long) other.upperBound;
+ long loLong = Math.min(Math.min(p1, p2), Math.min(p3, p4));
+ long hiLong = Math.max(Math.max(p1, p2), Math.max(p3, p4));
+ int lo = loLong > MAX_VALUE ? MAX_VALUE : (loLong < MIN_VALUE ? MIN_VALUE : (int) loLong);
+ int hi = hiLong > MAX_VALUE ? MAX_VALUE : (hiLong < MIN_VALUE ? MIN_VALUE : (int) hiLong);
+ return new IntInterval(lo, hi);
+ }
+
+ @Pure
+ @Override
+ public BooleanValue equal(@NotNull Interval other) {
+ if (lowerBound.equals(upperBound) && other.lowerBound.equals(other.upperBound) && lowerBound.equals(other.lowerBound)) {
+ return new BooleanValue(true);
+ } else if (upperBound < other.lowerBound || lowerBound > other.upperBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue notEqual(@NotNull Interval other) {
+ if (upperBound < other.lowerBound || lowerBound > other.upperBound) {
+ return new BooleanValue(true);
+ } else if (lowerBound.equals(upperBound) && other.lowerBound.equals(other.upperBound) && lowerBound.equals(other.lowerBound)) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue smaller(@NotNull Interval other) {
+ if (upperBound < other.lowerBound) {
+ return new BooleanValue(true);
+ } else if (lowerBound >= other.upperBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue smallerEqual(@NotNull Interval other) {
+ if (upperBound <= other.lowerBound) {
+ return new BooleanValue(true);
+ } else if (lowerBound > other.upperBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue bigger(@NotNull Interval other) {
+ if (lowerBound > other.upperBound) {
+ return new BooleanValue(true);
+ } else if (upperBound <= other.lowerBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Pure
+ @Override
+ public BooleanValue biggerEqual(@NotNull Interval other) {
+ if (upperBound <= other.lowerBound) {
+ return new BooleanValue(true);
+ } else if (lowerBound > other.upperBound) {
+ return new BooleanValue(false);
+ } else {
+ return new BooleanValue();
+ }
+ }
+
+ @Impure
+ @Override
+ public IntInterval plusPlus() {
+ long newLower = (long) lowerBound + 1;
+ long newUpper = (long) upperBound + 1;
+ lowerBound = newLower > MAX_VALUE ? MAX_VALUE : (int) newLower;
+ upperBound = newUpper > MAX_VALUE ? MAX_VALUE : (int) newUpper;
+ return this.copy();
+ }
+
+ @Impure
+ @Override
+ public IntInterval minusMinus() {
+ long newLower = (long) lowerBound - 1;
+ long newUpper = (long) upperBound - 1;
+ lowerBound = newLower < MIN_VALUE ? MIN_VALUE : (int) newLower;
+ upperBound = newUpper < MIN_VALUE ? MIN_VALUE : (int) newUpper;
+ return this.copy();
+ }
+
+ @Pure
+ @Override
+ public IntInterval unaryMinus() {
+ int newLower = (upperBound == Integer.MIN_VALUE) ? Integer.MAX_VALUE : -upperBound;
+ int newUpper = (lowerBound == Integer.MIN_VALUE) ? Integer.MAX_VALUE : -lowerBound;
+ return new IntInterval(newLower, newUpper);
+ }
+
+ @Pure
+ @Override
+ public IntInterval abs() {
+ if (upperBound < 0) {
+ return new IntInterval(safeAbs(upperBound), safeAbs(lowerBound));
+ } else if (lowerBound >= 0) {
+ return new IntInterval(lowerBound, upperBound);
+ } else {
+ int max = Math.max(safeAbs(lowerBound), safeAbs(upperBound));
+ return new IntInterval(0, max);
+ }
+ }
+
+ @Impure
+ @Override
+ public void merge(@NotNull Interval other) {
+ int smallerLowerBound = Math.min(this.lowerBound, other.lowerBound);
+ int largerUpperBound = Math.max(this.upperBound, other.upperBound);
+ assert smallerLowerBound <= largerUpperBound;
+ this.lowerBound = smallerLowerBound;
+ this.upperBound = largerUpperBound;
+ }
+
+ @Override
+ public int compareTo(@NotNull Interval o) {
+ if (!this.lowerBound.equals(o.lowerBound)) {
+ return Integer.compare(this.lowerBound, o.lowerBound);
+ } else {
+ return Integer.compare(this.upperBound, o.upperBound);
+ }
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/Interval.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/Interval.java
new file mode 100644
index 0000000000..a014560b37
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/numbers/helpers/Interval.java
@@ -0,0 +1,172 @@
+package de.jplag.java_cpg.ai.variables.values.numbers.helpers;
+
+import org.checkerframework.dataflow.qual.Impure;
+import org.checkerframework.dataflow.qual.Pure;
+import org.jetbrains.annotations.NotNull;
+
+import de.jplag.java_cpg.ai.variables.values.BooleanValue;
+
+/**
+ * Abstract class representing an interval of numbers.
+ * @author ujiqk
+ * @version 1.0
+ * @param the type of number (e.g., Integer, Double)
+ */
+public abstract class Interval> implements Comparable> {
+
+ protected T lowerBound;
+ protected T upperBound;
+
+ /**
+ * @return if exact information is available.
+ */
+ public abstract boolean getInformation();
+
+ /**
+ * @return the exact value. Only valid if getInformation() returns true.
+ */
+ public abstract T getValue();
+
+ /**
+ * Creates a deep copy of this interval.
+ * @return a new Interval instance with the same bounds.
+ */
+ public abstract Interval copy();
+
+ /**
+ * Sets this interval to represent an unknown value.
+ */
+ public abstract void setToUnknown();
+
+ /**
+ * @param other the other interval to add.
+ * @return the resulting interval
+ */
+ @Pure
+ public abstract Interval plus(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to subtract.
+ * @return the resulting interval
+ */
+ @Pure
+ public abstract Interval minus(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to multiply.
+ * @return the resulting interval
+ */
+ @Pure
+ public abstract Interval times(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to divide.
+ * @return the resulting interval
+ */
+ @Pure
+ public abstract Interval divided(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to check equality.
+ * @return if the intervals are equal
+ */
+ @Pure
+ public abstract BooleanValue equal(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to check inequality.
+ * @return if the intervals are not equal
+ */
+ @Pure
+ public abstract BooleanValue notEqual(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to compare.
+ * @return if this interval is smaller than the other
+ */
+ @Pure
+ public abstract BooleanValue smaller(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to compare.
+ * @return if this interval is smaller than or equal to the other
+ */
+ @Pure
+ public abstract BooleanValue smallerEqual(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to compare.
+ * @return if this interval is bigger than the other
+ */
+ @Pure
+ public abstract BooleanValue bigger(@NotNull Interval other);
+
+ /**
+ * @param other the other interval to compare.
+ * @return if this interval is bigger than or equal to the other
+ */
+ @Pure
+ public abstract BooleanValue biggerEqual(@NotNull Interval other);
+
+ /**
+ * @return the interval with all values incremented by one
+ */
+ @Impure
+ public abstract Interval plusPlus();
+
+ /**
+ * @return the interval with all values decremented by one
+ */
+ @Impure
+ public abstract Interval minusMinus();
+
+ /**
+ * @return the negated interval
+ */
+ @Pure
+ public abstract Interval unaryMinus();
+
+ /**
+ * @return the absolute value of the interval
+ */
+ @Pure
+ public abstract Interval abs();
+
+ /**
+ * Merges another interval into this one.
+ * @param other the other interval to merge
+ */
+ @Impure
+ public abstract void merge(@NotNull Interval other);
+
+ /**
+ * @return the lower bound of this interval
+ */
+ public T getLowerBound() {
+ return lowerBound;
+ }
+
+ /**
+ * @param lowerBound the new lower bound
+ */
+ public void setLowerBound(@NotNull T lowerBound) {
+ assert lowerBound.compareTo(upperBound) <= 0;
+ this.lowerBound = lowerBound;
+ }
+
+ /**
+ * @return the upper bound of this interval
+ */
+ public T getUpperBound() {
+ return upperBound;
+ }
+
+ /**
+ * @param upperBound the new upper bound
+ */
+ public void setUpperBound(T upperBound) {
+ assert lowerBound.compareTo(upperBound) <= 0;
+ this.upperBound = upperBound;
+ }
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/IStringValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/IStringValue.java
new file mode 100644
index 0000000000..18e9786fea
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/IStringValue.java
@@ -0,0 +1,25 @@
+package de.jplag.java_cpg.ai.variables.values.string;
+
+import org.jetbrains.annotations.Nullable;
+
+import de.jplag.java_cpg.ai.variables.values.IJavaObject;
+
+/**
+ * Strings are objects with added functionality.
+ * @author ujiqk
+ * @version 1.0
+ */
+public interface IStringValue extends IJavaObject {
+
+ /**
+ * @return true if the string value has definite information (i.e., a known value), false otherwise.
+ */
+ boolean getInformation();
+
+ /**
+ * @return if known, the string value.
+ */
+ @Nullable
+ String getValue();
+
+}
diff --git a/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringCharInclValue.java b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringCharInclValue.java
new file mode 100644
index 0000000000..e6d5bd9631
--- /dev/null
+++ b/languages/java-cpg/src/main/java/de/jplag/java_cpg/ai/variables/values/string/StringCharInclValue.java
@@ -0,0 +1,270 @@
+package de.jplag.java_cpg.ai.variables.values.string;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration;
+import de.jplag.java_cpg.ai.variables.Type;
+import de.jplag.java_cpg.ai.variables.values.IValue;
+import de.jplag.java_cpg.ai.variables.values.JavaObject;
+import de.jplag.java_cpg.ai.variables.values.Value;
+import de.jplag.java_cpg.ai.variables.values.VoidValue;
+import de.jplag.java_cpg.ai.variables.values.numbers.INumberValue;
+
+/**
+ * String representation using character inclusion sets.
+ * @author ujiqk
+ * @version 1.0
+ */
+public class StringCharInclValue extends JavaObject implements IStringValue {
+
+ // String=null <--> certainContained=null
+ /**
+ * Characters that are definitely contained in the string. Null if string is null.
+ */
+ @Nullable
+ Set certainContained;
+ /**
+ * Characters that may be contained in the string.
+ */
+ Set maybeContained;
+
+ /**
+ * A string value with no information.
+ */
+ public StringCharInclValue() {
+ super(Type.STRING);
+ certainContained = new HashSet<>();
+ maybeContained = allCharactersSet();
+ }
+
+ /**
+ * Constructor for StringCharInclValue with exact information.
+ * @param value the string value.
+ */
+ public StringCharInclValue(@Nullable String value) {
+ super(Type.STRING);
+ maybeContained = new HashSet<>();
+ if (value == null) {
+ certainContained = null;
+ return;
+ }
+ certainContained = new HashSet<>();
+ for (char c : value.toCharArray()) {
+ certainContained.add(c);
+ }
+ }
+
+ /**
+ * Constructor for StringCharInclValue with possible values.
+ * @param possibleValues the set of possible string values.
+ */
+ public StringCharInclValue(@NotNull Set possibleValues) {
+ super(Type.STRING);
+ certainContained = new HashSet<>();
+ maybeContained = new HashSet<>();
+ for (String value : possibleValues) {
+ for (char c : value.toCharArray()) {
+ maybeContained.add(c);
+ }
+ }
+ }
+
+ private StringCharInclValue(@Nullable Set certainContained, Set maybeContained) {
+ super(Type.STRING);
+ this.certainContained = certainContained;
+ this.maybeContained = maybeContained;
+ }
+
+ @Override
+ public IValue callMethod(@NotNull String methodName, List paramVars, MethodDeclaration method) {
+ switch (methodName) {
+ case "length" -> {
+ assert paramVars == null || paramVars.isEmpty();
+ return Value.valueFactory(Type.INT);
+ }
+ case "parseInt" -> {
+ assert paramVars.size() == 1;
+ assert paramVars.getFirst() instanceof IStringValue;
+ return Value.valueFactory(Type.INT);
+ }
+ case "parseDouble" -> {
+ assert paramVars.size() == 1;
+ assert paramVars.getFirst() instanceof IStringValue;
+ return Value.valueFactory(Type.FLOAT);
+ }
+ case "startsWith" -> {
+ assert paramVars.size() == 1;
+ assert paramVars.getFirst() instanceof IStringValue;
+ return Value.valueFactory(Type.BOOLEAN);
+ }
+ case "equals" -> {
+ assert paramVars.size() == 1;
+ if (paramVars.getFirst() instanceof VoidValue) {
+ paramVars.set(0, new StringCharInclValue());
+ }
+ StringCharInclValue other = (StringCharInclValue) paramVars.getFirst();
+ if (!Objects.equals(this.certainContained, other.certainContained) && this.maybeContained.isEmpty()
+ && other.maybeContained.isEmpty()) {
+ return Value.valueFactory(false);
+ } else {
+ return Value.valueFactory(Type.BOOLEAN);
+ }
+ }
+ case "toUpperCase" -> {
+ Set newCertain = new HashSet<>();
+ if (this.certainContained != null) {
+ for (Character c : certainContained) {
+ newCertain.add(Character.toUpperCase(c));
+ }
+ }
+ Set newMaybe = new HashSet<>();
+ for (Character c : maybeContained) {
+ newMaybe.add(Character.toUpperCase(c));
+ }
+ return new StringCharInclValue(newCertain, newMaybe);
+ }
+ case "charAt" -> {
+ assert paramVars.size() == 1;
+ assert paramVars.getFirst() instanceof INumberValue;
+ return Value.valueFactory(Type.CHAR);
+ }
+ case "trim" -> {
+ return new StringCharInclValue();
+ }
+ default -> throw new UnsupportedOperationException(methodName);
+ }
+ }
+
+ @Override
+ public Value accessField(@NotNull String fieldName) {
+ throw new UnsupportedOperationException("Access field not supported in StringValue");
+ }
+
+ @Override
+ public IValue binaryOperation(@NotNull String operator, @NotNull IValue other) {
+ if (other instanceof VoidValue) {
+ return new StringCharInclValue();
+ }
+ if (operator.equals("+") && other instanceof INumberValue inumbervalue) {
+ if (inumbervalue.getInformation()) { // ToDo: what to do with intervals?
+ Set newCertain = certainContained == null ? null : new HashSet<>(certainContained);
+ assert newCertain != null;
+ newCertain.addAll(doubleToCharSet(inumbervalue.getValue()));
+ return new StringCharInclValue(newCertain, new HashSet<>(maybeContained));
+ } else {
+ return new StringCharInclValue();
+ }
+ } else if (operator.equals("+") && other instanceof StringCharInclValue stringValue) {
+ assert !(this.certainContained == null || stringValue.certainContained == null);
+ Set newCertain = new HashSet<>(this.certainContained);
+ newCertain.addAll(stringValue.certainContained);
+ Set newMaybe = new HashSet<>(this.maybeContained);
+ newMaybe.addAll(stringValue.maybeContained);
+ return new StringCharInclValue(newCertain, newMaybe);
+ }
+ throw new UnsupportedOperationException("Binary operation " + operator + " not supported between " + getType() + " and " + other.getType());
+ }
+
+ @NotNull
+ @Override
+ public JavaObject copy() {
+ return new StringCharInclValue(certainContained == null ? null : new HashSet<>(certainContained), new HashSet<>(maybeContained));
+ }
+
+ @Override
+ public void merge(@NotNull IValue other) {
+ if (other instanceof VoidValue) {
+ this.certainContained = new HashSet<>();
+ this.maybeContained = allCharactersSet();
+ return;
+ }
+ assert other instanceof StringCharInclValue;
+ StringCharInclValue otherString = (StringCharInclValue) other;
+ if (this.certainContained == null || otherString.certainContained == null) {
+ this.certainContained = null;
+ } else {
+ Set removed = new HashSet<>(this.certainContained);
+ this.certainContained.retainAll(otherString.certainContained);
+ removed.removeAll(this.certainContained);
+ this.maybeContained.addAll(removed);
+ Set otherCertainNotInThis = new HashSet<>(otherString.certainContained);
+ otherCertainNotInThis.removeAll(this.certainContained);
+ this.maybeContained.addAll(otherCertainNotInThis);
+ this.maybeContained.addAll(otherString.maybeContained);
+ }
+ }
+
+ @Override
+ public void setToUnknown() {
+ this.certainContained = new HashSet<>();
+ this.maybeContained = allCharactersSet();
+ }
+
+ @Override
+ public void setInitialValue() {
+ this.certainContained = null;
+ this.maybeContained = new HashSet<>();
+ }
+
+ @NotNull
+ private Set allCharactersSet() {
+ Set allChars = new HashSet<>();
+ for (char c = Character.MIN_VALUE; c < Character.MAX_VALUE; c++) {
+ allChars.add(c);
+ }
+ return allChars;
+ }
+
+ @NotNull
+ private Set doubleToCharSet(double value) {
+ Set