Skip to content

Commit

Permalink
feat: implement path objectives (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
dstallenberg authored Nov 14, 2023
1 parent e75194c commit 687cb5b
Show file tree
Hide file tree
Showing 18 changed files with 234 additions and 392 deletions.
8 changes: 4 additions & 4 deletions libraries/search-javascript/lib/criterion/BranchDistance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ import { transformSync, traverse } from "@babel/core";
import { defaultBabelOptions } from "@syntest/analysis-javascript";
import { getLogger, Logger } from "@syntest/logging";
import {
BranchDistance as FrameworkBranchDistance,
BranchDistanceCalculator as AbstractBranchDistanceCalculator,
shouldNeverHappen,
} from "@syntest/search";

import { BranchDistanceVisitor } from "./BranchDistanceVisitor";

export class BranchDistance extends FrameworkBranchDistance {
export class BranchDistanceCalculator extends AbstractBranchDistanceCalculator {
protected static LOGGER: Logger;
protected syntaxForgiving: boolean;
protected stringAlphabet: string;

constructor(syntaxForgiving: boolean, stringAlphabet: string) {
super();
this.syntaxForgiving = syntaxForgiving;
BranchDistance.LOGGER = getLogger("BranchDistance");
BranchDistanceCalculator.LOGGER = getLogger("BranchDistance");
this.stringAlphabet = stringAlphabet;
}

Expand Down Expand Up @@ -84,7 +84,7 @@ export class BranchDistance extends FrameworkBranchDistance {
const variables_ = Object.entries(variables)
.map(([key, value]) => `${key}=${String(value)}`)
.join(", ");
BranchDistance.LOGGER.warn(
BranchDistanceCalculator.LOGGER.warn(
`Calculated distance for condition '${condition}' -> ${String(
trueOrFalse
)}, is zero. Variables: ${variables_}`
Expand Down
157 changes: 3 additions & 154 deletions libraries/search-javascript/lib/search/JavaScriptSubject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,168 +17,17 @@
*/
import { TargetType } from "@syntest/analysis";
import { RootContext, SubTarget, Target } from "@syntest/analysis-javascript";
import { ControlFlowGraph, Edge, EdgeType } from "@syntest/cfg";
import {
ApproachLevel,
BranchObjectiveFunction,
FunctionObjectiveFunction,
ObjectiveFunction,
SearchSubject,
shouldNeverHappen,
} from "@syntest/search";
import { ObjectiveFunction, SearchSubject } from "@syntest/search";

import { BranchDistance } from "../criterion/BranchDistance";
import { JavaScriptTestCase } from "../testcase/JavaScriptTestCase";

export class JavaScriptSubject extends SearchSubject<JavaScriptTestCase> {
protected syntaxForgiving: boolean;
protected stringAlphabet: string;
constructor(
target: Target,
rootContext: RootContext,
syntaxForgiving: boolean,
stringAlphabet: string
objectives: ObjectiveFunction<JavaScriptTestCase>[]
) {
super(target, rootContext);
this.syntaxForgiving = syntaxForgiving;
this.stringAlphabet = stringAlphabet;

this._extractObjectives();
}

protected _extractObjectives(): void {
this._objectives = new Map<
ObjectiveFunction<JavaScriptTestCase>,
ObjectiveFunction<JavaScriptTestCase>[]
>();

const functions = this._rootContext.getControlFlowProgram(
this._target.path
).functions;

// FUNCTION objectives
for (const function_ of functions) {
const graph = function_.graph;
// Branch objectives
// Find all control nodes
// I.E. nodes that have more than one outgoing edge
const controlNodeIds = [...graph.nodes.keys()].filter(
(node) => graph.getOutgoingEdges(node).length > 1
);

for (const controlNodeId of controlNodeIds) {
const outGoingEdges = graph.getOutgoingEdges(controlNodeId);

for (const edge of outGoingEdges) {
if (["ENTRY", "SUCCESS_EXIT", "ERROR_EXIT"].includes(edge.target)) {
throw new Error(
`Function ${function_.name} in ${function_.id} ends in entry/exit node`
);
}
// Add objective function
this._objectives.set(
new BranchObjectiveFunction(
new ApproachLevel(),
new BranchDistance(this.syntaxForgiving, this.stringAlphabet),
this,
edge.target
),
[]
);
}
}

for (const objective of this._objectives.keys()) {
const childrenObject = this.findChildren(graph, objective);
this._objectives.get(objective).push(...childrenObject);
}

const entry = function_.graph.entry;

const children = function_.graph.getChildren(entry.id);

if (children.length !== 1) {
throw new Error(shouldNeverHappen("JavaScriptSubject")); //, "entry node has more than one child"))
}

// Add objective
const functionObjective = new FunctionObjectiveFunction(
this,
function_.id
);

// find first control node in function
let firstControlNodeInFunction = children[0];
while (
function_.graph.getChildren(firstControlNodeInFunction.id).length === 1
) {
firstControlNodeInFunction = function_.graph.getChildren(
firstControlNodeInFunction.id
)[0];
}

// there are control nodes in the function
if (
function_.graph.getChildren(firstControlNodeInFunction.id).length === 2
) {
const firstObjectives = function_.graph
.getChildren(firstControlNodeInFunction.id)
.map((child) => {
return [...this._objectives.keys()].find(
(objective) => objective.getIdentifier() === child.id
);
});

if (!firstObjectives[0] || !firstObjectives[1]) {
throw new Error(
`Cannot find objective with id: ${firstControlNodeInFunction.id}`
);
}

this._objectives.set(functionObjective, [...firstObjectives]);
} else {
// no control nodes so no sub objectives
this._objectives.set(functionObjective, []);
}
}
}

findChildren(
graph: ControlFlowGraph,
object: ObjectiveFunction<JavaScriptTestCase>
): ObjectiveFunction<JavaScriptTestCase>[] {
let childObjectives: ObjectiveFunction<JavaScriptTestCase>[] = [];

let edges2Visit = [...graph.getOutgoingEdges(object.getIdentifier())];

const visitedEdges: Edge[] = [];

while (edges2Visit.length > 0) {
const edge = edges2Visit.pop();

if (visitedEdges.includes(edge)) {
// this condition is made to avoid infinite loops
continue;
}

if (edge.type === EdgeType.BACK_EDGE) {
continue;
}

visitedEdges.push(edge);

const found = this.getObjectives().filter(
(child) => child.getIdentifier() === edge.target
);
if (found.length === 0) {
const additionalEdges = graph.getOutgoingEdges(edge.target);
edges2Visit = [...edges2Visit, ...additionalEdges];
} else {
childObjectives = [...childObjectives, ...found];
}
}

return childObjectives;
super(target, rootContext, objectives);
}

getActionableTargets(): SubTarget[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
import { expect } from "chai";

import { BranchDistance } from "../../lib/criterion/BranchDistance";
import { BranchDistanceCalculator } from "../../lib/criterion/BranchDistance";

describe("BranchDistance a == b test", () => {
// number
Expand All @@ -26,7 +26,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = true;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -41,7 +41,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = false;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -54,7 +54,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = true;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -67,7 +67,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = false;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -82,7 +82,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = true;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -97,7 +97,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = false;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -111,7 +111,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = true;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -124,7 +124,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = false;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -139,7 +139,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = true;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -154,7 +154,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = false;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -168,7 +168,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = true;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand All @@ -181,7 +181,7 @@ describe("BranchDistance a == b test", () => {
const variables = {};
const trueOrFalse = false;

const calculator = new BranchDistance(
const calculator = new BranchDistanceCalculator(
false,
"0123456789abcdefghijklmnopqrstuvxyz"
);
Expand Down
Loading

0 comments on commit 687cb5b

Please sign in to comment.