Skip to content

Commit

Permalink
PartialEvaluator support for String methods that return arrays
Browse files Browse the repository at this point in the history
Improves the support for the behavior of methods that transform a `String` value into an array of values to now create an array reference (e.g. `DetailedArrayReferenceValue`), while before a `ParticularReferenceValue` was created.
A frequent example is a `String` being transformed using a method such as `toBytes()`/`toCharArray()`.
Tests were added and the `PartialEvaluatorHelper` was corrected to use the `DetailedArrayValueFactory`.

The `createArrayReferenceValue`* is being suggested to marked as deprecated because:
1. it is not being used in practice, the few implementations fallback on the version without the `elementValue`
2. it does not make much sense creating and array reference with a `Value` (not clear what the value can be)

*`ValueFactory.createArrayReferenceValue(String type, Clazz referencedClass, IntegerValue arrayLength, Value elementValue)`
  • Loading branch information
nmsa authored and robinlefever committed Dec 1, 2023
1 parent 5569474 commit b33a274
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package proguard.evaluation;

import java.lang.reflect.Array;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -594,6 +595,15 @@ else if (isInternalPrimitiveType(finalReturnType))
return createPrimitiveValue(methodResult, returnType);
}

// If the return value is a primitive array, store this in a (Detailed)ArrayReferenceValue
if (ClassUtil.internalArrayTypeDimensionCount(returnType) == 1 && isInternalPrimitiveType(ClassUtil.internalTypeFromArrayType(returnType)))
{
return valueFactory.createArrayReferenceValue(returnType,
null,
valueFactory.createIntegerValue(Array.getLength(methodResult)),
methodResult);
}

// If it is not a primitiveValue, it will be stored in a ParticularReferenceValue.
// If the return type is void, we still return an object, since this might need to be replaced on stack/variables.
// To create a referenceValue with a correct type (i.e. not void), we update the type from the instance we called upon.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,13 @@ public ReferenceValue createArrayReferenceValue(String type,
* Creates a new ReferenceValue that represents an array with elements of
* the given type, with the given length and initial element values.
*/
@Override
public ReferenceValue createArrayReferenceValue(String type,
Clazz referencedClass,
IntegerValue arrayLength,
Value elementValue)
Object elementValues)
{
return trace(valueFactory.createArrayReferenceValue(type, referencedClass, arrayLength, elementValue));
return trace(valueFactory.createArrayReferenceValue(type, referencedClass, arrayLength, elementValues));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,11 @@ public ReferenceValue createArrayReferenceValue(String type,
return createReferenceValue(type, referencedClass, false, false);
}


@Override
public ReferenceValue createArrayReferenceValue(String type,
Clazz referencedClass,
IntegerValue arrayLength,
Value elementValue)
Object elementValues)
{
return createArrayReferenceValue(type, referencedClass, arrayLength);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,126 @@ public ReferenceValue createArrayReferenceValue(String type,
this,
referenceID++);
}

public ReferenceValue createArrayReferenceValue(String type,
Clazz referencedClass,
IntegerValue arrayLength,
Object elementValues)

{


if (type == null) return TypedReferenceValueFactory.REFERENCE_VALUE_NULL;

if (!arrayLength.isParticular())
{
return new IdentifiedArrayReferenceValue(type,
referencedClass,
false,
arrayLength,
this,
referenceID++);
}
if(!elementValues.getClass().isArray() || elementValues.getClass().getComponentType().isArray()){
throw new IllegalArgumentException("Only one-dimension array type is supported: " + elementValues.getClass());
}


DetailedArrayReferenceValue detailedArray = new DetailedArrayReferenceValue(type,
referencedClass,
false,
arrayLength,
this,
referenceID++);
if(elementValues.getClass().isArray())
{
switch (type.charAt(1))// 0 is the array char
{
case TypeConstants.BOOLEAN: storeBooleanArray(detailedArray, (boolean[]) elementValues); break;
case TypeConstants.BYTE: storeByteArray(detailedArray, (byte[]) elementValues); break;
case TypeConstants.CHAR: storeCharArray(detailedArray, (char[]) elementValues); break;
case TypeConstants.SHORT: storeShortArray(detailedArray, (short[]) elementValues); break;
case TypeConstants.INT: storeIntArray(detailedArray, (int[]) elementValues); break;
case TypeConstants.LONG: storeLongArray(detailedArray, (long[]) elementValues); break;
case TypeConstants.FLOAT: storeFloatArray(detailedArray, (float[]) elementValues); break;
case TypeConstants.DOUBLE: storeDoubleArray(detailedArray, (double[]) elementValues); break;
default: storeObjectArray(detailedArray, (Object[]) elementValues);
}
}
return detailedArray;
}

private void storeBooleanArray(DetailedArrayReferenceValue detailedArray, boolean[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createIntegerValue(elementValues[i] ? 1:0));
}
}

private void storeByteArray(DetailedArrayReferenceValue detailedArray, byte[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createIntegerValue(elementValues[i]));
}
}

private void storeCharArray(DetailedArrayReferenceValue detailedArray, char[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createIntegerValue(elementValues[i]));
}
}

private void storeShortArray(DetailedArrayReferenceValue detailedArray, short[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createIntegerValue(elementValues[i]));
}
}

private void storeIntArray(DetailedArrayReferenceValue detailedArray, int[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createIntegerValue(elementValues[i]));
}
}

private void storeLongArray(DetailedArrayReferenceValue detailedArray, long[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createLongValue(elementValues[i]));
}
}


private void storeFloatArray(DetailedArrayReferenceValue detailedArray, float[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createFloatValue(elementValues[i]));
}
}


private void storeDoubleArray(DetailedArrayReferenceValue detailedArray, double[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createDoubleValue(elementValues[i]));
}
}

private void storeObjectArray(DetailedArrayReferenceValue detailedArray, Object[] elementValues)
{
for (int i = 0; i < elementValues.length; i++)
{
detailedArray.arrayStore(createIntegerValue(i), createReferenceValue(detailedArray.referencedClass, elementValues[i]));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ public ReferenceValue createArrayReferenceValue(String type, Clazz referencedCla
}

@Override
public ReferenceValue createArrayReferenceValue(String type, Clazz referencedClass, IntegerValue arrayLength, Value elementValue)
public ReferenceValue createArrayReferenceValue(String type, Clazz referencedClass, IntegerValue arrayLength, Object elementValues)
{
return wrap(super.createArrayReferenceValue(type, referencedClass, arrayLength, elementValue));
return wrap(super.createArrayReferenceValue(type, referencedClass, arrayLength, elementValues));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,16 +245,16 @@ public ReferenceValue createArrayReferenceValue(String type,
arrayLength);
}


@Override
public ReferenceValue createArrayReferenceValue(String type,
Clazz referencedClass,
IntegerValue arrayLength,
Value elementValue)
Object elementValues)
{
return arrayReferenceValueFactory.createArrayReferenceValue(type,
referencedClass,
arrayLength,
elementValue);
elementValues);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public ReferenceValue createReferenceValue(String type,
REFERENCE_VALUE_JAVA_LANG_OBJECT_NOT_NULL;
}


@Override
public ReferenceValue createArrayReferenceValue(String type,
Clazz referencedClass,
IntegerValue arrayLength)
Expand All @@ -67,11 +67,11 @@ public ReferenceValue createArrayReferenceValue(String type,
true));
}


@Override
public ReferenceValue createArrayReferenceValue(String type,
Clazz referencedClass,
IntegerValue arrayLength,
Value elementValue)
Object elementValues)
{
return createReferenceValue(TypeConstants.ARRAY + type,
referencedClass,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ ReferenceValue createArrayReferenceValue(String type,
Clazz referencedClass,
IntegerValue arrayLength);


/**
* Creates a new ReferenceValue that represents a non-null array with
* elements of the given type, with the given length and initial element
Expand All @@ -220,5 +219,5 @@ ReferenceValue createArrayReferenceValue(String type,
ReferenceValue createArrayReferenceValue(String type,
Clazz referencedClass,
IntegerValue arrayLength,
Value elementValue);
Object elementValues);
}
89 changes: 89 additions & 0 deletions base/src/test/kotlin/proguard/DetailedArrayTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import proguard.evaluation.value.ArrayReferenceValue
import proguard.evaluation.value.DetailedArrayReferenceValue
import proguard.evaluation.value.DetailedArrayValueFactory
import proguard.evaluation.value.IdentifiedArrayReferenceValue
import proguard.evaluation.value.ParticularIntegerValue
import proguard.evaluation.value.ParticularValueFactory
import proguard.evaluation.value.UnknownIntegerValue
import proguard.evaluation.value.ValueFactory
import proguard.testutils.ClassPoolBuilder
import proguard.testutils.JavaSource
import proguard.testutils.PartialEvaluatorUtil
import proguard.util.PartialEvaluatorHelper

class DetailedArrayTest : FreeSpec({
val valueFactory: ValueFactory = ParticularValueFactory(DetailedArrayValueFactory(), ParticularReferenceValueFactory())
Expand Down Expand Up @@ -80,6 +82,54 @@ class DetailedArrayTest : FreeSpec({
s.integerArrayLoad(valueFactory.createIntegerValue(1), valueFactory).integerValue().value() shouldBe 42
}

"Regression test: array from String with future update does not change previous value" {

val code = JavaSource(
"Test.java",
"""
public class Test {

public void arrayTest() {
byte[] array = "00000".getBytes();
array[0] = 4;
array[1] = 42;
array[1] = 102;
}
}
"""
)

val (classPool, _) = ClassPoolBuilder.fromSource(code, javacArguments = listOf("-g", "-source", "1.8", "-target", "1.8"))

val (instructions, variableTable) = PartialEvaluatorUtil.evaluate(
"Test",
"arrayTest",
"()V",
classPool,
partialEvaluator
)
/*
[8] => (11, iconst_1)
[9] => (12, bipush 42)
[10] => (14, bastore)
[11] => (15, aload_1 v1)
*/
val (instruction, _) = instructions[10]
val s = partialEvaluator.getVariablesAfter(instruction)
.getValue(variableTable["array"]!!) as DetailedArrayReferenceValue
s.type shouldBe "[B"
s.integerArrayLoad(valueFactory.createIntegerValue(1), valueFactory).integerValue()
.value() shouldBe 42

val (instructionEnd, _) = instructions.last()
val sEnd = partialEvaluator.getVariablesAfter(instructionEnd)
.getValue(variableTable["array"]!!) as DetailedArrayReferenceValue

sEnd.type shouldBe "[B"
sEnd.integerArrayLoad(valueFactory.createIntegerValue(0), valueFactory).integerValue().value() shouldBe 4
sEnd.integerArrayLoad(valueFactory.createIntegerValue(1), valueFactory).integerValue().value() shouldBe 102
}

"String array with no assignment in try block" {
val code = JavaSource(
"Test.java",
Expand Down Expand Up @@ -327,6 +377,45 @@ class DetailedArrayTest : FreeSpec({
}
}

"String function returning primitive array" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"A.java",
"""
class A {
public void functions() throws java.io.UnsupportedEncodingException
{
String str = "42";

byte[] byteArray = str.getBytes("UTF-8");
System.out.println(byteArray);

char[] charArray = str.toCharArray();
System.out.println(charArray);
}
}
"""
),
javacArguments = listOf("-source", "8", "-target", "8")
)

val invocationsWithStack = PartialEvaluatorHelper.evaluateMethod("A", "functions", "()V", programClassPool, DetailedArrayValueFactory())

"Primitive byte array evaluated correctly" {
val value = invocationsWithStack[14]!!.stack[0]
value.shouldBeInstanceOf<DetailedArrayReferenceValue>()
value.type shouldBe "[B"
value.value() shouldBe arrayOf(ParticularIntegerValue(52), ParticularIntegerValue(50))
}

"Primitive char array evaluated correctly" {
val value = invocationsWithStack[26]!!.stack[0]
value.shouldBeInstanceOf<DetailedArrayReferenceValue>()
value.type shouldBe "[C"
value.value() shouldBe arrayOf(ParticularIntegerValue('4'.code), ParticularIntegerValue('2'.code))
}
}

"Array reference generalization" - {
val code = JavaSource(
"Test.java",
Expand Down
Loading

0 comments on commit b33a274

Please sign in to comment.