66import subprocess
77import sys
88import tempfile
9- import re
109import argparse
1110
12-
13- def quote_if_needed (row ):
14- if row != "true" and row != "false" :
15- return '"' + row + '"'
16- # subtypes column
17- return row [0 ].upper () + row [1 :]
18-
19-
20- def parseData (data ):
21- rows = {}
22-
23- for row in data :
24- d = row [0 ].split (";" )
25- namespace = d [0 ]
26- d = map (quote_if_needed , d )
27- helpers .insert_update (rows , namespace , " - [" + ", " .join (d ) + "]\n " )
28-
29- return rows
30-
31-
3211description = """\
3312 This generates summary, source, sink and neutral models for the code in the database.
3413The files will be placed in `LANGUAGE/ql/lib/ext/generated/modelgenerator/DIR`"""
@@ -182,76 +161,65 @@ def runQuery(self, query):
182161
183162 return helpers .readData (self .workDir , resultBqrs )
184163
185- def asAddsTo (self , rows , predicate ):
186- extensions = {}
187- for key in rows :
188- extensions [key ] = helpers .addsToTemplate .format (
189- f"codeql/{ self .language } -all" , predicate , rows [key ]
190- )
191- return extensions
192-
193- def getAddsTo (self , query , predicate ):
164+ def convert_if_needed (self , value ):
165+ # Convert a string value to a boolean if it is "true" or "false". Otherwise, return the original
166+ # value as a string. Since no extensible predicate currently uses integers, don't attempt to
167+ # convert numeric strings to integers.
168+ if value == "true" :
169+ return True
170+ if value == "false" :
171+ return False
172+ return value
173+
174+ def merge_query_results (self , query , predicate , merger ):
194175 data = self .runQuery (query )
195- rows = parseData (data )
196- if self .single_file and rows :
197- rows = {self .single_file : "" .join (rows .values ())}
198- return self .asAddsTo (rows , predicate )
176+ for row in data :
177+ d = row [0 ].split (";" )
178+ namespace = d [0 ]
179+ d = list (map (self .convert_if_needed , d ))
180+ if self .single_file :
181+ merger .add_row (self .single_file , predicate , d )
182+ else :
183+ merger .add_row (namespace , predicate , d )
184+
185+ def make_extensions (self ):
186+ merger = helpers .ExtensionMerger (f"codeql/{ self .language } -all" )
199187
200- def makeContent (self ):
201- summaryAddsTo = {}
202188 if self .with_summaries :
203- summaryAddsTo = self .getAddsTo (
204- "CaptureSummaryModels.ql" , helpers .summaryModelPredicate
189+ self .merge_query_results (
190+ "CaptureSummaryModels.ql" , helpers .summaryModelPredicate , merger
205191 )
206192
207- sinkAddsTo = {}
208193 if self .with_sinks :
209- sinkAddsTo = self .getAddsTo (
210- "CaptureSinkModels.ql" , helpers .sinkModelPredicate
194+ self .merge_query_results (
195+ "CaptureSinkModels.ql" , helpers .sinkModelPredicate , merger
211196 )
212197
213- sourceAddsTo = {}
214198 if self .with_sources :
215- sourceAddsTo = self .getAddsTo (
216- "CaptureSourceModels.ql" , helpers .sourceModelPredicate
199+ self .merge_query_results (
200+ "CaptureSourceModels.ql" , helpers .sourceModelPredicate , merger
217201 )
218202
219- neutralAddsTo = {}
220203 if self .with_neutrals :
221- neutralAddsTo = self .getAddsTo (
222- "CaptureNeutralModels.ql" , helpers .neutralModelPredicate
204+ self .merge_query_results (
205+ "CaptureNeutralModels.ql" , helpers .neutralModelPredicate , merger
223206 )
224207
225- return helpers . merge ( summaryAddsTo , sinkAddsTo , sourceAddsTo , neutralAddsTo )
208+ return merger
226209
227- def makeTypeBasedContent (self ):
210+ def make_type_based_extensions (self ):
211+ merger = helpers .ExtensionMerger (f"codeql/{ self .language } -all" )
228212 if self .with_typebased_summaries :
229- typeBasedSummaryAddsTo = self .getAddsTo (
230- "CaptureTypeBasedSummaryModels.ql" , helpers .summaryModelPredicate
231- )
232- else :
233- typeBasedSummaryAddsTo = {}
234-
235- return typeBasedSummaryAddsTo
236-
237- def save (self , extensions , extension ):
238- # Create a file for each namespace and save models.
239- extensionTemplate = """# THIS FILE IS AN AUTO-GENERATED MODELS AS DATA FILE. DO NOT EDIT.
240- extensions:
241- {0}"""
242- for entry in extensions :
243- # Replace problematic characters with dashes, and collapse multiple dashes.
244- sanitizedEntry = re .sub (
245- r"-+" , "-" , entry .replace ("/" , "-" ).replace (":" , "-" )
213+ self .merge_query_results (
214+ "CaptureTypeBasedSummaryModels.ql" ,
215+ helpers .summaryModelPredicate ,
216+ merger ,
246217 )
247- target = os .path .join (self .generated_frameworks , sanitizedEntry + extension )
248- with open (target , "w" ) as f :
249- f .write (extensionTemplate .format (extensions [entry ]))
250- print ("Models as data extensions written to " + target )
218+ return merger
251219
252220 def run (self ):
253- content = self .makeContent ()
254- typeBasedContent = self .makeTypeBasedContent ()
221+ merger = self .make_extensions ()
222+ type_based_extensions = self .make_type_based_extensions ()
255223
256224 if self .dry_run :
257225 print ("Models as data extensions generated, but not written to file." )
@@ -263,10 +231,12 @@ def run(self):
263231 or self .with_summaries
264232 or self .with_neutrals
265233 ):
266- self .save (content , ".model.yml" )
234+ merger .save (self . generated_frameworks , ".model.yml" )
267235
268236 if self .with_typebased_summaries :
269- self .save (typeBasedContent , ".typebased.model.yml" )
237+ type_based_extensions .save (
238+ self .generated_frameworks , ".typebased.model.yml"
239+ )
270240
271241
272242if __name__ == "__main__" :
0 commit comments