diff --git a/src/main/java/pascal/taie/analysis/pta/client/MayAliasPair.java b/src/main/java/pascal/taie/analysis/pta/client/MayAliasPair.java new file mode 100644 index 000000000..ee8945e95 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/client/MayAliasPair.java @@ -0,0 +1,145 @@ +/* + * Tai-e: A Static Analysis Framework for Java + * + * Copyright (C) 2022 Tian Tan + * Copyright (C) 2022 Yue Li + * + * This file is part of Tai-e. + * + * Tai-e 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 3 + * of the License, or (at your option) any later version. + * + * Tai-e 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 Tai-e. If not, see . + */ + +package pascal.taie.analysis.pta.client; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pascal.taie.World; +import pascal.taie.analysis.ProgramAnalysis; +import pascal.taie.analysis.pta.PointerAnalysis; +import pascal.taie.analysis.pta.PointerAnalysisResult; +import pascal.taie.analysis.pta.core.heap.Obj; +import pascal.taie.config.AnalysisConfig; +import pascal.taie.ir.exp.Var; +import pascal.taie.util.Indexer; +import pascal.taie.util.collection.IndexerBitSet; +import pascal.taie.util.collection.Maps; +import pascal.taie.util.collection.Sets; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class MayAliasPair extends ProgramAnalysis { + + private static final Logger logger = LogManager.getLogger(MayAliasPair.class); + + public static final String ID = "may-alias-pair"; + + private PointerAnalysisResult ptaResult; + + public MayAliasPair(AnalysisConfig config) { + super(config); + } + + @Override + public MayAliasPairResult analyze() { + ptaResult = World.get().getResult(PointerAnalysis.ID); + Set vars = Sets.newSet(ptaResult.getVars()); + Set appVars = vars.stream() + .filter(MayAliasPair::isApp) + .collect(Collectors.toUnmodifiableSet()); + + long nAliasPairs = computeMayAliasPairs(vars); + long nVars = vars.size(); + + long nAppAliasPairs = computeMayAliasPairs(appVars); + long nAppVars = appVars.size(); + + // Log statistics + logger.info("#{}: found {} in {} variable pairs", + ID, nAliasPairs, (nVars - 1) * nVars / 2); + logger.info("#{}: found {} in {} variable pairs (app)", + ID, nAppAliasPairs, (nAppVars - 1) * nAppVars / 2); + + return new MayAliasPairResult(nAliasPairs, nAppAliasPairs); + } + + private long computeMayAliasPairs(Set vars) { + VarIndexer indexer = new VarIndexer(vars); + Map> obj2Vars = Maps.newMap(); + vars.forEach(v -> { + for (Obj obj : ptaResult.getPointsToSet(v)) { + obj2Vars.computeIfAbsent(obj, k -> indexer.makeIndexerBitSet()) + .add(v); + } + }); + + // mayAlias(u, v) if + // exists o, s.t. in(o, pts(u)) and in(o, pts(v)) + long nAliasPairs = vars.parallelStream() + .mapToLong(v -> { + Set aliasVars = indexer.makeIndexerBitSet(); + for (Obj o : ptaResult.getPointsToSet(v)) { + aliasVars.addAll(obj2Vars.getOrDefault(o, Collections.emptySet())); + } + // v may alias to itself, but we do not count in this case + aliasVars.remove(v); + return aliasVars.size(); + }).sum(); + // mayAlias(u, v) iff mayAlias(v, u), so a pair is counted twice + return nAliasPairs / 2; + } + + private static boolean isApp(Var v) { + return v.getMethod().isApplication(); + } + + public record MayAliasPairResult(long aliasPairs, long appAliasPairs) { + } + + // A global indexer for Vars + private static class VarIndexer implements Indexer { + + private static final boolean IS_SPARSE = true; + + private final Var[] vars; + + private final Map varIndexMap = Maps.newMap(); + + VarIndexer(Collection vars) { + this.vars = vars.stream() + .distinct() + .toArray(Var[]::new); + for (int index = 0; index < this.vars.length; index++) { + varIndexMap.put(this.vars[index], index); + } + } + + public IndexerBitSet makeIndexerBitSet() { + return new IndexerBitSet<>(this, IS_SPARSE); + } + + @Override + public int getIndex(Var o) { + return varIndexMap.get(o); + } + + @Override + public Var getObject(int index) { + return vars[index]; + } + } +} diff --git a/src/main/resources/tai-e-analyses.yml b/src/main/resources/tai-e-analyses.yml index 09a4bb4ba..72e244ece 100644 --- a/src/main/resources/tai-e-analyses.yml +++ b/src/main/resources/tai-e-analyses.yml @@ -54,6 +54,11 @@ id: poly-call requires: [ pta ] +- description: identify variable pairs that may alias + analysisClass: pascal.taie.analysis.pta.client.MayAliasPair + id: may-alias-pair + requires: [ pta ] + - description: find modified objects of methods and statements analysisClass: pascal.taie.analysis.sideeffect.SideEffectAnalysis id: side-effect diff --git a/src/test/java/pascal/taie/analysis/pta/client/MayAliasPairTest.java b/src/test/java/pascal/taie/analysis/pta/client/MayAliasPairTest.java new file mode 100644 index 000000000..23ce51c93 --- /dev/null +++ b/src/test/java/pascal/taie/analysis/pta/client/MayAliasPairTest.java @@ -0,0 +1,84 @@ +/* + * Tai-e: A Static Analysis Framework for Java + * + * Copyright (C) 2022 Tian Tan + * Copyright (C) 2022 Yue Li + * + * This file is part of Tai-e. + * + * Tai-e 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 3 + * of the License, or (at your option) any later version. + * + * Tai-e 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 Tai-e. If not, see . + */ + + +package pascal.taie.analysis.pta.client; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import pascal.taie.Main; +import pascal.taie.World; +import pascal.taie.analysis.pta.PointerAnalysis; +import pascal.taie.analysis.pta.PointerAnalysisResult; +import pascal.taie.ir.exp.Var; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MayAliasPairTest { + static final String DIR = "basic"; + + @ParameterizedTest + @ValueSource(strings = { + "New", + "Assign", + "StoreLoad", + "Call", + "InstanceField", + "CallParamRet", + "StaticCall", + "MergeParam", + }) + void test(String mainClass) { + String ptaTestRoot = "src/test/resources/pta"; + String classPath = ptaTestRoot + "/" + DIR; + List args = List.of( + // for loading class PTAAssert + "-cp", ptaTestRoot, + // for loading main class + "-cp", classPath, "-m", mainClass, + "-a", MayAliasPair.ID + ); + Main.main(args.toArray(new String[0])); + MayAliasPair.MayAliasPairResult resultByAnalysis = World.get().getResult(MayAliasPair.ID); + long appResultByDefinition = computeByDefinition(); + assertEquals(appResultByDefinition, resultByAnalysis.appAliasPairs()); + } + + private static long computeByDefinition() { + PointerAnalysisResult ptaResult = World.get().getResult(PointerAnalysis.ID); + Var[] appVars = ptaResult.getVars().stream() + .filter(v -> v.getMethod().isApplication()) + .toArray(Var[]::new); + long aliasPairs = 0; + for (int i = 0; i < appVars.length; i++) { + for (int j = i + 1; j < appVars.length; j++) { + Var v1 = appVars[i], v2 = appVars[j]; + if (ptaResult.mayAlias(v1, v2)) { + aliasPairs++; + } + } + } + return aliasPairs; + } +}