Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ExecutingInvocationUnit to use customizable executors #126

Merged
merged 11 commits into from
Feb 15, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void invokeMethod(
String internalReturnClassName = ClassUtil.internalClassNameFromType(returnType);
if (returnType != null
&& internalReturnClassName != null
&& executingInvocationUnit.isSupportedClass(internalReturnClassName)) {
&& executingInvocationUnit.isSupportedMethodCall(internalReturnClassName, null)) {
// we can at most know the return type
pushReturnTypedValue(state, operands, (ConcreteCall) call, returnType);
return;
Expand Down
8 changes: 8 additions & 0 deletions base/src/main/java/proguard/classfile/JavaConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
public class JavaConstants {
public static final String JAVA_FILE_EXTENSION = ".java";

public static final String TYPE_JAVA_LANG_BOOLEAN = "java.lang.Boolean";
public static final String TYPE_JAVA_LANG_INTEGER = "java.lang.Integer";
public static final String TYPE_JAVA_LANG_LONG = "java.lang.Long";
public static final String TYPE_JAVA_LANG_CHARACTER = "java.lang.Character";
public static final String TYPE_JAVA_LANG_BYTE = "java.lang.Byte";
public static final String TYPE_JAVA_LANG_SHORT = "java.lang.Short";
public static final String TYPE_JAVA_LANG_FLOAT = "java.lang.Float";
public static final String TYPE_JAVA_LANG_DOUBLE = "java.lang.Double";
public static final String TYPE_JAVA_LANG_OBJECT = "java.lang.Object";
public static final String TYPE_JAVA_LANG_STRING = "java.lang.String";
public static final String TYPE_JAVA_LANG_CLASS = "java.lang.Class";
Expand Down
1,079 changes: 399 additions & 680 deletions base/src/main/java/proguard/evaluation/ExecutingInvocationUnit.java

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions base/src/main/java/proguard/evaluation/executor/Executor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* ProGuardCORE -- library to process Java bytecode.
*
* Copyright (c) 2002-2023 Guardsquare NV
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package proguard.evaluation.executor;

import java.util.Optional;
import proguard.classfile.MethodSignature;
import proguard.evaluation.ExecutingInvocationUnit;
import proguard.evaluation.executor.instancehandler.ExecutorInstanceHandler;
import proguard.evaluation.executor.matcher.ExecutorMatcher;
import proguard.evaluation.value.ReferenceValue;
import proguard.evaluation.value.Value;

/**
* This abstract class specifies a modular component which can be added to a {@link
* ExecutingInvocationUnit} in order to extend its capabilities. An {@link Executor} specifies which
* method calls it supports, what methods return their own instance and how a method result is
* calculated.
*/
public abstract class Executor {
protected ExecutorMatcher executorMatcher;
protected ExecutorInstanceHandler instanceHandler;

/**
* Calculate the result of a given method. This is the return value for a non-constructor method
* (<c>null</c> if it returns <c>void</c>) and the instantiated object for a constructor.
*
* @param methodData Information about the called method.
* @param instance The {@link ReferenceValue} of the instance, <c>null</c> for static methods.
* @param callingInstance The instance object, <c>null</c> for static methods.
* @param parameters An array of the parameter values of the method call.
* @return The result of the method call wrapped in an optional. <code>Optional.empty()</code> if
* a result could not be calculated.
*/
public abstract Optional<Object> getMethodResult(
MethodExecutionInfo methodData,
ReferenceValue instance,
Object callingInstance,
Value[] parameters);

/**
* Returns whether a certain method invocation is supported.
*
* @param signature The method signature.
* @return whether the method invocation is supported.
*/
public boolean isSupportedMethodCall(MethodSignature signature) {
return executorMatcher.matches(signature);
}

/**
* Returns whether a certain method invocation is assumed to return its own instance. This
* indicates whether replacing the instance reference in the stack and variables is necessary.
*
* @param signature The method signature.
* @param returnsSameTypeAsInstance whether the method has matching return and instance types
* @return whether the method returns its own instance.
*/
public boolean returnsOwnInstance(MethodSignature signature, boolean returnsSameTypeAsInstance) {
return returnsSameTypeAsInstance
&& instanceHandler.returnsOwnInstance(signature.getClassName(), signature.method);
}

/**
* Get an object which will act as the calling instance. If we know that executing the method does
* not modify the instance this can just be the same value. Otherwise, a copy should be returned
* in order to not change the reference whose state may be of interest at the end of an analysis.
*
* @param instanceValue The {@link ReferenceValue} of the instance.
* @param className The name of the class of the instance.
* @return The new calling instance.
*/
public abstract Object getInstanceCopyIfMutable(ReferenceValue instanceValue, String className);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* ProGuardCORE -- library to process Java bytecode.
*
* Copyright (c) 2002-2023 Guardsquare NV
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package proguard.evaluation.executor;

import static proguard.classfile.AccessConstants.STATIC;
import static proguard.classfile.ClassConstants.METHOD_NAME_INIT;

import java.util.Optional;
import proguard.analysis.datastructure.CodeLocation;
import proguard.analysis.datastructure.callgraph.ConcreteCall;
import proguard.classfile.Clazz;
import proguard.classfile.Method;
import proguard.classfile.MethodSignature;
import proguard.classfile.constant.AnyMethodrefConstant;
import proguard.classfile.util.ClassUtil;
import proguard.classfile.visitor.ReturnClassExtractor;
import proguard.evaluation.value.IdentifiedReferenceValue;
import proguard.evaluation.value.ReferenceValue;
import proguard.evaluation.value.Value;

/**
* This class stores data relevant to modeling the execution of a method and offers methods to
* extract additional information.
*/
public class MethodExecutionInfo {
private final MethodSignature signature;
private final CodeLocation caller;
private final boolean isConstructor;
private final boolean isStatic;
private final String returnType;
private final Clazz resultClass;
private final String resultType;

/**
* Constructs a MethodExecutionInfo.
*
* @param clazz The referenced class.
* @param method The referenced method.
* @param caller The code location of the call site. May be null.
*/
public MethodExecutionInfo(Clazz clazz, Method method, CodeLocation caller) {
signature = new MethodSignature(clazz, method);
mrjameshamilton marked this conversation as resolved.
Show resolved Hide resolved
this.caller = caller;

returnType = ClassUtil.internalMethodReturnType(signature.descriptor.toString());
isConstructor = signature.method.equals(METHOD_NAME_INIT);
isStatic = (method.getAccessFlags() & STATIC) != 0;

if (isConstructor) {
// For a Constructor, we always return a type, even if the return type of the method would be
// void.
resultClass = clazz;
} else {
// Get the class of the return type, if available, null otherwise.
ReturnClassExtractor returnClassExtractor = new ReturnClassExtractor();
method.accept(clazz, returnClassExtractor);
resultClass = returnClassExtractor.returnClass;
}
resultType =
resultClass != null && isConstructor
? ClassUtil.internalTypeFromClassName(resultClass.getName())
: returnType;
}

/**
* Constructs a MethodExecutionInfo.
*
* @param anyMethodrefConstant A method reference constant. Requires referenced class to be
* initialized (using {@link proguard.classfile.util.ClassReferenceInitializer}.
* @param caller The code location of the call site. May be null.
*/
public MethodExecutionInfo(AnyMethodrefConstant anyMethodrefConstant, CodeLocation caller) {
this(anyMethodrefConstant.referencedClass, anyMethodrefConstant.referencedMethod, caller);
}

/**
* Constructs a MethodExecutionInfo.
*
* @param call the concrete call.
*/
public MethodExecutionInfo(ConcreteCall call) {
this(call.getTargetClass(), call.getTargetMethod(), call.caller);
}

/** Get the method signature of the method */
public MethodSignature getSignature() {
return signature;
}

/** Get the code location of the call site */
public Optional<CodeLocation> getCaller() {
return Optional.ofNullable(caller);
}

/** Return whether the method is a constructor. */
public boolean isConstructor() {
return isConstructor;
}

/** Return whether the method is static. */
public boolean isStatic() {
return isStatic;
}

/** Get the return type of the method. */
public String getReturnType() {
return returnType;
}

/**
* Get the result class of the method. If the method is a constructor, this will be the class of
* the instance.
*/
public Clazz getResultClass() {
return resultClass;
}

/**
* Get the result type of the method. If the method is a constructor, this will be the type of the
* instance.
*/
public String getResultType() {
return resultType;
}

/** Get the {@link ReferenceValue} of the instance. */
public Optional<ReferenceValue> getInstanceValue(Value instance) {
return instance instanceof ReferenceValue
? Optional.of(instance.referenceValue())
: Optional.empty();
}

/** Get the reference value of the instance. */
public Optional<ReferenceValue> getInstanceValue(Value[] parameters) {
return parameters != null && !isStatic ? getInstanceValue(parameters[0]) : Optional.empty();
}

/** Get the object ID for the instance value. */
public Optional<Object> getObjectId(ReferenceValue instanceValue) {
return instanceValue instanceof IdentifiedReferenceValue
? Optional.of(((IdentifiedReferenceValue) instanceValue).id)
: Optional.empty();
}

/** Get the calling instance using the copy behavior defined by the executor. */
public Optional<Object> getCallingInstance(Executor executor, Value[] parameters) {
return getInstanceValue(parameters)
.flatMap(instanceValue -> getCallingInstance(executor, instanceValue));
}

/** Get the calling instance using the copy behavior defined by the executor. */
public Optional<Object> getCallingInstance(Executor executor, ReferenceValue instanceValue) {
return (instanceValue != null && instanceValue.isParticular() && !isConstructor)
? Optional.of(executor.getInstanceCopyIfMutable(instanceValue, signature.getClassName()))
: Optional.empty();
}

/** Get the type of the instance. */
public Optional<String> getInstanceType(ReferenceValue instanceValue) {
return instanceValue != null ? Optional.of(instanceValue.internalType()) : Optional.empty();
}

/** Return whether the return and instance types of the method match. */
public boolean returnsSameTypeAsInstance(Value[] parameters) {
return getInstanceValue(parameters).map(this::returnsSameTypeAsInstance).orElse(false);
}

/** Return whether the return and instance types of the method match. */
public boolean returnsSameTypeAsInstance(Value instance) {
return getInstanceValue(instance).map(this::returnsSameTypeAsInstance).orElse(false);
}

/** Return whether the return and instance types of the method match. */
public boolean returnsSameTypeAsInstance(ReferenceValue instance) {
return getInstanceValue(instance)
.map(
instanceValue ->
getInstanceType(instanceValue)
.map(instanceType -> instanceType.equals(returnType))
.orElse(false))
.orElse(false);
}
}
Loading
Loading