/* * This is a prototype implementation of the concept of Feature-Sen * sitive Dataflow Analysis. More details in the AOSD'12 paper: * Dataflow Analysis for Software Product Lines * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package br.ufal.cideei.soot.instrument; import java.util.BitSet; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.dom.CompilationUnit; import soot.Body; import soot.BodyTransformer; import soot.SootClass; import soot.Unit; import soot.tagkit.SourceFileTag; import soot.tagkit.SourceLnPosTag; import soot.util.NumberedString; import soot.util.StringNumberer; import br.ufal.cideei.features.IFeatureExtracter; import br.ufal.cideei.util.CachedICompilationUnitParser; /** * The Class FeatureModelInstrumentor is a Soot transformation for transcribing feature information to every Unit. This * is done via Tag annonations (FeatureTag). */ // TODO: change class name to something more appropriate, like // "FeatureInstrumentorTransformer" public class FeatureModelInstrumentorTransformer extends BodyTransformer { /** Feature extracter. */ private static IFeatureExtracter extracter; /** Current compilation unit the transformation is working on */ private IFile file; /** * XXX: Workaround for the preTransform method. See comments on FeatureModelInstrumentorTransformer#preTransform() * method. */ private static String classPath; private CachedICompilationUnitParser cachedParser = new CachedICompilationUnitParser(); private CachedLineNumberMapper cachedLineColorMapper = new CachedLineNumberMapper(); private Map<Integer, Set<String>> currentColorMap; private StringNumberer featureNumberer = new StringNumberer(); // #ifdef METRICS //@ protected static long transformationTime = 0; //@ protected static long parsingTime = 0; //@ protected static long colorLookupTableBuildingTime = 0; //@ protected AbstractMetricsSink sink; //@ protected static String COLOR_LOOKUP = "color table"; //@ protected static String PARSING = "parsing"; //@ protected static String INSTRUMENTATION = "instrumentation"; //@ // #endif /* * TODO: maybe injecting the sink depency in a different way could make this funcionality less intrusive. */ public FeatureModelInstrumentorTransformer(IFeatureExtracter extracter, String classPath) { FeatureModelInstrumentorTransformer.classPath = classPath; FeatureModelInstrumentorTransformer.extracter = extracter; } // #ifdef METRICS //@ public FeatureModelInstrumentorTransformer setMetricsSink(AbstractMetricsSink sink) { //@ this.sink = sink; //@ return this; //@ } //@ // #endif /* * (non-Javadoc) * * @see soot.BodyTransformer#internalTransform(soot.Body, java.lang.String, java.util.Map) */ @Override protected void internalTransform(Body body, String phase, @SuppressWarnings("rawtypes") Map options) { SootClass sootClass = body.getMethod().getDeclaringClass(); if(!sootClass.isApplicationClass()) return; //if no application class, then we don't care if (!sootClass.hasTag("SourceFileTag")) { throw new IllegalArgumentException("the body cannot be traced to its source file"); } preTransform(body); // #ifdef METRICS //@ long startTransform = System.nanoTime(); // #endif /* * Iterate over all units, look up for their colors and add a new FeatureTag to each of them, and also compute * all the colors found in the whole body. Units with no colors receive an empty FeatureTag. */ Iterator<Unit> unitIt = body.getUnits().iterator(); /* * After the following loop, allPresentFeatures will hold all the colors found in the body. Used to calculate a * "local" power set. */ Set<String> allPresentFeatures = new HashSet<String>(); /* * The set of features are represented as bits, for a more compact representation. The mapping between a feature * and it's ID is stored in the FeatureTag of the body. * * This is necessary so that clients, such as r.d. analysis, can safely iterate over all configurations without * explicitly invoking Set operations like containsAll(); * * TODO: check redundancy between allPresentFeatures & allPresentFeaturesId */ // String->Integer // BidiMap allPresentFeaturesId = new DualHashBidiMap(); //FeatureTag emptyFeatureTag; // #ifdef LAZY //@ BitVectorFeatureRep emptyBitVectorRep = new BitVectorFeatureRep(Collections.EMPTY_SET, allPresentFeaturesId); //@ emptyFeatureTag = new FeatureTag(emptyBitVectorRep); //@ // #else //emptyFeatureTag = new FeatureTag(new BitFeatureRep(Collections.EMPTY_SET, allPresentFeaturesId)); // #endif // #ifdef LAZY //@ /* //@ * in the lazy approach, the representation can only be consolidade after all features have been discovery. All //@ * IFeatureRep will stored so that it is possible to consolidade later. //@ */ //@ List<BitVectorFeatureRep> generateVectorLater = new ArrayList<BitVectorFeatureRep>(); // #endif BitSet union = new BitSet(); BitSet intersection = new BitSet(1000); // 1000 = some sufficiently large number intersection.set(0,1000); while (unitIt.hasNext()) { Unit nextUnit = unitIt.next(); SourceLnPosTag lineTag = (SourceLnPosTag) nextUnit.getTag("SourceLnPosTag"); if (lineTag == null) { //nextUnit.addTag(emptyFeatureTag); } else { int unitLine = lineTag.startLn(); Set<String> nextUnitColors = currentColorMap.get(unitLine); if (nextUnitColors != null) { // for (String color : nextUnitColors) { // if (!allPresentFeaturesId.containsKey(color)) { // allPresentFeaturesId.put(color, idGen); // idGen = idGen << 1; // } // } /* * increment local powerset with new found colors. */ // allPresentFeatures.addAll(nextUnitColors); IFeatureRep featRep; FeatureTag featureTag; // #ifdef LAZY //@ featRep = new BitVectorFeatureRep(nextUnitColors, allPresentFeaturesId); //@ generateVectorLater.add((BitVectorFeatureRep) featRep); //@ featureTag = new FeatureTag(featRep); //@ nextUnit.addTag(featureTag); // #else // featRep = new BitFeatureRep(nextUnitColors, allPresentFeaturesId); BitSet bs = new BitSet(); for (String color : nextUnitColors) { NumberedString numberedString = featureNumberer.findOrAdd(color); bs.set(numberedString.getNumber()); } // #endif featureTag = new FeatureTag(bs); nextUnit.addTag(featureTag); union.or(bs); intersection.and(bs); } else { intersection.clear(); //nextUnit.addTag(emptyFeatureTag); } } } intersection.and(union); if(!intersection.isEmpty()) { FeatureTag t = new FeatureTag(intersection); body.addTag(t); body.getMethod().addTag(t); } // UnmodifiableBidiMap unmodAllPresentFeaturesId = (UnmodifiableBidiMap) UnmodifiableBidiMap.decorate(allPresentFeaturesId); // #ifdef LAZY //@ /* //@ * generate vectors //@ */ //@ //@ for (BitVectorFeatureRep featureRep : generateVectorLater) { //@ featureRep.generateBitVector(idGen); //@ } // #endif // #ifdef METRICS //@ long transformationDelta = System.nanoTime() - startTransform; //@ if (sink != null) { //@ sink.flow(body, FeatureModelInstrumentorTransformer.INSTRUMENTATION, transformationDelta); //@ } //@ FeatureModelInstrumentorTransformer.transformationTime += transformationDelta; // #endif ConfigTag configTag; // #ifdef LAZY //@ //@ BitVectorConfigRep localConfigurations = BitVectorConfigRep.localConfigurations(idGen, unmodAllPresentFeaturesId); //@ emptyBitVectorRep.generateBitVector(idGen); //@ //@ Set<IConfigRep> lazyConfig = new HashSet<IConfigRep>(); //@ lazyConfig.add(localConfigurations); //@ configTag = new ConfigTag(lazyConfig); //@ body.addTag(configTag); //@ // #else // configTag = new ConfigTag(BitConfigRep.localConfigurations(idGen, unmodAllPresentFeaturesId).getConfigs()); // body.addTag(configTag); // // #endif } /** * Do the transformation on the body. To accomplish this, the class that declares this SootMethod needs to be tagged * with the SourceFileTag. * * @param body * the body * @param compilationUnit * the compilation unit */ public void transform2(Body body, String classPath) { FeatureModelInstrumentorTransformer.classPath = classPath; preTransform(body); this.transform(body); } private void preTransform(Body body) { SootClass sootClass = body.getMethod().getDeclaringClass(); /* * XXX: WARNING! tag.getAbsolutePath() returns an INCORRECT value for the absolute path AFTER the first body * transformation. In this workaround, since this method depends on the classpath , it is injected on this class * constructor. We will use tag.getSourceFile() in order to resolve the file name. * * Yes, this is ugly. */ SourceFileTag sourceFileTag = (SourceFileTag) sootClass.getTag("SourceFileTag"); // /* // * The String absolutePath will be transformed to the absolute path to the Class which body belongs to. See the // * XXX above for the explanation. // */ // String absolutePath = sootClass.getName(); // int lastIndexOf = absolutePath.lastIndexOf("."); // if (lastIndexOf != -1) { // absolutePath = absolutePath.substring(0, lastIndexOf); // } else { // absolutePath = ""; // } // // /* // * XXX String#replaceAll does not work properly when replacing "special" chars like File.separator. The Matcher // * and Pattern composes a workaround for that. // */ // absolutePath = absolutePath.replaceAll(Pattern.quote("."), Matcher.quoteReplacement(File.separator)); // absolutePath = classPath + File.separator + absolutePath + File.separator + sourceFileTag.getSourceFile(); // sourceFileTag.setAbsolutePath(absolutePath); String abspath = sourceFileTag.getAbsolutePath(); if(abspath==null) return; IPath path = new Path(abspath); this.file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(path); // #ifdef METRICS //@ long startCompilationUnitParser = System.nanoTime(); // #endif CompilationUnit compilationUnit = cachedParser.parse(file); // #ifdef METRICS //@ long parsingDelta = System.nanoTime() - startCompilationUnitParser; //@ if (sink != null) //@ sink.flow(body, FeatureModelInstrumentorTransformer.PARSING, parsingDelta); //@ FeatureModelInstrumentorTransformer.parsingTime += parsingDelta; //@ //@ long startBuilderColorLookUpTable = System.nanoTime(); // #endif this.currentColorMap = cachedLineColorMapper.makeAccept(compilationUnit, file, extracter, compilationUnit); // #ifdef METRICS //@ long builderColorLookUpTableDelta = System.nanoTime() - startBuilderColorLookUpTable; //@ if (sink != null) //@ sink.flow(body, FeatureModelInstrumentorTransformer.COLOR_LOOKUP, builderColorLookUpTableDelta); //@ FeatureModelInstrumentorTransformer.colorLookupTableBuildingTime += builderColorLookUpTableDelta; // #endif } public int numFeaturesPresent() { return featureNumberer.size(); } public StringNumberer getFeatureNumberer() { return featureNumberer; } }