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