1
+ import errno
2
+ import json
3
+ import os
4
+ import os .path
5
+ import re
6
+ import shlex
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+
12
+ if len (sys .argv ) != 4 :
13
+ print ("Usage: GenerateFlowTestCase.py specsToTest.ssv projectPom.xml outdir" , file = sys .stderr )
14
+ print ("specsToTest.ssv should contain SSV rows describing method taint-propagation specifications to test" , file = sys .stderr )
15
+ print ("projectPom.xml should import dependencies sufficient to resolve the types used in specsToTest.ssv" , file = sys .stderr )
16
+ sys .exit (1 )
17
+
18
+ try :
19
+ os .makedirs (sys .argv [3 ])
20
+ except Exception as e :
21
+ if e .errno != errno .EEXIST :
22
+ print ("Failed to create output directory %s: %s" % (sys .argv [3 ], e ))
23
+ sys .exit (1 )
24
+
25
+ resultJava = os .path .join (sys .argv [3 ], "Test.java" )
26
+ resultQl = os .path .join (sys .argv [3 ], "test.ql" )
27
+
28
+ if os .path .exists (resultJava ) or os .path .exists (resultQl ):
29
+ print ("Won't overwrite existing files '%s' or '%s'" % (resultJava , resultQl ), file = sys .stderr )
30
+ sys .exit (1 )
31
+
32
+ workDir = tempfile .mkdtemp ()
33
+
34
+ # Step 1: make a database that touches all types whose methods we want to test:
35
+ print ("Creating Maven project" )
36
+ projectDir = os .path .join (workDir , "mavenProject" )
37
+ os .makedirs (projectDir )
38
+
39
+ try :
40
+ shutil .copyfile (sys .argv [2 ], os .path .join (projectDir , "pom.xml" ))
41
+ except Exception as e :
42
+ print ("Failed to read project POM %s: %s" % (sys .argv [2 ], e ), file = sys .stderr )
43
+ sys .exit (1 )
44
+
45
+ commentRegex = re .compile ("^\s*(//|#)" )
46
+ def isComment (s ):
47
+ return commentRegex .match (s ) is not None
48
+
49
+ try :
50
+ with open (sys .argv [1 ], "r" ) as f :
51
+ specs = [l for l in f if not isComment (l )]
52
+ except Exception as e :
53
+ print ("Failed to open %s: %s\n " % (sys .argv [1 ], e ))
54
+ sys .exit (1 )
55
+
56
+ projectTestPkgDir = os .path .join (projectDir , "src" , "main" , "java" , "test" )
57
+ projectTestFile = os .path .join (projectTestPkgDir , "Test.java" )
58
+
59
+ os .makedirs (projectTestPkgDir )
60
+
61
+ def qualifiedOuterNameFromSsvRow (row ):
62
+ cells = row .split (";" )
63
+ if len (cells ) < 2 :
64
+ return None
65
+ return cells [0 ] + "." + cells [1 ].replace ("$" , "." )
66
+
67
+ with open (projectTestFile , "w" ) as testJava :
68
+ testJava .write ("package test;\n \n public class Test {\n \n " )
69
+
70
+ for i , spec in enumerate (specs ):
71
+ outerName = qualifiedOuterNameFromSsvRow (spec )
72
+ if outerName is None :
73
+ print ("A taint specification has the wrong format: should be 'package;classname;methodname....'" , file = sys .stderr )
74
+ print ("Mis-formatted row: " + spec , file = sys .stderr )
75
+ sys .exit (1 )
76
+ testJava .write ("\t %s obj%d = null;\n " % (outerName , i ))
77
+
78
+ testJava .write ("}" )
79
+
80
+ print ("Creating project database" )
81
+ cmd = ["codeql" , "database" , "create" , "--language=java" , "db" ]
82
+ ret = subprocess .call (cmd , cwd = projectDir )
83
+ if ret != 0 :
84
+ print ("Failed to create project database. Check that '%s' is a valid POM that pulls in all necessary dependencies, and '%s' specifies valid classes and methods." % (sys .argv [2 ], sys .argv [1 ]), file = sys .stderr )
85
+ print ("Failed command was: %s (cwd: %s)" % (shlex .join (cmd ), projectDir ), file = sys .stderr )
86
+ sys .exit (1 )
87
+
88
+ print ("Creating test-generation query" )
89
+ queryDir = os .path .join (workDir , "query" )
90
+ os .makedirs (queryDir )
91
+ qlFile = os .path .join (queryDir , "gen.ql" )
92
+ with open (os .path .join (queryDir , "qlpack.yml" ), "w" ) as f :
93
+ f .write ("name: test-generation-query\n version: 0.0.0\n libraryPathDependencies: codeql-java" )
94
+ with open (qlFile , "w" ) as f :
95
+ f .write ("import java\n import utils.GenerateFlowTestCase\n \n class GenRow extends CsvRow {\n \n \t GenRow() {\n \t \t this = [\n " )
96
+ f .write (",\n " .join ('\t \t \t "%s"' % spec .strip () for spec in specs ))
97
+ f .write ("\n \t \t ]\n \t }\n }\n " )
98
+
99
+ print ("Generating tests" )
100
+ generatedBqrs = os .path .join (queryDir , "out.bqrs" )
101
+ cmd = ['codeql' , 'query' , 'run' , qlFile , '--database' , os .path .join (projectDir , "db" ), '--output' , generatedBqrs ]
102
+ ret = subprocess .call (cmd )
103
+ if ret != 0 :
104
+ print ("Failed to generate tests. Failed command was: " + shlex .join (cmd ))
105
+ sys .exit (1 )
106
+
107
+ generatedJson = os .path .join (queryDir , "out.json" )
108
+ cmd = ['codeql' , 'bqrs' , 'decode' , generatedBqrs , '--format=json' , '--output' , generatedJson ]
109
+ ret = subprocess .call (cmd )
110
+ if ret != 0 :
111
+ print ("Failed to decode BQRS. Failed command was: " + shlex .join (cmd ))
112
+ sys .exit (1 )
113
+
114
+ def getTuples (queryName , jsonResult , fname ):
115
+ if queryName not in jsonResult or "tuples" not in jsonResult [queryName ]:
116
+ print ("Failed to read generated tests: expected key '%s' with a 'tuples' subkey in file '%s'" % (queryName , fname ), file = sys .stderr )
117
+ sys .exit (1 )
118
+ return jsonResult [queryName ]["tuples" ]
119
+
120
+ with open (generatedJson , "r" ) as f :
121
+ generateOutput = json .load (f )
122
+ testCaseRows = getTuples ("getTestCase" , generateOutput , generatedJson )
123
+ supportModelRows = getTuples ("getASupportMethodModel" , generateOutput , generatedJson )
124
+ if len (testCaseRows ) != 1 or len (testCaseRows [0 ]) != 1 :
125
+ print ("Expected exactly one getTestCase result with one column (got: %s)" % json .dumps (testCaseRows ), file = sys .stderr )
126
+ if any (len (row ) != 1 for row in supportModelRows ):
127
+ print ("Expected exactly one column in getASupportMethodModel relation (got: %s)" % json .dumps (supportModelRows ), file = sys .stderr )
128
+
129
+ with open (resultJava , "w" ) as f :
130
+ f .write (generateOutput ["getTestCase" ]["tuples" ][0 ][0 ])
131
+
132
+ scriptPath = os .path .dirname (sys .argv [0 ])
133
+
134
+ with open (resultQl , "w" ) as f :
135
+ with open (os .path .join (scriptPath , "testHeader.qlfrag" ), "r" ) as header :
136
+ shutil .copyfileobj (header , f )
137
+ f .write (", " .join ('"%s"' % modelSpecRow [0 ].strip () for modelSpecRow in supportModelRows ))
138
+ with open (os .path .join (scriptPath , "testFooter.qlfrag" ), "r" ) as header :
139
+ shutil .copyfileobj (header , f )
140
+
141
+ cmd = ['codeql' , 'query' , 'format' , '-qq' , '-i' , resultQl ]
142
+ subprocess .call (cmd )
143
+
144
+ shutil .rmtree (workDir )
0 commit comments