/****************************************************************************** * Copyright (c) 2011 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.correlations; import java.io.File; import java.io.FileNotFoundException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import com.ibm.wala.cast.ipa.callgraph.CAstAnalysisScope; import com.ibm.wala.cast.ir.ssa.AbstractReflectiveGet; import com.ibm.wala.cast.ir.ssa.AbstractReflectivePut; import com.ibm.wala.cast.ir.ssa.AstIRFactory; import com.ibm.wala.cast.ir.translator.TranslatorToCAst.Error; import com.ibm.wala.cast.js.html.DefaultSourceExtractor; import com.ibm.wala.cast.js.html.WebUtil; import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil; import com.ibm.wala.cast.js.loader.JavaScriptLoader; import com.ibm.wala.cast.js.loader.JavaScriptLoaderFactory; import com.ibm.wala.cast.js.translator.JavaScriptTranslatorFactory; import com.ibm.wala.cast.loader.AstMethod; import com.ibm.wala.cast.loader.AstMethod.LexicalInformation; import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.classLoader.SourceModule; import com.ibm.wala.classLoader.SourceURLModule; import com.ibm.wala.ipa.callgraph.impl.Everywhere; import com.ibm.wala.ipa.cha.ClassHierarchyException; import com.ibm.wala.ipa.cha.ClassHierarchyFactory; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.shrikeBT.IBinaryOpInstruction; import com.ibm.wala.shrikeBT.IBinaryOpInstruction.IOperator; import com.ibm.wala.ssa.DefUse; import com.ibm.wala.ssa.IR; import com.ibm.wala.ssa.IRFactory; import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; import com.ibm.wala.ssa.SSABinaryOpInstruction; import com.ibm.wala.ssa.SSAInstruction; import com.ibm.wala.ssa.SSAOptions; import com.ibm.wala.ssa.SSAPhiInstruction; import com.ibm.wala.util.WalaException; import com.ibm.wala.util.collections.HashMapFactory; import com.ibm.wala.util.collections.Iterator2Iterable; import com.ibm.wala.util.collections.ObjectArrayMapping; import com.ibm.wala.util.collections.Pair; import com.ibm.wala.util.intset.BitVectorIntSet; import com.ibm.wala.util.intset.IntIterator; import com.ibm.wala.util.intset.IntSet; import com.ibm.wala.util.intset.MutableIntSet; import com.ibm.wala.util.intset.OrdinalSetMapping; import com.ibm.wala.util.io.FileProvider; /** * Helper class for identifying correlated read/write pairs. * * @author mschaefer * */ public class CorrelationFinder { private final static boolean TRACK_ESCAPES = true; private final static boolean IGNORE_NUMERIC_INDICES = false; private final JavaScriptTranslatorFactory translatorFactory; public CorrelationFinder(JavaScriptTranslatorFactory translatorFactory) { this.translatorFactory = translatorFactory; } @SuppressWarnings("unused") public static CorrelationSummary findCorrelatedAccesses(IMethod method, IR ir) { AstMethod astMethod = (AstMethod)method; DefUse du = new DefUse(ir); OrdinalSetMapping instrIndices = new ObjectArrayMapping<>(ir.getInstructions()); CorrelationSummary summary = new CorrelationSummary(method, instrIndices); // collect all dynamic property writes in the method LinkedList puts = new LinkedList<>(); for(SSAInstruction inst : Iterator2Iterable.make(ir.iterateNormalInstructions())) if(inst instanceof AbstractReflectivePut) puts.addFirst((AbstractReflectivePut)inst); SSAInstruction insts[] = ir.getInstructions(); instrs: for(int ii = 0; ii < insts.length; ii++) { SSAInstruction inst = insts[ii]; if(inst instanceof AbstractReflectiveGet) { AbstractReflectiveGet get = (AbstractReflectiveGet)inst; int index = get.getMemberRef(); if(ir.getSymbolTable().isConstant(index)) continue; if(ir.getSymbolTable().isParameter(index)) continue; // try to determine what "index" is called at the source level String indexName = getSourceLevelName(ir, ii, index); if(indexName == null) continue instrs; // check that "index" is not accessed in an inner function LexicalInformation lexicalInfo = astMethod.lexicalInfo(); if (lexicalInfo.getExposedNames() != null) { for(Pair n : lexicalInfo.getExposedNames()) { if (n.fst.equals(indexName) && lexicalInfo.getScopingName().equals(n.snd)) continue instrs; } } // if "index" is a numeric variable, it is not worth extracting if(IGNORE_NUMERIC_INDICES && mustBeNumeric(ir, du, index)) continue instrs; // set of SSA variables into which the value read by 'get' may flow MutableIntSet reached = new BitVectorIntSet(); reached.add(get.getDef()); // saturate reached by following def-use chains through phi instructions and across function calls LinkedList worklist = new LinkedList<>(); MutableIntSet done = new BitVectorIntSet(); worklist.add(get.getDef()); while(!worklist.isEmpty()) { Integer i = worklist.pop(); done.add(i); for(SSAInstruction inst2 : Iterator2Iterable.make(du.getUses(i))) { int i2 = instrIndices.getMappedIndex(inst2); if(inst2 instanceof SSAPhiInstruction) { int def = inst2.getDef(); if(reached.add(def) && !done.contains(def)) worklist.add(def); } else if(inst2 instanceof SSAAbstractInvokeInstruction) { int def = inst2.getDef(); if(reached.add(def) && !done.contains(def)) worklist.add(def); // if the index also flows into this invocation, record an escape correlation if(TRACK_ESCAPES) { for(int j=0;j getSourceLevelNames(IR ir, int index, IntSet vs) { Set res = new HashSet<>(); for(IntIterator iter=vs.intIterator();iter.hasNext();) { String name = getSourceLevelName(ir, index, iter.next()); if(name != null) res.add(name); } return res; } // checks whether the given SSA variable must always be assigned a numeric value private static boolean mustBeNumeric(IR ir, DefUse du, int v) { LinkedList worklist = new LinkedList<>(); MutableIntSet done = new BitVectorIntSet(); worklist.add(v); while(!worklist.isEmpty()) { int i = worklist.pop(); done.add(i); if(ir.getSymbolTable().isConstant(i) && ir.getSymbolTable().getConstantValue(i) instanceof Number) continue; SSAInstruction inst2 = du.getDef(i); if(inst2 instanceof SSAPhiInstruction) { for(int j=0;j summaries) { List> correlations = new ArrayList<>(); for(CorrelationSummary summary : summaries.values()) correlations.addAll(summary.pp()); Collections.sort(correlations, new Comparator>() { @Override @SuppressWarnings("unchecked") public int compare(Pair o1, Pair o2) { return o1.fst.compareTo(o2.fst); } }); int i = 0; for(Pair p : correlations) System.out.println((i++) + " -- " + p.fst + ": " + p.snd); } public Map findCorrelatedAccesses(URL url) throws ClassHierarchyException { Set scripts = null; if(url.getPath().endsWith(".js")) { scripts = Collections.singleton(new SourceURLModule(url)); } else { JavaScriptLoader.addBootstrapFile(WebUtil.preamble); try { scripts = WebUtil.extractScriptFromHTML(url, DefaultSourceExtractor.factory).fst; } catch (Error e) { e.printStackTrace(); assert false : e.warning; } } Map summaries = findCorrelatedAccesses(scripts); return summaries; } public Map findCorrelatedAccesses(Collection scripts) throws ClassHierarchyException { return findCorrelatedAccesses(scripts.toArray(new SourceModule[scripts.size()])); } public Map findCorrelatedAccesses(SourceModule[] scripts_array) throws ClassHierarchyException { JSCallGraphUtil.setTranslatorFactory(translatorFactory); JavaScriptLoaderFactory loaders = JSCallGraphUtil.makeLoaders(null); CAstAnalysisScope scope = new CAstAnalysisScope(scripts_array, loaders, Collections.singleton(JavaScriptLoader.JS)); IClassHierarchy cha = ClassHierarchyFactory.make(scope, loaders, JavaScriptLoader.JS); try { com.ibm.wala.cast.util.Util.checkForFrontEndErrors(cha); } catch (WalaException e) { return Collections.emptyMap(); } IRFactory factory = AstIRFactory.makeDefaultFactory(); Map correlations = HashMapFactory.make(); for(IClass klass : cha) { for(IMethod method : klass.getAllMethods()) { IR ir = factory.makeIR(method, Everywhere.EVERYWHERE, SSAOptions.defaultOptions()); CorrelationSummary summary = findCorrelatedAccesses(method, ir); if(!summary.getCorrelations().isEmpty()) correlations.put(method, summary); } } return correlations; } @SuppressWarnings("unused") private URL toUrl(String src) throws MalformedURLException { // first try interpreting as local file name, if that doesn't work just assume it's a URL try { File f = (new FileProvider()).getFileFromClassLoader(src, this.getClass().getClassLoader()); URL url = f.toURI().toURL(); return url; } catch(FileNotFoundException fnfe) { return new URL(src); } } }