/******************************************************************************* * Copyright (c) 2013 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.wala.cast.js.ipa.callgraph; import com.ibm.wala.analysis.reflection.InstanceKeyWithNode; import com.ibm.wala.cast.ipa.callgraph.ScopeMappingInstanceKeys.ScopeMappingInstanceKey; import com.ibm.wala.cast.js.ipa.summaries.JavaScriptConstructorFunctions.JavaScriptConstructor; import com.ibm.wala.classLoader.CallSiteReference; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.callgraph.CGNode; import com.ibm.wala.ipa.callgraph.Context; import com.ibm.wala.ipa.callgraph.ContextKey; import com.ibm.wala.ipa.callgraph.ContextSelector; import com.ibm.wala.ipa.callgraph.impl.Everywhere; import com.ibm.wala.ipa.callgraph.propagation.FilteredPointerKey; import com.ibm.wala.ipa.callgraph.propagation.FilteredPointerKey.SingleInstanceFilter; import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; import com.ibm.wala.ipa.callgraph.propagation.cfa.CallString; import com.ibm.wala.ipa.callgraph.propagation.cfa.CallStringContext; import com.ibm.wala.util.intset.IntSet; /** * A context selector that attempts to detect recursion beyond some depth in a * base selector. If such recursion is detected, the base selector's context is * replaced with {@link Everywhere#EVERYWHERE}. */ public class RecursionBoundContextSelector implements ContextSelector { private final ContextSelector base; private final int recursionBound; /** * the highest parameter index that we'll check . this is a HACK. ideally, * given a context, we'd have some way to know all the {@link ContextKey}s * that it knows about. * * @see ContextKey#PARAMETERS */ private static final int MAX_INTERESTING_PARAM = 5; /** * @param base * @param recursionBound * bound on recursion depth, with the top level of the context * returned by the base selector being depth 0. The * {@link Everywhere#EVERYWHERE} context is returned if the base * context <em>exceeds</em> this bound. */ public RecursionBoundContextSelector(ContextSelector base, int recursionBound) { this.base = base; this.recursionBound = recursionBound; } @Override public Context getCalleeTarget(CGNode caller, CallSiteReference site, IMethod callee, InstanceKey[] actualParameters) { Context baseContext = base.getCalleeTarget(caller, site, callee, actualParameters); final boolean exceedsRecursionBound = exceedsRecursionBound(baseContext, 0); if (!exceedsRecursionBound) { return baseContext; } else if (callee instanceof JavaScriptConstructor) { // for constructors, we want to keep some basic context sensitivity to // avoid horrible imprecision return new CallStringContext(new CallString(site, caller.getMethod())); } else { // TODO somehow k-limit more smartly? return Everywhere.EVERYWHERE; } } private boolean exceedsRecursionBound(Context baseContext, int curLevel) { if (curLevel > recursionBound) { return true; } // we just do a case analysis here. we might have to add cases later to // account for new types of context / recursion. CGNode callerNode = (CGNode) baseContext.get(ContextKey.CALLER); if (callerNode != null && exceedsRecursionBound(callerNode.getContext(), curLevel + 1)) { return true; } for (int i = 0; i < MAX_INTERESTING_PARAM; i++) { FilteredPointerKey.SingleInstanceFilter filter = (SingleInstanceFilter) baseContext.get(ContextKey.PARAMETERS[i]); if (filter != null) { InstanceKey ik = filter.getInstance(); if (ik instanceof ScopeMappingInstanceKey) { ik = ((ScopeMappingInstanceKey) ik).getBase(); } if (ik instanceof InstanceKeyWithNode) { if (exceedsRecursionBound(((InstanceKeyWithNode) ik).getNode().getContext(), curLevel + 1)) { return true; } } } } return false; } @Override public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) { return base.getRelevantParameters(caller, site); } }