From fc53fc0e5cd848de8798a9ed55b3efc81f8a2612 Mon Sep 17 00:00:00 2001 From: "toon.willemot" Date: Thu, 9 Jan 2025 17:30:57 +0100 Subject: [PATCH] Give JavaReflectionApiExecutor a more accurate name Renames the JavaReflectionApiExecutor to ObjectGetClassExecutor, since all the other functionality has been moved into the models themselves. --- ...cutor.java => ObjectGetClassExecutor.java} | 60 +++-- .../main/java/proguard/exception/ErrorId.java | 3 +- .../executor/JavaReflectionApiExecutorTest.kt | 233 ------------------ ...kt => ReflectiveModelExecutorJava9Test.kt} | 6 +- .../executor/ReflectiveModelExecutorTest.kt | 221 ++++++++++++++++- 5 files changed, 252 insertions(+), 271 deletions(-) rename base/src/main/java/proguard/evaluation/executor/{JavaReflectionApiExecutor.java => ObjectGetClassExecutor.java} (65%) delete mode 100644 base/src/test/kotlin/proguard/evaluation/executor/JavaReflectionApiExecutorTest.kt rename base/src/test/kotlin/proguard/evaluation/executor/{Java9ReflectionApiExecutorTest.kt => ReflectiveModelExecutorJava9Test.kt} (95%) diff --git a/base/src/main/java/proguard/evaluation/executor/JavaReflectionApiExecutor.java b/base/src/main/java/proguard/evaluation/executor/ObjectGetClassExecutor.java similarity index 65% rename from base/src/main/java/proguard/evaluation/executor/JavaReflectionApiExecutor.java rename to base/src/main/java/proguard/evaluation/executor/ObjectGetClassExecutor.java index aa46c3f5f..ad961d60d 100644 --- a/base/src/main/java/proguard/evaluation/executor/JavaReflectionApiExecutor.java +++ b/base/src/main/java/proguard/evaluation/executor/ObjectGetClassExecutor.java @@ -3,7 +3,7 @@ import static proguard.classfile.ClassConstants.*; import static proguard.classfile.util.ClassUtil.internalClassName; import static proguard.classfile.util.ClassUtil.internalClassNameFromType; -import static proguard.exception.ErrorId.EVALUATION_JAVA_REFLECTION_EXECUTOR; +import static proguard.exception.ErrorId.OBJECT_GET_CLASS_EXECUTOR_UNSUPPORTED_SIGNATURE; import java.util.HashSet; import java.util.Optional; @@ -23,9 +23,12 @@ /** * This {@link Executor} provides an implementation for {@link Executor#getMethodResult} which - * resolves a number of simple {@link Class} API methods. + * resolves all types of 'Object'.getClass {@link Class} calls based on the classes in + * the class pools. + * + *

For example classA.getClass() and classB.getClass() */ -public class JavaReflectionApiExecutor implements Executor { +public class ObjectGetClassExecutor implements Executor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; @@ -33,7 +36,7 @@ public class JavaReflectionApiExecutor implements Executor { private final Set supportedMethodSignatures = new HashSet<>(); /** Private constructor reserved for the static {@link Builder}. */ - private JavaReflectionApiExecutor(ClassPool programClassPool, ClassPool libraryClassPool) { + private ObjectGetClassExecutor(ClassPool programClassPool, ClassPool libraryClassPool) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; @@ -49,26 +52,22 @@ private JavaReflectionApiExecutor(ClassPool programClassPool, ClassPool libraryC @Override public MethodResult getMethodResult( MethodExecutionInfo methodExecutionInfo, ValueCalculator valueCalculator) { - MethodSignature target = methodExecutionInfo.getSignature(); - - // Handling these signatures only requires the type of the instance. - if (METHOD_NAME_OBJECT_GET_CLASS.equals(target.getMethodName())) { - Value instance = methodExecutionInfo.getInstanceNonStatic(); - if (!(instance instanceof TypedReferenceValue)) return MethodResult.invalidResult(); - TypedReferenceValue typedInstance = (TypedReferenceValue) instance; - - if (typedInstance.getType() == null) return MethodResult.invalidResult(); - Optional clazz = - findReferencedClazz(internalClassNameFromType(typedInstance.getType())); - if (clazz.isPresent()) { - return createResult(methodExecutionInfo, valueCalculator, new ClassModel(clazz.get())); - } + if (!METHOD_NAME_OBJECT_GET_CLASS.equals(methodExecutionInfo.getSignature().getMethodName())) + throw new ProguardCoreException( + OBJECT_GET_CLASS_EXECUTOR_UNSUPPORTED_SIGNATURE, + String.format( + "%s is not a supported method signature.", methodExecutionInfo.getSignature())); + + Value instance = methodExecutionInfo.getInstanceNonStatic(); + if (!(instance instanceof TypedReferenceValue)) return MethodResult.invalidResult(); + TypedReferenceValue typedInstance = (TypedReferenceValue) instance; + + if (typedInstance.getType() == null) return MethodResult.invalidResult(); + Optional clazz = findReferencedClazz(internalClassNameFromType(typedInstance.getType())); + if (clazz.isPresent()) { + return createResult(methodExecutionInfo, valueCalculator, new ClassModel(clazz.get())); } - - throw new ProguardCoreException( - EVALUATION_JAVA_REFLECTION_EXECUTOR, - String.format( - "%s is not a supported method signature.", methodExecutionInfo.getSignature())); + return MethodResult.invalidResult(); } @Override @@ -113,12 +112,12 @@ private static MethodResult createResult( // Builder class. - /** Builder for {@link JavaReflectionApiExecutor}. */ - public static class Builder implements Executor.Builder { + /** Builder for {@link ObjectGetClassExecutor}. */ + public static class Builder implements Executor.Builder { private final ClassPool programClassPool; private final ClassPool libraryClassPool; - private JavaReflectionApiExecutor javaReflectionApiExecutor = null; + private ObjectGetClassExecutor objectGetClassExecutor = null; public Builder(@NotNull ClassPool programClassPool, @NotNull ClassPool libraryClassPool) { this.programClassPool = programClassPool; @@ -126,12 +125,11 @@ public Builder(@NotNull ClassPool programClassPool, @NotNull ClassPool libraryCl } @Override - public JavaReflectionApiExecutor build() { - if (javaReflectionApiExecutor == null) { - javaReflectionApiExecutor = - new JavaReflectionApiExecutor(programClassPool, libraryClassPool); + public ObjectGetClassExecutor build() { + if (objectGetClassExecutor == null) { + objectGetClassExecutor = new ObjectGetClassExecutor(programClassPool, libraryClassPool); } - return javaReflectionApiExecutor; + return objectGetClassExecutor; } } } diff --git a/base/src/main/java/proguard/exception/ErrorId.java b/base/src/main/java/proguard/exception/ErrorId.java index efb44f940..0c3b48539 100644 --- a/base/src/main/java/proguard/exception/ErrorId.java +++ b/base/src/main/java/proguard/exception/ErrorId.java @@ -121,7 +121,8 @@ public final class ErrorId { ANALYSIS_JVM_TRANSFER_RELATION_CONSTANT_INSTRUCTION_VISITOR_OPCODE_UNSUPPORTED = 9_044; public static final int ANALYSIS_JVM_DEFAULT_REDUCE_OPERATOR_STATE_UNSUPPORTED = 9_045; public static final int ANALYSIS_JVM_TREE_HEAP_STATE_INCOMPATIBLE = 9_046; - public static final int EVALUATION_JAVA_REFLECTION_EXECUTOR = 9_047; + + public static final int OBJECT_GET_CLASS_EXECUTOR_UNSUPPORTED_SIGNATURE = 9_047; /** Private constructor to prevent instantiation of the class. */ private ErrorId() {} diff --git a/base/src/test/kotlin/proguard/evaluation/executor/JavaReflectionApiExecutorTest.kt b/base/src/test/kotlin/proguard/evaluation/executor/JavaReflectionApiExecutorTest.kt deleted file mode 100644 index 53a43cb92..000000000 --- a/base/src/test/kotlin/proguard/evaluation/executor/JavaReflectionApiExecutorTest.kt +++ /dev/null @@ -1,233 +0,0 @@ -package proguard.evaluation.executor - -import io.kotest.core.spec.style.BehaviorSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeInstanceOf -import proguard.classfile.Clazz -import proguard.classfile.Method -import proguard.classfile.attribute.Attribute.CODE -import proguard.classfile.attribute.CodeAttribute -import proguard.classfile.attribute.visitor.AllAttributeVisitor -import proguard.classfile.attribute.visitor.AttributeNameFilter -import proguard.classfile.attribute.visitor.MultiAttributeVisitor -import proguard.classfile.editor.InstructionSequenceBuilder -import proguard.classfile.instruction.Instruction -import proguard.classfile.instruction.visitor.AllInstructionVisitor -import proguard.classfile.instruction.visitor.InstructionVisitor -import proguard.classfile.util.ClassReferenceInitializer -import proguard.classfile.util.InstructionSequenceMatcher -import proguard.classfile.visitor.NamedMethodVisitor -import proguard.evaluation.ExecutingInvocationUnit -import proguard.evaluation.PartialEvaluator -import proguard.evaluation.ParticularReferenceValueFactory -import proguard.evaluation.executor.model.ClassLoaderModelExecutor -import proguard.evaluation.executor.model.ClassModelExecutor -import proguard.evaluation.value.ArrayReferenceValueFactory -import proguard.evaluation.value.ParticularValueFactory -import proguard.evaluation.value.`object`.model.ClassModel -import proguard.testutils.AssemblerSource -import proguard.testutils.ClassPoolBuilder -import proguard.testutils.JavaSource -import java.util.ArrayList - -class JavaReflectionApiExecutorTest : BehaviorSpec({ - Given("A method which uses various ways to access class details") { - val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( - JavaSource( - "Foo.java", - """ - package com.example; - public class Foo { - public static void main(String[] args) throws Exception { - // ClassLoader - Foo.class.getClassLoader().loadClass("com.example.Foo").getDeclaredConstructors(); - // getClass() - new Foo().getClass().getDeclaredConstructors(); - // getSimpleName() - Class.forName("com.example." + Foo.class.getSimpleName()).getDeclaredConstructors(); - Class.forName("com.example." + new Foo().getClass().getSimpleName()).getDeclaredConstructors(); - // getTypeName() - Class.forName(Foo.class.getTypeName()).getDeclaredConstructors(); - Class.forName(new Foo().getClass().getTypeName()).getDeclaredConstructors(); - // getSuperclass() - SubFoo.class.getSuperclass().getDeclaredConstructors(); - new SubFoo().getClass().getSuperclass().getDeclaredConstructors(); - // getCanonicalName() - Class.forName(SubFoo.InnerSubFoo.class.getCanonicalName() - // This regex reverts the canonical name to the original name. - .replaceAll("\\.(?=[^\\.]*${'$'})", "\\${'$'}")) - .getSuperclass().getDeclaredConstructors(); - // StringBuilder & newInstance - ((Foo)Class.forName(new StringBuilder().append("com.example.Foo").toString()).newInstance()).getClass().getDeclaredConstructors(); - } - } - - class SubFoo extends Foo { - public class InnerSubFoo extends Foo {} - } - - """.trimIndent(), - ), - javacArguments = listOf("-source", "1.8", "-target", "1.8"), - ) - - When("It is partially evaluated with a JavaReflectionExecutor") { - val particularValueFactory = ParticularValueFactory( - ArrayReferenceValueFactory(), - ParticularReferenceValueFactory(), - ) - - val particularValueEvaluator = PartialEvaluator.Builder.create() - .setValueFactory(particularValueFactory) - .setInvocationUnit( - ExecutingInvocationUnit.Builder(programClassPool, libraryClassPool) - .setEnableSameInstanceIdApproximation(true) - .useDefaultStringReflectionExecutor(true) - .addExecutor(JavaReflectionApiExecutor.Builder(programClassPool, libraryClassPool)) - .addExecutor(ClassModelExecutor.Builder(programClassPool, libraryClassPool)) - .addExecutor(ClassLoaderModelExecutor.Builder(programClassPool, libraryClassPool)) - .build(particularValueFactory), - ) - .setEvaluateAllCode(true) - .stopAnalysisAfterNEvaluations(50) - .build() - - // We'll also collect the instruction offsets of where we expect ClassModels to be on the stack. - val getDeclaredConstructorOffsets = ArrayList() - val builder = InstructionSequenceBuilder().invokevirtual( - "java/lang/Class", - "getDeclaredConstructors", - "()[Ljava/lang/reflect/Constructor;", - ) - val matcher = InstructionSequenceMatcher(builder.constants(), builder.instructions()) - val getDeclaredConstructorOffsetCollector: InstructionVisitor = object : InstructionVisitor { - override fun visitAnyInstruction( - clazz: Clazz, - method: Method, - codeAttribute: CodeAttribute, - offset: Int, - instruction: Instruction, - ) { - instruction.accept(clazz, method, codeAttribute, offset, matcher) - if (matcher.isMatching) getDeclaredConstructorOffsets.add(offset) - } - } - - programClassPool.classesAccept( - "com/example/Foo", - NamedMethodVisitor( - "main", - "([Ljava/lang/String;)V", - AllAttributeVisitor( - AttributeNameFilter( - CODE, - MultiAttributeVisitor( - AllInstructionVisitor(getDeclaredConstructorOffsetCollector), - particularValueEvaluator, - ), - ), - ), - ), - ) - - Then("Then the retrieved classes should all be modeled") { - getDeclaredConstructorOffsets.forEach { offset -> - val stackBeforeGetDeclaredConstructor = particularValueEvaluator.getStackBefore(offset) - val fooValue = stackBeforeGetDeclaredConstructor.getTop(0).referenceValue().value.modeledValue - fooValue.shouldBeInstanceOf() - fooValue.clazz shouldBe programClassPool.getClass("com/example/Foo") - } - } - } - } - - Given("A method which calls ClassLoader.findLoadedClass") { - val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( - AssemblerSource( - "Test.jbc", - """ - version 1.8; - public final class Foo { - private static final void foo() { - invokestatic java.lang.ClassLoader#java.lang.ClassLoader getSystemClassLoader() - ldc "Foo" - invokevirtual java.lang.ClassLoader#java.lang.Class findLoadedClass(java.lang.String) - invokevirtual java.lang.Class#java.lang.reflect.Constructor[] getDeclaredConstructors() - pop - return - } - } - """.trimIndent(), - ), - ) - - When("It is partially evaluated with a JavaReflectionExecutor") { - val particularValueFactory = ParticularValueFactory( - ArrayReferenceValueFactory(), - ParticularReferenceValueFactory(), - ) - - val particularValueEvaluator = PartialEvaluator.Builder.create() - .setValueFactory(particularValueFactory) - .setInvocationUnit( - ExecutingInvocationUnit.Builder(programClassPool, libraryClassPool) - .setEnableSameInstanceIdApproximation(true) - .useDefaultStringReflectionExecutor(true) - .addExecutor(ClassLoaderModelExecutor.Builder(programClassPool, libraryClassPool)) - .build(particularValueFactory), - ) - .setEvaluateAllCode(true) - .stopAnalysisAfterNEvaluations(50) - .build() - - // We'll also collect the instruction offsets of where we expect ClassModels to be on the stack. - val getDeclaredConstructorOffsets = ArrayList() - val builder = InstructionSequenceBuilder().invokevirtual( - "java/lang/Class", - "getDeclaredConstructors", - "()[Ljava/lang/reflect/Constructor;", - ) - val matcher = InstructionSequenceMatcher(builder.constants(), builder.instructions()) - val getDeclaredConstructorOffsetCollector: InstructionVisitor = object : InstructionVisitor { - override fun visitAnyInstruction( - clazz: Clazz, - method: Method, - codeAttribute: CodeAttribute, - offset: Int, - instruction: Instruction, - ) { - instruction.accept(clazz, method, codeAttribute, offset, matcher) - if (matcher.isMatching) getDeclaredConstructorOffsets.add(offset) - } - } - - // ClassLoader.findLoadedClass has protected access, so we need to ignore access rules during initialization. - programClassPool.classesAccept(ClassReferenceInitializer(programClassPool, libraryClassPool, false)) - programClassPool.classesAccept( - "Foo", - NamedMethodVisitor( - "foo", - "()V", - AllAttributeVisitor( - AttributeNameFilter( - CODE, - MultiAttributeVisitor( - AllInstructionVisitor(getDeclaredConstructorOffsetCollector), - particularValueEvaluator, - ), - ), - ), - ), - ) - - Then("Then the retrieved classes should all be modeled") { - getDeclaredConstructorOffsets.forEach { offset -> - val stackBeforeGetDeclaredConstructor = particularValueEvaluator.getStackBefore(offset) - val fooValue = stackBeforeGetDeclaredConstructor.getTop(0).referenceValue().value.modeledValue - fooValue.shouldBeInstanceOf() - fooValue.clazz shouldBe programClassPool.getClass("Foo") - } - } - } - } -}) diff --git a/base/src/test/kotlin/proguard/evaluation/executor/Java9ReflectionApiExecutorTest.kt b/base/src/test/kotlin/proguard/evaluation/executor/ReflectiveModelExecutorJava9Test.kt similarity index 95% rename from base/src/test/kotlin/proguard/evaluation/executor/Java9ReflectionApiExecutorTest.kt rename to base/src/test/kotlin/proguard/evaluation/executor/ReflectiveModelExecutorJava9Test.kt index d2d80d8c3..805c8c35e 100644 --- a/base/src/test/kotlin/proguard/evaluation/executor/Java9ReflectionApiExecutorTest.kt +++ b/base/src/test/kotlin/proguard/evaluation/executor/ReflectiveModelExecutorJava9Test.kt @@ -26,10 +26,9 @@ import proguard.evaluation.value.`object`.model.ClassModel import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import proguard.testutils.RequiresJavaVersion -import java.util.ArrayList @RequiresJavaVersion(9) -class Java9ReflectionApiExecutorTest : BehaviorSpec({ +class ReflectiveModelExecutorJava9Test : BehaviorSpec({ Given("A method which uses various ways to access class details") { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( @@ -47,7 +46,7 @@ class Java9ReflectionApiExecutorTest : BehaviorSpec({ javacArguments = listOf("-source", "1.8", "-target", "1.8"), ) - When("It is partially evaluated with a JavaReflectionExecutor") { + When("It is partially evaluated with a ClassModel executor") { val particularValueFactory = ParticularValueFactory( ArrayReferenceValueFactory(), ParticularReferenceValueFactory(), @@ -59,7 +58,6 @@ class Java9ReflectionApiExecutorTest : BehaviorSpec({ ExecutingInvocationUnit.Builder(programClassPool, libraryClassPool) .setEnableSameInstanceIdApproximation(true) .useDefaultStringReflectionExecutor(true) - .addExecutor(JavaReflectionApiExecutor.Builder(programClassPool, libraryClassPool)) .addExecutor(ClassModelExecutor.Builder(programClassPool, libraryClassPool)) .build(particularValueFactory), ) diff --git a/base/src/test/kotlin/proguard/evaluation/executor/ReflectiveModelExecutorTest.kt b/base/src/test/kotlin/proguard/evaluation/executor/ReflectiveModelExecutorTest.kt index 8fdad1b78..d47f93107 100644 --- a/base/src/test/kotlin/proguard/evaluation/executor/ReflectiveModelExecutorTest.kt +++ b/base/src/test/kotlin/proguard/evaluation/executor/ReflectiveModelExecutorTest.kt @@ -3,20 +3,37 @@ package proguard.evaluation.executor import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf +import proguard.classfile.Clazz +import proguard.classfile.Method +import proguard.classfile.attribute.Attribute.CODE +import proguard.classfile.attribute.CodeAttribute +import proguard.classfile.attribute.visitor.AllAttributeVisitor +import proguard.classfile.attribute.visitor.AttributeNameFilter +import proguard.classfile.attribute.visitor.MultiAttributeVisitor +import proguard.classfile.editor.InstructionSequenceBuilder +import proguard.classfile.instruction.Instruction +import proguard.classfile.instruction.visitor.AllInstructionVisitor +import proguard.classfile.instruction.visitor.InstructionVisitor +import proguard.classfile.util.ClassReferenceInitializer +import proguard.classfile.util.InstructionSequenceMatcher +import proguard.classfile.visitor.NamedMethodVisitor import proguard.evaluation.ExecutingInvocationUnit import proguard.evaluation.PartialEvaluator import proguard.evaluation.ParticularReferenceValueFactory +import proguard.evaluation.executor.model.ClassLoaderModelExecutor +import proguard.evaluation.executor.model.ClassModelExecutor import proguard.evaluation.value.ArrayReferenceValueFactory import proguard.evaluation.value.ParticularReferenceValue import proguard.evaluation.value.ParticularValueFactory import proguard.evaluation.value.`object`.model.ClassModel +import proguard.testutils.AssemblerSource import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import proguard.testutils.PartialEvaluatorUtil import proguard.util.BasicHierarchyProvider class ReflectiveModelExecutorTest : BehaviorSpec({ - Given("Test model executor") { + Given("Test ClassModel executor") { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( "Foo.java", @@ -37,7 +54,7 @@ class ReflectiveModelExecutorTest : BehaviorSpec({ javacArguments = listOf("-g", "-source", "1.8", "-target", "1.8"), ) - When("It is partially evaluated with a JavaReflectionExecutor") { + When("It is partially evaluated with a ClassModel executor") { val particularValueFactory = ParticularValueFactory( ArrayReferenceValueFactory(), ParticularReferenceValueFactory(), @@ -79,4 +96,204 @@ class ReflectiveModelExecutorTest : BehaviorSpec({ } } } + + Given("A method which uses various ways to access class details") { + val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( + JavaSource( + "Foo.java", + """ + package com.example; + public class Foo { + public static void main(String[] args) throws Exception { + // ClassLoader + Foo.class.getClassLoader().loadClass("com.example.Foo").getDeclaredConstructors(); + // getClass() + new Foo().getClass().getDeclaredConstructors(); + // getSimpleName() + Class.forName("com.example." + Foo.class.getSimpleName()).getDeclaredConstructors(); + Class.forName("com.example." + new Foo().getClass().getSimpleName()).getDeclaredConstructors(); + // getTypeName() + Class.forName(Foo.class.getTypeName()).getDeclaredConstructors(); + Class.forName(new Foo().getClass().getTypeName()).getDeclaredConstructors(); + // getSuperclass() + SubFoo.class.getSuperclass().getDeclaredConstructors(); + new SubFoo().getClass().getSuperclass().getDeclaredConstructors(); + // getCanonicalName() + Class.forName(SubFoo.InnerSubFoo.class.getCanonicalName() + // This regex reverts the canonical name to the original name. + .replaceAll("\\.(?=[^\\.]*${'$'})", "\\${'$'}")) + .getSuperclass().getDeclaredConstructors(); + // StringBuilder & newInstance + ((Foo)Class.forName(new StringBuilder().append("com.example.Foo").toString()).newInstance()).getClass().getDeclaredConstructors(); + } + } + + class SubFoo extends Foo { + public class InnerSubFoo extends Foo {} + } + + """.trimIndent(), + ), + javacArguments = listOf("-source", "1.8", "-target", "1.8"), + ) + + When("It is partially evaluated using executors for the relevant Models") { + val particularValueFactory = ParticularValueFactory( + ArrayReferenceValueFactory(), + ParticularReferenceValueFactory(), + ) + + val particularValueEvaluator = PartialEvaluator.Builder.create() + .setValueFactory(particularValueFactory) + .setInvocationUnit( + ExecutingInvocationUnit.Builder(programClassPool, libraryClassPool) + .setEnableSameInstanceIdApproximation(true) + .useDefaultStringReflectionExecutor(true) + .addExecutor(ObjectGetClassExecutor.Builder(programClassPool, libraryClassPool)) + .addExecutor(ClassModelExecutor.Builder(programClassPool, libraryClassPool)) + .addExecutor(ClassLoaderModelExecutor.Builder(programClassPool, libraryClassPool)) + .build(particularValueFactory), + ) + .setEvaluateAllCode(true) + .stopAnalysisAfterNEvaluations(50) + .build() + + // We'll also collect the instruction offsets of where we expect ClassModels to be on the stack. + val getDeclaredConstructorOffsets = ArrayList() + val builder = InstructionSequenceBuilder().invokevirtual( + "java/lang/Class", + "getDeclaredConstructors", + "()[Ljava/lang/reflect/Constructor;", + ) + val matcher = InstructionSequenceMatcher(builder.constants(), builder.instructions()) + val getDeclaredConstructorOffsetCollector: InstructionVisitor = object : InstructionVisitor { + override fun visitAnyInstruction( + clazz: Clazz, + method: Method, + codeAttribute: CodeAttribute, + offset: Int, + instruction: Instruction, + ) { + instruction.accept(clazz, method, codeAttribute, offset, matcher) + if (matcher.isMatching) getDeclaredConstructorOffsets.add(offset) + } + } + + programClassPool.classesAccept( + "com/example/Foo", + NamedMethodVisitor( + "main", + "([Ljava/lang/String;)V", + AllAttributeVisitor( + AttributeNameFilter( + CODE, + MultiAttributeVisitor( + AllInstructionVisitor(getDeclaredConstructorOffsetCollector), + particularValueEvaluator, + ), + ), + ), + ), + ) + + Then("Then the retrieved classes should all be modeled") { + getDeclaredConstructorOffsets.forEach { offset -> + val stackBeforeGetDeclaredConstructor = particularValueEvaluator.getStackBefore(offset) + val fooValue = stackBeforeGetDeclaredConstructor.getTop(0).referenceValue().value.modeledValue + fooValue.shouldBeInstanceOf() + fooValue.clazz shouldBe programClassPool.getClass("com/example/Foo") + } + } + } + } + + Given("A method which calls ClassLoader.findLoadedClass") { + val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( + AssemblerSource( + "Test.jbc", + """ + version 1.8; + public final class Foo { + private static final void foo() { + invokestatic java.lang.ClassLoader#java.lang.ClassLoader getSystemClassLoader() + ldc "Foo" + invokevirtual java.lang.ClassLoader#java.lang.Class findLoadedClass(java.lang.String) + invokevirtual java.lang.Class#java.lang.reflect.Constructor[] getDeclaredConstructors() + pop + return + } + } + """.trimIndent(), + ), + ) + + When("It is partially evaluated with a ClassLoaderModel executor") { + val particularValueFactory = ParticularValueFactory( + ArrayReferenceValueFactory(), + ParticularReferenceValueFactory(), + ) + + val particularValueEvaluator = PartialEvaluator.Builder.create() + .setValueFactory(particularValueFactory) + .setInvocationUnit( + ExecutingInvocationUnit.Builder(programClassPool, libraryClassPool) + .setEnableSameInstanceIdApproximation(true) + .useDefaultStringReflectionExecutor(true) + .addExecutor(ClassLoaderModelExecutor.Builder(programClassPool, libraryClassPool)) + .build(particularValueFactory), + ) + .setEvaluateAllCode(true) + .stopAnalysisAfterNEvaluations(50) + .build() + + // We'll also collect the instruction offsets of where we expect ClassModels to be on the stack. + val getDeclaredConstructorOffsets = ArrayList() + val builder = InstructionSequenceBuilder().invokevirtual( + "java/lang/Class", + "getDeclaredConstructors", + "()[Ljava/lang/reflect/Constructor;", + ) + val matcher = InstructionSequenceMatcher(builder.constants(), builder.instructions()) + val getDeclaredConstructorOffsetCollector: InstructionVisitor = object : InstructionVisitor { + override fun visitAnyInstruction( + clazz: Clazz, + method: Method, + codeAttribute: CodeAttribute, + offset: Int, + instruction: Instruction, + ) { + instruction.accept(clazz, method, codeAttribute, offset, matcher) + if (matcher.isMatching) getDeclaredConstructorOffsets.add(offset) + } + } + + // ClassLoader.findLoadedClass has protected access, so we need to ignore access rules during initialization. + programClassPool.classesAccept(ClassReferenceInitializer(programClassPool, libraryClassPool, false)) + programClassPool.classesAccept( + "Foo", + NamedMethodVisitor( + "foo", + "()V", + AllAttributeVisitor( + AttributeNameFilter( + CODE, + MultiAttributeVisitor( + AllInstructionVisitor(getDeclaredConstructorOffsetCollector), + particularValueEvaluator, + ), + ), + ), + ), + ) + + Then("Then the retrieved classes should all be modeled") { + getDeclaredConstructorOffsets.forEach { offset -> + val stackBeforeGetDeclaredConstructor = particularValueEvaluator.getStackBefore(offset) + val fooValue = stackBeforeGetDeclaredConstructor.getTop(0).referenceValue().value.modeledValue + fooValue.shouldBeInstanceOf() + fooValue.clazz shouldBe programClassPool.getClass("Foo") + } + } + } + } })