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

it.unive.lisa.LiSAFactory Maven / Gradle / Ivy

package it.unive.lisa;

import it.unive.lisa.analysis.AbstractState;
import it.unive.lisa.analysis.SimpleAbstractState;
import it.unive.lisa.analysis.dataflow.DataflowElement;
import it.unive.lisa.analysis.dataflow.DefiniteForwardDataflowDomain;
import it.unive.lisa.analysis.dataflow.PossibleForwardDataflowDomain;
import it.unive.lisa.analysis.heap.HeapDomain;
import it.unive.lisa.analysis.heap.MonolithicHeap;
import it.unive.lisa.analysis.nonrelational.NonRelationalDomain;
import it.unive.lisa.analysis.nonrelational.heap.HeapEnvironment;
import it.unive.lisa.analysis.nonrelational.heap.NonRelationalHeapDomain;
import it.unive.lisa.analysis.nonrelational.inference.InferenceSystem;
import it.unive.lisa.analysis.nonrelational.inference.InferredValue;
import it.unive.lisa.analysis.nonrelational.value.NonRelationalValueDomain;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.analysis.numeric.Interval;
import it.unive.lisa.analysis.value.ValueDomain;
import it.unive.lisa.interprocedural.ContextBasedAnalysis;
import it.unive.lisa.interprocedural.ContextSensitivityToken;
import it.unive.lisa.interprocedural.InterproceduralAnalysis;
import it.unive.lisa.interprocedural.ModularWorstCaseAnalysis;
import it.unive.lisa.interprocedural.RecursionFreeToken;
import it.unive.lisa.interprocedural.callgraph.CallGraph;
import it.unive.lisa.interprocedural.callgraph.RTACallGraph;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;

/**
 * An utility class for instantiating analysis components, that is, modular
 * pieces of the analysis that have several implementations. The default
 * instance for a component can be retrieved through
 * {@link #getDefaultFor(Class, Object...)}, while a specific instance can be
 * retrieved through {@link #getInstance(Class, Object...)}. Note that custom
 * defaults for each component can be defined through
 * {@link #registerDefaultFor(Class, Class)}.
 * 
 * @author Luca Negrini
 */
public final class LiSAFactory {

	/**
	 * Default implementation for analysis components. Keys of this map are the
	 * class objects of the analysis components (e.g.
	 * {@link CallGraph}{@code .class}) while values are the key's default
	 * implementation's class object (e.g. {@link RTACallGraph}{@code .class}).
	 */
	static final Map, Class> DEFAULT_IMPLEMENTATIONS = new HashMap<>();

	/**
	 * Default parameters types for analysis components' implementations. Keys
	 * of this map are the class objects of the analysis components (e.g.
	 * {@link SimpleAbstractState}{@code .class}) while values are arrays
	 * containing the key's default parameters' class objects (e.g.
	 * {@code [}{@link MonolithicHeap}{@code .class, }{@link Interval}{@code .class]}).
	 */
	static final Map, Class[]> DEFAULT_PARAMETERS = new HashMap<>();

	static {
		DEFAULT_IMPLEMENTATIONS.put(InterproceduralAnalysis.class, ModularWorstCaseAnalysis.class);
		DEFAULT_IMPLEMENTATIONS.put(CallGraph.class, RTACallGraph.class);
		DEFAULT_IMPLEMENTATIONS.put(HeapDomain.class, MonolithicHeap.class);
		DEFAULT_IMPLEMENTATIONS.put(ValueDomain.class, Interval.class);
		DEFAULT_IMPLEMENTATIONS.put(AbstractState.class, SimpleAbstractState.class);
		DEFAULT_PARAMETERS.put(SimpleAbstractState.class, new Class[] { MonolithicHeap.class, Interval.class });
		DEFAULT_PARAMETERS.put(ContextBasedAnalysis.class, new Class[] { RecursionFreeToken.class });
	}

	private LiSAFactory() {
		// this class is just a static holder
	}

	@SuppressWarnings("unchecked")
	private static  T construct(Class component, Class[] argTypes, Object[] params)
			throws AnalysisSetupException {
		if (ContextSensitivityToken.class.isAssignableFrom(component)) {
			// tokens use the getSingleton() pattern for construction
			try {
				Method method = component.getMethod("getSingleton");
				if (!Modifier.isStatic(method.getModifiers()))
					throw new AnalysisSetupException(
							"Unable to instantiate " + component.getSimpleName() + ": getSingleton() is not static");
				return (T) method.invoke(null);
			} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
					| InvocationTargetException e) {
				throw new AnalysisSetupException("Unable to instantiate " + component.getSimpleName(), e);
			}
		}

		try {
			Constructor constructor = component.getConstructor(argTypes);
			return constructor.newInstance(params);
		} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
				| IllegalArgumentException
				| InvocationTargetException e) {
			throw new AnalysisSetupException("Unable to instantiate " + component.getSimpleName(), e);
		}
	}

	private static Class[] findConstructorSignature(Class component, Object[] params)
			throws AnalysisSetupException {
		Map, List> candidates = new IdentityHashMap<>();
		Class[] types;
		outer: for (Constructor constructor : component.getConstructors()) {
			types = constructor.getParameterTypes();
			if (params.length != types.length)
				continue;

			List toWrap = new ArrayList<>();
			for (int i = 0; i < types.length; i++)
				if (needsWrapping(params[i].getClass(), types[i]))
					toWrap.add(i);
				else if (!types[i].isAssignableFrom(params[i].getClass()))
					continue outer;

			candidates.put(constructor, toWrap);
		}

		if (candidates.isEmpty())
			throw new AnalysisSetupException(
					"No suitable constructor of " + component.getSimpleName() + " found for argument types "
							+ Arrays.toString(Arrays.stream(params).map(Object::getClass).toArray(Class[]::new)));

		if (candidates.size() > 1)
			throw new AnalysisSetupException(
					"Constructor call of " + component.getSimpleName() + " is ambiguous for argument types "
							+ Arrays.toString(Arrays.stream(params).map(Object::getClass).toArray(Class[]::new)));

		for (int p : candidates.values().iterator().next())
			params[p] = wrapParam(params[p]);

		return candidates.keySet().iterator().next().getParameterTypes();
	}

	private static boolean needsWrapping(Class actual, Class desired) {
		if (NonRelationalHeapDomain.class.isAssignableFrom(actual) && desired.isAssignableFrom(HeapDomain.class))
			return true;
		else if (NonRelationalValueDomain.class.isAssignableFrom(actual) && desired.isAssignableFrom(ValueDomain.class))
			return true;
		else if (InferredValue.class.isAssignableFrom(actual) && desired.isAssignableFrom(ValueDomain.class))
			return true;
		else if (DataflowElement.class.isAssignableFrom(actual) && desired.isAssignableFrom(ValueDomain.class))
			return true;
		else
			return false;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static Object wrapParam(Object param) {
		if (NonRelationalHeapDomain.class.isAssignableFrom(param.getClass()))
			return new HeapEnvironment((NonRelationalHeapDomain) param);
		else if (NonRelationalValueDomain.class.isAssignableFrom(param.getClass()))
			return new ValueEnvironment((NonRelationalValueDomain) param);
		else if (InferredValue.class.isAssignableFrom(param.getClass()))
			return new InferenceSystem((InferredValue) param);
		else if (DataflowElement.class.isAssignableFrom(param.getClass())) {
			Class elem = (Class) param.getClass();
			if (elem.getGenericInterfaces().length == 0)
				return param;

			for (Type gi : elem.getGenericInterfaces())
				if (gi instanceof ParameterizedType && ((ParameterizedType) gi).getRawType() == DataflowElement.class) {
					Type domain = ((ParameterizedType) gi).getActualTypeArguments()[0];
					if (((ParameterizedType) domain).getRawType() == PossibleForwardDataflowDomain.class)
						return new PossibleForwardDataflowDomain((DataflowElement) param);
					else if (((ParameterizedType) domain).getRawType() == DefiniteForwardDataflowDomain.class)
						return new DefiniteForwardDataflowDomain((DataflowElement) param);
					else
						return param;
				}
		}
		return param;
	}

	/**
	 * Creates an instance of the given {@code component}. If {@code params} are
	 * provided, a suitable (and not ambiguous) constructor must exist in
	 * {@code component}'s class. If no {@code params} have been provided but
	 * default parameters have been registered through
	 * {@link #registerDefaultParametersFor(Class, Class...)},
	 * {@link #getInstance(Class, Object...)} will be used to create such
	 * parameters and those will be used. Otherwise, the nullary constructor of
	 * {@code component} is invoked.
	 * 
	 * @param        the type of the component
	 * @param component the component to instantiate
	 * @param params    the parameters for the creation
	 * 
	 * @return an instance of the given component
	 * 
	 * @throws AnalysisSetupException if the component cannot be created
	 */
	public static  T getInstance(Class component, Object... params) throws AnalysisSetupException {
		try {
			if (params != null && params.length != 0)
				return construct(component, findConstructorSignature(component, params), params);

			Class[] defaultParams;
			if (!DEFAULT_PARAMETERS.containsKey(component)
					|| (defaultParams = DEFAULT_PARAMETERS.get(component)) == null
					|| defaultParams.length == 0)
				return construct(component, ArrayUtils.EMPTY_CLASS_ARRAY, ArrayUtils.EMPTY_OBJECT_ARRAY);

			Object[] defaults = new Object[defaultParams.length];
			for (int i = 0; i < defaults.length; i++)
				defaults[i] = getInstance(defaultParams[i]);

			return construct(component, findConstructorSignature(component, defaults), defaults);
		} catch (NullPointerException e) {
			throw new AnalysisSetupException("Unable to instantiate default " + component.getSimpleName(), e);
		}
	}

	/**
	 * Registers a default implementation for {@code component}, taking
	 * precedence over the predefined defaults. Any previous default for
	 * {@code component} introduced by calling this method is removed.
	 * 
	 * @param component             the component whose default implementation
	 *                                  is to be registered
	 * @param defaultImplementation the new default implementation for
	 *                                  {@code component}
	 */
	public static void registerDefaultFor(Class component, Class defaultImplementation) {
		DEFAULT_IMPLEMENTATIONS.put(component, defaultImplementation);
	}

	/**
	 * Registers the types (i.e. implementations) of the parameters to use for
	 * the creation of the given component. These will be used whenever
	 * {@link #getInstance(Class, Object...)} or
	 * {@link #getDefaultFor(Class, Object...)} are invoked without specifying
	 * parameters but a default exist. Any previous default for
	 * {@code component} introduced by calling this method is removed.
	 * 
	 * @param component         the component whose default parameters are to be
	 *                              registered
	 * @param defaultParameters the new parameters for the creation of
	 *                              {@code component}
	 */
	public static void registerDefaultParametersFor(Class component, Class... defaultParameters) {
		DEFAULT_PARAMETERS.put(component, defaultParameters);
	}

	/**
	 * Builds the default instance of the specified analysis component. The
	 * instance to create is retrieved by first looking into the custom defaults
	 * provided through {@link #registerDefaultFor(Class, Class)}. If no entry
	 * for {@code component} has been provided, then the instance is looked up
	 * in the predefined defaults. If {@code component} does not have a
	 * predefined default, then an {@link AnalysisSetupException} is thrown.
	 * Then, {@link #getInstance(Class, Object...)} is invoked on the retrieved
	 * instance, using the given {@code params}. If no {@code params} have been
	 * provided but default parameters have been registered through
	 * {@link #registerDefaultParametersFor(Class, Class...)},
	 * {@link #getInstance(Class, Object...)} will be used to create such
	 * parameters and those will be used. If the default instance is a
	 * {@link NonRelationalDomain} and the component is a {@link HeapDomain} or
	 * {@link ValueDomain}, then the instance is wrapped into the appropriate
	 * environment (either {@link HeapEnvironment} or {@link ValueEnvironment})
	 * before being returned.
	 * 
	 * @param        the type of the component
	 * @param component the component to instantiate
	 * @param params    the parameters for the creation of the default instance
	 * 
	 * @return an instance of the default implementation of the given component
	 * 
	 * @throws AnalysisSetupException if the default implementation cannot be
	 *                                    created
	 */
	@SuppressWarnings("unchecked")
	public static  T getDefaultFor(Class component, Object... params) throws AnalysisSetupException {
		try {
			Class def = DEFAULT_IMPLEMENTATIONS.get(component);
			if (def == null)
				throw new AnalysisSetupException("No registered default for " + component);

			Class[] defParams = DEFAULT_PARAMETERS.getOrDefault(def, ArrayUtils.EMPTY_CLASS_ARRAY);
			if (params.length == 0 && defParams.length > 0) {
				params = new Object[defParams.length];
				for (int i = 0; i < params.length; i++)
					params[i] = getInstance(defParams[i]);
			}

			if (needsWrapping(def, component))
				return (T) wrapParam(getInstance(def, params));
			else
				return (T) getInstance(def, params);
		} catch (NullPointerException e) {
			throw new AnalysisSetupException("Unable to instantiate default " + component.getSimpleName(), e);
		}
	}

	/**
	 * An analysis component that can be configured, that is, it has more than
	 * one implementation that can be modularly integrated into the analysis.
	 * {@link #getComponent()} yields the component itself, i.e. the interface
	 * or abstract class that defines the analysis components.
	 * {@link #getDefaultInstance()} yields the default implementation of the
	 * component that will be used if no specific implementation is requested.
	 * {@link #getAlternatives()} yields all the concrete implementations of the
	 * components.
	 * 
	 * @author Luca Negrini
	 * 
	 * @param  the type of the component
	 */
	public static final class ConfigurableComponent {
		private static final Reflections scanner = new Reflections(LiSA.class, new SubTypesScanner());

		private final Class component;
		private final Class defaultInstance;
		private final Class[] defaultParameters;
		private final Collection> alternatives;

		@SuppressWarnings("unchecked")
		private ConfigurableComponent(Class component) {
			this.component = component;

			if (DEFAULT_IMPLEMENTATIONS.containsKey(component)) {
				defaultInstance = (Class) DEFAULT_IMPLEMENTATIONS.get(component);
				if (DEFAULT_PARAMETERS.containsKey(defaultInstance))
					defaultParameters = DEFAULT_PARAMETERS.get(defaultInstance);
				else
					defaultParameters = null;
			} else {
				defaultInstance = null;
				defaultParameters = null;
			}

			this.alternatives = scanner.getSubTypesOf(component)
					.stream()
					.map(c -> Pair.of(c, c.getModifiers()))
					.filter(p -> !Modifier.isAbstract(p.getRight()) && !Modifier.isInterface(p.getRight()))
					.map(p -> p.getLeft())
					.collect(Collectors.toList());
		}

		/**
		 * Yields the component represented by this
		 * {@link ConfigurableComponent}.
		 * 
		 * @return the analysis component
		 */
		public Class getComponent() {
			return component;
		}

		/**
		 * Yields the default implementation for this component, that is, the
		 * concrete class that implements it and that will be used if the
		 * component is requested but the user did not specify which
		 * implementation to use (among the ones offered by
		 * {@link #getAlternatives()}. Might be {@code null} if no default is
		 * set.
		 * 
		 * @return the default implementation for this component
		 */
		public Class getDefaultInstance() {
			return defaultInstance;
		}

		/**
		 * Yields the classes of the parameters passed by-default to the
		 * constructor of the default implementation for this component. Might
		 * be {@code null} if no default is set, or might be an empty array if
		 * the default implementation does not require parameters.
		 * 
		 * @return the classes of the parameters for the construction of the
		 *             default implementation for this component
		 */
		public Class[] getDefaultParameters() {
			return defaultParameters;
		}

		/**
		 * Yields the alternatives for this component, that is, the concrete
		 * classes that implements it.
		 * 
		 * @return the alternatives for this component
		 */
		public Collection> getAlternatives() {
			return alternatives;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((alternatives == null) ? 0 : alternatives.hashCode());
			result = prime * result + ((component == null) ? 0 : component.hashCode());
			result = prime * result + ((defaultInstance == null) ? 0 : defaultInstance.hashCode());
			result = prime * result + ((defaultParameters == null) ? 0 : Arrays.hashCode(defaultParameters));
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			ConfigurableComponent other = (ConfigurableComponent) obj;
			if (alternatives == null) {
				if (other.alternatives != null)
					return false;
			} else if (!alternatives.equals(other.alternatives))
				return false;
			if (component == null) {
				if (other.component != null)
					return false;
			} else if (!component.equals(other.component))
				return false;
			if (defaultInstance == null) {
				if (other.defaultInstance != null)
					return false;
			} else if (!defaultInstance.equals(other.defaultInstance))
				return false;
			if (defaultParameters == null) {
				if (other.defaultParameters != null)
					return false;
			} else if (!Arrays.equals(defaultParameters, other.defaultParameters))
				return false;
			return true;
		}

		@Override
		public String toString() {
			String result = component.getName();
			if (defaultInstance != null) {
				result += " (defaults to: '" + defaultInstance.getName() + "'";
				if (defaultParameters != null && defaultParameters.length != 0) {
					String[] paramNames = Arrays.stream(defaultParameters).map(c -> c.getName()).toArray(String[]::new);
					result += " with parameters [" + StringUtils.join(paramNames, ", ") + "]";
				}
				result += ")";
			}
			String[] alternatives = this.alternatives.stream().map(c -> c.getName()).toArray(String[]::new);
			result += " possible implementations: " + alternatives;
			return result;
		}
	}

	/**
	 * Yields the collection of {@link ConfigurableComponent}s that can be used
	 * to customize the analysis.
	 * 
	 * @return the components that can be configured
	 */
	public static Collection> configurableComponents() {
		Collection> in = new ArrayList<>();
		in.add(new ConfigurableComponent<>(InterproceduralAnalysis.class));
		in.add(new ConfigurableComponent<>(CallGraph.class));
		in.add(new ConfigurableComponent<>(AbstractState.class));
		in.add(new ConfigurableComponent<>(HeapDomain.class));
		in.add(new ConfigurableComponent<>(ValueDomain.class));
		in.add(new ConfigurableComponent<>(NonRelationalHeapDomain.class));
		in.add(new ConfigurableComponent<>(NonRelationalValueDomain.class));
		in.add(new ConfigurableComponent<>(InferredValue.class));
		in.add(new ConfigurableComponent<>(DataflowElement.class));
		return in;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy