Core Steps

Core Steps are Ocular Query Components which can be combined with any other Step. Ocular offers four Core Steps, map, sideEffect, dedup and clone.

We will look at each one while analyzing a simple program named X42:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
if (argc > 1 && strcmp(argv[1], "42") == 0) {
fprintf(stderr, "It depends!\n");
exit(42);
}
printf("What is the meaning of life?\n");
exit(0);
}

map

The map Core Step is a Step which transforms objects in a traversal with an expression. Its expression takes one argument, a variable representing the item the map Core Step is suffixing, and can return any other type. For example, say that you'd like to return the value of the CODE property, together with the value of the TYPE_FULL_NAME property of all LITERAL nodes in X42's Code Property Graph:

ocular> cpg.literal.map(node => List(node.typeFullName, node.code)).toList
res223: List[List[String]] = List(
List("char *", "\"What is the meaning of life?\\n\""),
List("int", "42"),
List("int", "0"),
List("char *", "\"It depends!\\n\""),
List("int", "0"),
List("char *", "\"42\""),
List("int", "1"),
List("int", "1")
)

sideEffect

sideEffect is a step that executes a function on each node of the traversal it suffixes.

ocular> cpg.literal.sideEffect(node => println("Called once for ID " + node.id.toString())).code.l
Called once for ID 32
Called once for ID 30
Called once for ID 34
Called once for ID 28
Called once for ID 24
Called once for ID 23
Called once for ID 22
Called once for ID 17
res0: List[String] = List(
"\"What is the meaning of life?\\n\"",
"42",
"0",
"\"It depends!\\n\"",
"0",
"\"42\"",
"1",
"1"
)

dedup

dedup is a step that removes duplicates from the traversal it suffixes.

For example, say you'd like to query X42's Code Property Graph for the AST parent nodes of all CALL nodes, and print out their CODE property:

ocular> cpg.call.astParent.isCall.code.l
res0: List[String] = List(
"strcmp(argv[1], \"42\")",
"strcmp(argv[1], \"42\") == 0",
"argc > 1 && strcmp(argv[1], \"42\") == 0",
"argc > 1 && strcmp(argv[1], \"42\") == 0"
)

Because of the structure of the resulting AST, the query returns a duplicate result. To remove it, add dedup to the query:

ocular> cpg.call.astParent.isCall.dedup.code.l
res0: List[String] = List(
"strcmp(argv[1], \"42\")",
"strcmp(argv[1], \"42\") == 0",
"argc > 1 && strcmp(argv[1], \"42\") == 0"
)

clone

clone is a step that creates an in-memory copy of a traversal. The step is useful in situations in which you'd like to execute the same query with a slight change. For example, say you'd like to query X42's Code Property Graph for the value of the NAME property of all CALL nodes, and use the same query to get their number. If you simply store the query in a variable and then execute it twice, the second result will be 0 because the query has already been executed once:

ocular> var queryA = cpg.call
queryA: NodeSteps[Call] = io.shiftleft.semanticcpg.language.NodeSteps@7a71be09
ocular> queryA.name.toList
res0: List[String] = List(
"exit",
"printf",
"exit",
"fprintf",
"<operator>.indirectIndexAccess",
"strcmp",
"<operator>.equals",
"<operator>.greaterThan",
"<operator>.logicalAnd"
)
ocular> queryA.size
res1: Int = 0

But if instead you execute a copy of the query, the first query will return the expected results:

ocular> var queryA = cpg.call
queryA: NodeSteps[Call] = io.shiftleft.semanticcpg.language.NodeSteps@625955bb
ocular> queryA.clone.name.toList
res0: List[String] = List(
"exit",
"printf",
"exit",
"fprintf",
"<operator>.indirectIndexAccess",
"strcmp",
"<operator>.equals",
"<operator>.greaterThan",
"<operator>.logicalAnd"
)
ocular> queryA.size
res1: Int = 9