All Downloads are FREE. Search and download functionalities are using the official Maven repository.

proguard.evaluation.executor.ReflectiveModelExecutor Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.6
Show newest version
package proguard.evaluation.executor;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import proguard.classfile.AccessConstants;
import proguard.classfile.Clazz;
import proguard.classfile.MethodInfo;
import proguard.classfile.MethodSignature;
import proguard.classfile.util.ClassUtil;
import proguard.classfile.util.InitializedClassUtil;
import proguard.evaluation.MethodResult;
import proguard.evaluation.ValueCalculator;
import proguard.evaluation.value.ReferenceValue;
import proguard.evaluation.value.TypedReferenceValue;
import proguard.evaluation.value.Value;
import proguard.evaluation.value.object.AnalyzedObject;
import proguard.evaluation.value.object.model.Model;
import proguard.evaluation.value.object.model.reflective.ModelHelper;
import proguard.evaluation.value.object.model.reflective.ReflectiveModel;
import proguard.util.HierarchyProvider;

/**
 * An {@link Executor} with support for {@link ReflectiveModel}s.
 *
 * 

Checks the parameters sanity so that the models can work assuming they get clean data and * matches the target {@link ReflectiveModel} and executes the method on it via the reflection * supported by {@link ModelHelper}. */ public class ReflectiveModelExecutor implements Executor { private static final Logger log = LogManager.getLogger(ReflectiveModelExecutor.class); protected final Set>> supportedModels; protected final Set supportedSignatures; protected final Map>> supportedSignatureToModel; protected ReflectiveModelExecutor( Set> supportedModels, HierarchyProvider hierarchy) { this.supportedModels = supportedModels; Set supportedSignaturesMutable = new HashSet<>(); Map>> supportedSignatureToModelMutable = new HashMap<>(); for (SupportedModelInfo> modelInfo : supportedModels) { generateMethodSignaturesForModel(modelInfo, hierarchy) .forEach( signature -> { supportedSignaturesMutable.add(signature); supportedSignatureToModelMutable.put(signature, modelInfo.getModelClass()); }); } this.supportedSignatures = Collections.unmodifiableSet(supportedSignaturesMutable); this.supportedSignatureToModel = Collections.unmodifiableMap(supportedSignatureToModelMutable); } @Override public MethodResult getMethodResult( MethodExecutionInfo methodExecutionInfo, ValueCalculator valueCalculator) { checkInstanceSanity(methodExecutionInfo, supportedSignatureToModel); if (!canExecuteWithInstance(methodExecutionInfo)) { return MethodResult.invalidResult(); } checkParameterSanity(methodExecutionInfo, supportedSignatureToModel); Model instanceModel = getInstanceModel(methodExecutionInfo); return execute(methodExecutionInfo, valueCalculator, instanceModel); } /** * For instance methods returns the instance model, for static location and constructors returns a * dummy model on which the invocation can be performed. */ private > T getInstanceModel( MethodExecutionInfo methodExecutionInfo) { ReferenceValue callingInstance = methodExecutionInfo.getInstanceOrNullIfStatic(); boolean isInstanceParticular = callingInstance != null && callingInstance.isParticular(); T instanceModel; // Set up the calling instance, a dummy instance is used for constructors and static methods, // as well as when the instance is not particular if (methodExecutionInfo.isConstructor() || methodExecutionInfo.isStatic() || !isInstanceParticular) { Class targetClass = (Class) Objects.requireNonNull( supportedSignatureToModel.get(methodExecutionInfo.getResolvedTargetSignature()), "The target signature has been already checked to be supported"); instanceModel = ModelHelper.getDummyObject(targetClass); } else { // Since we are not analyzing a static method the instance has to be not-null instanceModel = (T) Objects.requireNonNull(callingInstance).getValue().getModeledValue(); } return instanceModel; } private MethodResult execute( MethodExecutionInfo methodExecutionInfo, ValueCalculator valueCalculator, Model instanceModel) { if (methodExecutionInfo.isConstructor()) { return instanceModel.init(methodExecutionInfo, valueCalculator); } if (methodExecutionInfo.isStatic()) { return instanceModel.invokeStatic(methodExecutionInfo, valueCalculator); } return instanceModel.invoke(methodExecutionInfo, valueCalculator); } @Override public Set getSupportedMethodSignatures() { return supportedSignatures; } // Methods for arguments sanity checks /** * Checks sanity of the instance, represented as a {@link ReferenceValue} object wrapping an * {@link AnalyzedObject} object. The sanity checks performed are: * *

    *
  • For static methods, the instance Value can't be non-null *
  • Vice-versa, for instance methods, it must be non-null *
  • For particular instance Values, the underlying object must be non-null *
  • The value must be modeled *
* * @param methodInfo The method execution information. * @param supportedSignatureToModel A mapping from the signature of a method supported by the * executor and the model class that should handle that method invocation. * @throws IllegalStateException If any of the sanity checks fail. */ private void checkInstanceSanity( MethodExecutionInfo methodInfo, Map>> supportedSignatureToModel) { ReferenceValue instanceValue = methodInfo.getInstanceOrNullIfStatic(); // Static methods can always be executed and the instance is expected to not be assigned if (methodInfo.isStatic() && instanceValue != null) { throw new IllegalStateException("No instance expected for static methods."); } else if (methodInfo.isStatic()) { // Static methods don't use an instance, hence need no further checking return; } // Vice-versa, for a non-static method, we need a non-null instance Value object if (instanceValue == null) { throw new IllegalStateException("Unexpected null instance value for an instance method."); } // The instance is null, which would cause an NPE in the analyzed code, the case is handled // explicitly later. if (instanceValue.isParticular() && instanceValue.getValue().isNull()) { return; } // Special checks for particular instances // Check if the Value is modeled, since this executor only supports models. if (instanceValue.isParticular()) { ReferenceValue instanceReferenceValue = instanceValue.referenceValue(); if (!instanceReferenceValue.getValue().isModeled()) { throw new IllegalStateException( "Should not use ReflectiveModelExecutor on non-model instances, are you sure you are matching the expected executor?"); } checkArgumentCorrectModel(instanceReferenceValue.getValue(), supportedSignatureToModel); } } /** * Additional validity checks on instances that the modeled instances themselves cannot perform * for lack of information, namely: * *
    *
  • For constructors, if the instance Value is specific, i.e. identified. *
  • For instance methods, whether the instance is null in the analyzed code. *
* * @param methodInfo The method execution information. * @return Whether the executor is able to provide a valid result given the instance. */ private boolean canExecuteWithInstance(MethodExecutionInfo methodInfo) { if (methodInfo.isStatic()) { return true; } ReferenceValue instanceValue = methodInfo.getInstanceNonStatic(); if (methodInfo.isConstructor()) { return instanceValue.isSpecific(); } // Null instance value would be an NPE, but the analysis doesn't support // exceptional paths, so we approximate by continuing the analysis with an unknown value. return !instanceValue.isParticular() || !instanceValue.getValue().isNull(); } /** * Performs sanity checking on a list of parameters. The sanity checks consists of: * *
    *
  • The length of the parameter list is equal to arguments specified in the descriptor. *
  • For each parameter, the type matches (including inheritance), and *
  • If it is a modeled value, the correct Model implementation is used. *
* * @throws IllegalStateException If any of the above checks fail. */ private void checkParameterSanity( MethodExecutionInfo executionInfo, Map>> supportedSignatureToModel) { List parameters = executionInfo.getParameters(); List parameterTypes = executionInfo.getSignature().descriptor.getArgumentTypes(); if (parameterTypes.size() != parameters.size()) { throw new IllegalStateException( "The number of provided parameters is different from the number of parameters specified in the called method signature"); } for (int i = 0; i < parameters.size(); i++) { Value parameter = parameters.get(i); String staticType = parameterTypes.get(i); Clazz parameterClass = executionInfo.getParametersClasses().get(i); checkParameter(staticType, parameter, parameterClass, supportedSignatureToModel); } } private void checkParameter( String staticType, Value parameter, Clazz parameterClass, Map>> supportedSignatureToModel) { if (ClassUtil.isInternalPrimitiveType(staticType)) { if (ClassUtil.internalPrimitiveTypeToComputationalType(staticType) != ClassUtil.internalPrimitiveTypeToComputationalType(parameter.internalType())) { throw new IllegalStateException( String.format( "The parameter should be primitive, but the computational type of \"%s\" and \"%s\" do not correspond", parameter.internalType(), staticType)); } } else { ReferenceValue parameterValue = parameter.referenceValue(); String parameterType = parameterValue.getType(); // Null parameter is always valid // We also can't check if the parameter has the right type if we have no type information if (parameterType == null || (parameterValue.isParticular() && parameterValue.getValue().isNull()) || !(parameterValue instanceof TypedReferenceValue)) { return; } if (isParameterTypeInvalid(parameterType, parameterClass, staticType)) { throw new IllegalStateException( String.format( "Parameter type is \"%s\", which does not match or inherit from " + "the static type \"%s\"", parameterType, staticType)); } if (parameterValue.isParticular() && parameterValue.getValue().isModeled()) { checkArgumentCorrectModel(parameterValue.getValue(), supportedSignatureToModel); } } } /** * A parameter is invalid if all of these conditions hold: * *
    *
  • Its type doesn't correspond to the static type in the signature *
  • Does not extend/implement the target Clazz *
*/ private boolean isParameterTypeInvalid( String parameterType, Clazz parameterClass, String staticType) { return !parameterType.equals(staticType) && parameterClass != null && !InitializedClassUtil.isInstanceOf(parameterType, parameterClass); } private void checkArgumentCorrectModel( AnalyzedObject parameterObject, Map>> supportedSignatureToModel) { for (Map.Entry>> entry : supportedSignatureToModel.entrySet()) { if (isArgumentWrongModel( entry.getKey(), entry.getValue(), parameterObject.getType(), parameterObject.getModeledValue())) { throw new IllegalArgumentException( String.format( "The parameter is of the type supported by ReflectiveModelExecutor \"%s\", but the model is of class \"%s\" instead of the model expected by the executor \"%s\"", entry.getKey(), parameterObject.getModeledValue().getClass(), supportedSignatureToModel.get(entry.getKey()))); } } } private boolean isArgumentWrongModel( MethodSignature supportedSignature, Class supportedModel, @Nullable String parameterType, Model parameterModel) { return ClassUtil.internalTypeFromClassName(supportedSignature.getClassName()) .equals(parameterType) && !supportedModel.isInstance(parameterModel); } // Static methods /** * Computes the methods the executor needs to support for the specified classes. This consists in * all the methods implemented by the specified {@link ReflectiveModel} and additionally a * signature for each class extending the class if inheritance has to be supported. * * @param modelInfo information on which classes the executor wants to support (e.g., only the * class modeled by a model or also everything implementing it, etc.) * @param hierarchy the class hierarchy. * @return the signatures the executor supports for the clazz. * @param the model modeling the clazz. */ public static > Set generateMethodSignaturesForModel( SupportedModelInfo modelInfo, HierarchyProvider hierarchy) { T dummy = ModelHelper.getDummyObject(modelInfo.modelClass); String className = ClassUtil.internalClassNameFromType(dummy.getType()); Clazz clazz = hierarchy.getClazz(className); if (clazz == null) { log.info( "The class hierarchy does not contain {}, the executor won't be able to execute methods from it", className); return Collections.emptySet(); } if ((clazz.getAccessFlags() & AccessConstants.INTERFACE) != 0) { log.error( "This method has been designed for classes, if it needs to be called for an interface explicit support should be added"); return Collections.emptySet(); } Set supportedMethods = new HashSet<>(); for (MethodInfo methodInfo : ModelHelper.getSupportedMethods(modelInfo.modelClass)) { if (modelInfo.supportsFullInheritance) { for (String subClassName : hierarchy.getSubClasses(className)) { MethodSignature signature = new MethodSignature( subClassName, methodInfo.getMethodName(), methodInfo.getDescriptor()); supportedMethods.add(signature); } } MethodSignature signature = new MethodSignature(className, methodInfo.getMethodName(), methodInfo.getDescriptor()); supportedMethods.add(signature); } return supportedMethods; } /** * Information provided by an executor to communicate which classes it supports. * * @param recursive generic type of the model. */ public static final class SupportedModelInfo> { private final Class modelClass; private final boolean supportsFullInheritance; /** * @param modelClass a clazz of a model. * @param supportsFullInheritance whether the executor should support classes implementing the * modeled class. "Full" support for inheritance means all methods, regardless of the fact * they override the modeled method or not. */ public SupportedModelInfo(Class modelClass, boolean supportsFullInheritance) { this.modelClass = modelClass; this.supportsFullInheritance = supportsFullInheritance; } public Class getModelClass() { return modelClass; } public boolean isSupportsFullInheritance() { return supportsFullInheritance; } } /** Builder for {@link ReflectiveModelExecutor}. */ public static class Builder implements Executor.Builder { private final Set>> supportedModels = new HashSet<>(); private final HierarchyProvider hierarchy; /** * Construct the builder. * * @param hierarchy the class hierarchy. */ public Builder(HierarchyProvider hierarchy) { this.hierarchy = hierarchy; } /** * Add a model to support. * * @param modelInfo information on which model to support and how to support it. * @return the builder. * @param recursive generic type of the model. */ public > Builder addSupportedModel( SupportedModelInfo modelInfo) { supportedModels.add(modelInfo); return this; } /** * Add a models to support. * * @param modelInfo information on which models to support and how to support each of them. * @return the builder. */ public Builder addSupportedModels( Collection>> modelInfo) { supportedModels.addAll(modelInfo); return this; } @Override public ReflectiveModelExecutor build() { return new ReflectiveModelExecutor(supportedModels, hierarchy); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy