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
:
Copy # 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:
Copy 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.
Copy 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:
Copy 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:
Copy 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:
Copy 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:
Copy 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