Skip to content

Qilin as a lib

Dongjie He edited this page Mar 24, 2022 · 1 revision

Use Qilin as a library

We use an example to show how to use Qilin as a third-party library.

The source code of this example can be found here.

The example will address the following problem:

How many potential data race alias pairs are in the application code of an input program?

Create a gradle project

We first use IntelliJ IDEA to create a new Gradle project named PotentialRacePairFinder. intellijGradleNew

Add dependencies

We have published Qilin in the Github packages Gradle registry. So you need to add the following dependencies:

dependencies {
    implementation "qilin:pta:1.0-SNAPSHOT"
    implementation "qilin:core:1.0-SNAPSHOT"
    implementation "qilin:util:1.0-SNAPSHOT"
    implementation files("${rootDir}/libs/sootclasses-4.2.1-jar-with-dependencies.jar")
}

Qilin also relies on Soot. For Soot, its official version contains some bugs affecting how Qilin works. That is why we have provided our own version. We will replace this version with a future official soot version later.

You also need to tell Gradle how to find these dependencies and from which repository:

repositories {
    flatDir {
        dirs 'libs'
    }
    maven {
        url "https://maven.pkg.github.com/QilinPTA/Qilin"
        credentials {
            username = project.findProperty("gpr.user") ?: "QilinPTA"
            password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
        }
    }
}

You can replace QilinPTA with your own Github account. You can follow the steps below to generate a token and set it as an environment variable.

Settings ---> Developer settings ---> Personal access tokens ---> Generate new token

Once the above is done, you can write your code with the APIs provided in Qilin.

Example code

import qilin.core.PTA;
import qilin.util.PTAUtils;
import qilin.util.Pair;
import qilin.util.Stopwatch;
import soot.Body;
import soot.Local;
import soot.Unit;
import soot.Value;
import soot.jimple.AssignStmt;
import soot.jimple.InstanceFieldRef;

import java.util.HashSet;
import java.util.Set;

/*
* find potential data-race pairs.
* (1) two different statements: a.f = ..., b.f = ...; check whether a aliases with b.
* (2) two different statements: a.f = ..., ... = b.f, check whether a aliases with b.
* (3) a and b should be local variables defined in application code.
* (4) a = b is allowed.
* */
public class Main {

    public static void main(String[] args) {
        Stopwatch stopwatch = Stopwatch.newAndStart("Pointer Analysis");
        PTA pta = driver.Main.run(args);
        stopwatch.stop();
        System.out.println(stopwatch);
        boolean verbose = false;
        run(pta, verbose);
    }

    public static void run(PTA pta, boolean verbose) {
        Set<Unit> writes = new HashSet<>();
        Set<Unit> reads = new HashSet<>();
        pta.getNakedReachableMethods().forEach(m -> {
            if (!m.isConcrete()) {
                return;
            }
            if (!m.getDeclaringClass().isApplicationClass()) {
                return;
            }
            Body body = PTAUtils.getMethodBody(m);
            for(Unit unit: body.getUnits()) {
                if (unit instanceof AssignStmt as) {
                    Value l = as.getLeftOp();
                    Value r = as.getRightOp();
                    if (l instanceof InstanceFieldRef) {
                        writes.add(unit);
                    } else if (r instanceof InstanceFieldRef) {
                        reads.add(unit);
                    }
                }
            }
        });
        System.out.println("#write:" + writes.size());
        System.out.println("#read:" + reads.size());

        Set<Pair<Unit, Unit>> potentialRacePairs = new HashSet<>();
        // check write and write pair
        Unit[] mWrites = writes.toArray(writes.toArray(new Unit[0]));
        for(int i = 0; i < mWrites.length; ++i) {
            Unit u1 = mWrites[i];
            for(int j = i + 1; j < mWrites.length; ++j) {
                Unit u2 = mWrites[j];
                AssignStmt as1 = (AssignStmt) u1;
                AssignStmt as2 = (AssignStmt) u2;
                InstanceFieldRef l1 = (InstanceFieldRef) as1.getLeftOp();
                InstanceFieldRef l2 = (InstanceFieldRef) as2.getLeftOp();
                if (l1.getField() != l2.getField()) {
                    continue;
                }
                Local base1 = (Local) l1.getBase();
                Local base2 = (Local) l2.getBase();
                if (pta.mayAlias(base1, base2)) {
                    potentialRacePairs.add(new Pair<>(u1, u2));
                }
            }
        }
        int wwRace = potentialRacePairs.size();
        System.out.println("#write-write-race:" + wwRace);
        // check read and write pair
        for(Unit u1 : writes) {
            for(Unit u2 : reads) {
                AssignStmt as1 = (AssignStmt) u1;
                AssignStmt as2 = (AssignStmt) u2;
                InstanceFieldRef l1 = (InstanceFieldRef) as1.getLeftOp();
                InstanceFieldRef l2 = (InstanceFieldRef) as2.getRightOp();
                if (l1.getField() != l2.getField()) {
                    continue;
                }
                Local base1 = (Local) l1.getBase();
                Local base2 = (Local) l2.getBase();
                if (pta.mayAlias(base1, base2)) {
                    potentialRacePairs.add(new Pair<>(u1, u2));
                }
            }
        }
        System.out.println("#read-write-race:" + (potentialRacePairs.size() - wwRace));
        if (verbose) {
            System.out.println("Race pairs:");
            for(Pair<Unit, Unit> pair : potentialRacePairs) {
                System.out.println(pair.getFirst() + "\t" + pair.getSecond());
            }
        }
    }
}

The above code is quite simple. It first invokes Qilin to perform a pointer analysis on the input program and obtains a PTA instance. Next, it scans its application methods to collect its field-read and field-write statements. Finally, it checks whether one field-write statement and another field-access statement (either field-write or field-read statement) have a potential data race by checking whether their base variables are aliases or not.

In practice, Qilin should be used together with a data-race analysis algorithm to remove those data race pairs that are deemed to be false positives (due to some synchronisation mechanisms used in the program).

Test the code

Please refer to this page.