|
1 # ################################################################# |
|
2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 # All rights reserved. |
|
4 # |
|
5 # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: |
|
6 # |
|
7 # * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. |
|
8 # * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. |
|
9 # * Neither the name of Nokia Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. |
|
10 # |
|
11 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
|
12 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS |
|
13 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
14 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
15 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.# |
|
16 # |
|
17 # linescanner.py - the main body of CodeScanner |
|
18 # |
|
19 # ################################################################# |
|
20 |
|
21 import base64 |
|
22 import datetime |
|
23 import encodings |
|
24 import getopt |
|
25 import os |
|
26 import os.path |
|
27 import psyco |
|
28 import re |
|
29 import sys |
|
30 import xml.dom.minidom |
|
31 import zlib |
|
32 |
|
33 # Ignore flags |
|
34 KIgnoreNothing = 0 |
|
35 KIgnoreComments = 1 |
|
36 KIgnoreCommentsAndQuotes = 2 |
|
37 KIgnoreQuotes = 3 |
|
38 |
|
39 # Severities for the scripts |
|
40 KSeverityHigh = 0 |
|
41 KSeverityMedium = 1 |
|
42 KSeverityLow = 2 |
|
43 |
|
44 # The names used in the XML configuration file for severity element names. |
|
45 KSeverityConfigMap = { |
|
46 KSeverityHigh : "high", |
|
47 KSeverityMedium : "medium", |
|
48 KSeverityLow : "low"} |
|
49 |
|
50 # The names used in the HTML summary file for severity element names. |
|
51 KSeverityHTMLMap = { |
|
52 KSeverityHigh : "High", |
|
53 KSeverityMedium : "Medium", |
|
54 KSeverityLow : "Low"} |
|
55 |
|
56 # Categories for the scripts |
|
57 KCategoryLegal = "Legal Code" |
|
58 KCategoryDefinitePanic = "Always Panic" |
|
59 KCategoryCanPanic = "Can Panic" |
|
60 KCategoryWrongFunctionality = "Wrong Functionality" |
|
61 KCategoryLocalisation = "Localisation" |
|
62 KCategoryPerformance = "Performance" |
|
63 KCategoryCodingStandards = "Coding Standard" |
|
64 KCategoryDocumentation = "Documentation" |
|
65 KCategoryCodeReviewGuides = "Code Review Guide" |
|
66 KCategoryOther = "Other" |
|
67 |
|
68 KCategoryHtmlDisplayOrder = [KCategoryLegal, |
|
69 KCategoryDefinitePanic, |
|
70 KCategoryCanPanic, |
|
71 KCategoryWrongFunctionality, |
|
72 KCategoryLocalisation, |
|
73 KCategoryPerformance, |
|
74 KCategoryCodingStandards, |
|
75 KCategoryDocumentation, |
|
76 KCategoryCodeReviewGuides, |
|
77 KCategoryOther] |
|
78 |
|
79 # The names used in the XML configuration file for category element names. |
|
80 KCategoryConfigMap = { |
|
81 KCategoryLegal : "legal", |
|
82 KCategoryDefinitePanic : "panic", |
|
83 KCategoryCanPanic : "canpanic", |
|
84 KCategoryWrongFunctionality : "functionality", |
|
85 KCategoryLocalisation : "localisation", |
|
86 KCategoryPerformance : "performance", |
|
87 KCategoryCodingStandards : "codingstandards", |
|
88 KCategoryDocumentation : "documentation", |
|
89 KCategoryCodeReviewGuides : "codereview", |
|
90 KCategoryOther : "other"} |
|
91 |
|
92 #Custom rule keyword types |
|
93 KKeywordBaseClass = "baseclass" |
|
94 KKeywordCall = "call" |
|
95 KKeywordClassName = "class" |
|
96 KKeywordComment = "comment" |
|
97 KKeywordGeneric = "generic" |
|
98 KKeywordLocal = "local" |
|
99 KKeywordMacro = "macro" |
|
100 KKeywordMember = "member" |
|
101 KKeywordMethod = "method" |
|
102 KKeywordParameter = "parameter" |
|
103 KKeywordUnknown = "unknown" |
|
104 |
|
105 #The names used in the XML configuration file for custom rule keyword types. |
|
106 KCustomRuleKeywordMap = { |
|
107 KKeywordBaseClass : "baseclass", |
|
108 KKeywordCall : "call", |
|
109 KKeywordClassName : "class", |
|
110 KKeywordComment : "comment", |
|
111 KKeywordGeneric : "generic", |
|
112 KKeywordLocal : "local", |
|
113 KKeywordMacro : "macro", |
|
114 KKeywordMember : "member", |
|
115 KKeywordMethod : "method", |
|
116 KKeywordParameter : "parameter", |
|
117 KKeywordUnknown : "unknown"} |
|
118 |
|
119 KVersion = "Nokia CodeScanner version 2.1.4" |
|
120 KCopyrightLine1 = "Copyright (c) 2007-2009. Nokia Corporation. All rights reserved." |
|
121 KCopyrightLine1Html = "Copyright © 2007-2009. Nokia Corporation. All rights reserved." |
|
122 KCopyrightLine2 = "For product and support information, visit www.forum.nokia.com." |
|
123 KWww = "www.forum.nokia.com" |
|
124 |
|
125 stringPool = {} |
|
126 #!LOCALISEHERE |
|
127 |
|
128 def Usage(code, msg=""): |
|
129 print msg |
|
130 print |
|
131 print KVersion |
|
132 print |
|
133 print "Usage: CodeScanner [options] <source dir> [<output dir>]" |
|
134 print " or: CodeScanner [options] <source file> [<output dir>]" |
|
135 print |
|
136 print "options:" |
|
137 print " -h - display command help" |
|
138 print " -v - display verbose messages" |
|
139 print " -c <config file> - use specified configuration file" |
|
140 print " -i <source dir/file> - specify additional directory/file to scan" |
|
141 print " -l <log file> - create debug log with specified filename" |
|
142 print " -o html|xml|std - specify output format : HTML, XML or StdOut; default is HTML" |
|
143 print " -x url to lxr site" |
|
144 print " -r lxr version" |
|
145 print " -t on/off - create a time-stamped directory for results, default is on" |
|
146 print |
|
147 print "<source dir> is the directory containing the source code to scan" |
|
148 print "<source file> is the single file containing the source code to scan" |
|
149 print "<output dir> is the directory in which to produce the output" |
|
150 print |
|
151 print "Notes:" |
|
152 print "<source dir> and <output dir> cannot be identical" |
|
153 print "<output dir> cannot be the root of a drive" |
|
154 print |
|
155 print KCopyrightLine1 |
|
156 print KCopyrightLine2 |
|
157 if scanner.iLog <> None: |
|
158 scanner.iLog.Write("usage(): exiting with code " + str(code)) |
|
159 scanner.iLog.Close() |
|
160 sys.exit(code) |
|
161 |
|
162 def DefaultCompare(aLines, aCurrentline, aRematch, aFilename): |
|
163 if aRematch.search(aLines[aCurrentline]): |
|
164 return 1 |
|
165 else: |
|
166 return 0 |
|
167 |
|
168 def DefaultFuncParamCompare(lines, currentline, rematch, filename): |
|
169 # distinguish local declaration from function parameter |
|
170 line = lines[currentline] |
|
171 m = rematch.search(line) |
|
172 if m: |
|
173 isFuncParam = (line.find(")") <> -1) |
|
174 isLocal = (line.find(";") <> -1) |
|
175 |
|
176 while (not isFuncParam) and (not isLocal) and (currentline + 1 < len(lines)): |
|
177 currentline += 1 |
|
178 line = lines[currentline] |
|
179 isFuncParam = (line.find(")") <> -1) |
|
180 isLocal = (line.find(";") <> -1) |
|
181 |
|
182 if isFuncParam: |
|
183 return 1 |
|
184 elif isLocal: |
|
185 return 0 |
|
186 |
|
187 return 0 |
|
188 |
|
189 def ScanDirOrFile(argument): |
|
190 if os.path.isdir(argument): |
|
191 scanner.iComponentManager.SetRoot(argument) |
|
192 scanner.TraverseDirectory(argument) |
|
193 elif os.path.isfile(argument): |
|
194 parentDir = os.path.dirname(argument) |
|
195 scanner.iComponentManager.SetRoot(parentDir) |
|
196 scanner.iComponentManager.BeginDirectory(parentDir) |
|
197 numberOfLinesScanned = 0 |
|
198 numberOfLinesScanned += scanner.ScanFile(argument) |
|
199 scanner.iComponentManager.EndDirectory(parentDir, numberOfLinesScanned) |
|
200 else: |
|
201 print "Unable to open specified source file: " + argument |
|
202 sys.exit(2) |
|
203 |
|
204 |
|
205 class CScript: |
|
206 |
|
207 # ####################################################### |
|
208 # CScript - a test script |
|
209 |
|
210 def __init__(self, aScriptName): |
|
211 self.iScriptName = aScriptName |
|
212 self.iCompare = DefaultCompare |
|
213 self.iReString = "" |
|
214 self.iReMatch = re.compile("") |
|
215 self.iTitle = stringPool[aScriptName + "!title"] |
|
216 self.iIdeTitle = stringPool[aScriptName + "!ideTitle"] |
|
217 self.iFileExts = [] |
|
218 self.iIgnore = KIgnoreNothing |
|
219 self.iDescription = stringPool[aScriptName + "!description"] |
|
220 self.iSeverity = KSeverityMedium |
|
221 self.iBaseClass = "" |
|
222 |
|
223 def ScriptConfig(self): |
|
224 if (scanner.iDomConfig <> None): |
|
225 for scriptsNode in scanner.iDomConfig.getElementsByTagName("scripts"): |
|
226 for scriptNode in scriptsNode.getElementsByTagName(self.iScriptName): |
|
227 return scriptNode |
|
228 return None |
|
229 |
|
230 def DefaultInheritanceCompare(self, lines, currentline, rematch, filename): |
|
231 m = rematch.search(lines[currentline]) |
|
232 if m: |
|
233 inheritanceString = m.group(3) |
|
234 # check for inheritance list spanning multiple lines |
|
235 i = currentline + 1 |
|
236 while (inheritanceString.find("{") == -1) and i < len(lines): |
|
237 if (inheritanceString.find(";") <> -1): |
|
238 return 0 |
|
239 inheritanceString += lines[i] |
|
240 i += 1 |
|
241 |
|
242 # construct inheritance class list |
|
243 inheritancelist = inheritanceString.split(",") |
|
244 reclass = re.compile("[\s:]*(public|protected|private)?\s*([\w:]+)") |
|
245 classlist = [] |
|
246 for inheritance in inheritancelist: |
|
247 match = reclass.search(inheritance) |
|
248 if match: |
|
249 inheritclass = match.group(2) |
|
250 colonpos = inheritclass.rfind(":") |
|
251 if (colonpos <> -1): |
|
252 inheritclass = inheritclass[colonpos + 1:] |
|
253 classlist.append(inheritclass) |
|
254 |
|
255 # search for inheritance class |
|
256 for classname in classlist: |
|
257 if classname == self.iBaseClass: |
|
258 return 1 |
|
259 |
|
260 return 0 |
|
261 |
|
262 |
|
263 class CCustomScript(CScript): |
|
264 |
|
265 # ####################################################### |
|
266 # CScript - a test script based on a custom rule |
|
267 |
|
268 def __init__(self, aScriptName): |
|
269 self.iScriptName = aScriptName |
|
270 self.iCompare = DefaultCompare |
|
271 self.iReString = "" |
|
272 self.iTitle = "" |
|
273 self.iIdeTitle = "" |
|
274 self.iFileExts = [] |
|
275 self.iIgnore = KIgnoreNothing |
|
276 self.iDescription = "" |
|
277 |
|
278 |
|
279 class CCategorisedScripts: |
|
280 |
|
281 # ####################################################### |
|
282 # CCategorisedScripts - a collection of scripts sorted |
|
283 # by script category (panic, can panic, etc.) |
|
284 |
|
285 def AddScript(self, aScript): |
|
286 # do we have a category for this already? |
|
287 category = aScript.iCategory |
|
288 if (not self.iScripts.has_key(category)): |
|
289 # no, create a linear array here |
|
290 self.iScripts[category] = [] |
|
291 |
|
292 # append to the correct category |
|
293 self.iScripts[category].append(aScript) |
|
294 |
|
295 # compile the reg-ex otherwise will get continuous hits |
|
296 aScript.iReMatch = re.compile(aScript.iReString, re.VERBOSE) |
|
297 |
|
298 def AllScripts(self): |
|
299 result = [] |
|
300 for scripts in self.iScripts.values(): |
|
301 result += scripts |
|
302 |
|
303 return result |
|
304 |
|
305 def PrintListOfTestScripts(self): |
|
306 for category in self.iScripts.keys(): |
|
307 print(category + "\n----------------------------------") |
|
308 for script in self.iScripts[category]: |
|
309 print("\t" + script.iScriptName) |
|
310 |
|
311 print("") |
|
312 |
|
313 # iScripts is a 2D array, 1st level is a hash of categories |
|
314 # 2nd level is linear array |
|
315 iScripts = {} |
|
316 |
|
317 class CLogger: |
|
318 |
|
319 # ####################################################### |
|
320 # CLogger |
|
321 # a simple log file interface |
|
322 |
|
323 def __init__(self, aFilename): |
|
324 if aFilename != None and len(aFilename) > 0: |
|
325 self.iFile = file(aFilename, "w") |
|
326 self.iFile.write(KVersion + " started at " + datetime.datetime.now().ctime() + "\n") |
|
327 else: |
|
328 self.iFile = None |
|
329 |
|
330 def Write(self, aText): |
|
331 if self.iFile <> None: |
|
332 self.iFile.write(str(datetime.datetime.now().time())+":"+aText+"\n") |
|
333 self.iFile.flush() |
|
334 |
|
335 def Close(self): |
|
336 if self.iFile <> None: |
|
337 self.iFile.write(KVersion + " ended at " + datetime.datetime.now().ctime() + "\n") |
|
338 self.iFile.close() |
|
339 |
|
340 |
|
341 class CRendererBase: |
|
342 |
|
343 # ####################################################### |
|
344 # CRendererBase - base class for renderers |
|
345 |
|
346 def RegisterSelf(self, aName, aDescription, aRendererManager): |
|
347 self.iName = aName |
|
348 self.iDescription = aDescription |
|
349 aRendererManager.AddRenderer(self) |
|
350 def BeginComponent(self, aComponent): |
|
351 return |
|
352 def BeginFile(self, aFilename): |
|
353 return |
|
354 def ReportError(self, aLineContext, aScript): |
|
355 return |
|
356 def EndFile(self): |
|
357 return |
|
358 def EndComponent(self, aComponent): |
|
359 return |
|
360 |
|
361 |
|
362 class CStdOutRenderer(CRendererBase): |
|
363 |
|
364 # ####################################################### |
|
365 # CStdOutRenderer - renderer for Standard Console Output |
|
366 # Output goes to standard output; when run in Carbide, |
|
367 # this shows up in the output window. Correctly formatted |
|
368 # lines can then be selected, automatically selecting |
|
369 # the corresponding line of the associated source file. |
|
370 # The format is: |
|
371 # <filename>(<line>) : <comment> |
|
372 |
|
373 def __init__(self, aRendererManager): |
|
374 self.RegisterSelf("stdout", "StdOut renderer", aRendererManager) |
|
375 print KVersion |
|
376 |
|
377 def BeginComponent(self, aComponent): |
|
378 return |
|
379 |
|
380 def BeginFile(self, aFilename): |
|
381 self.iErrorCount = 0 |
|
382 scanner.ReportAction("Scanning file " + aFilename) |
|
383 |
|
384 def ReportError(self, aLineContext, aScript): |
|
385 self.iErrorCount += 1 |
|
386 if (aScript.iSeverity == KSeverityLow): |
|
387 msgType = "info" |
|
388 elif (aScript.iSeverity == KSeverityMedium): |
|
389 msgType = "warning" |
|
390 elif (aScript.iSeverity == KSeverityHigh): |
|
391 msgType = "error" |
|
392 print(aLineContext.iFileName + "(" + str(aLineContext.iLineNumber) + ") : " + msgType + ": " + aScript.iScriptName + ": " + KSeverityConfigMap[aScript.iSeverity] + ": " + KCategoryConfigMap[aScript.iCategory] + ": " + aScript.iIdeTitle) |
|
393 if len(scanner.iRendererManager.iAnnotation)>0: |
|
394 print scanner.iRendererManager.iAnnotation |
|
395 scanner.iRendererManager.iAnnotation = "" |
|
396 |
|
397 def EndFile(self): |
|
398 scanner.ReportAction("Total problems found in file: " + str(self.iErrorCount)) |
|
399 |
|
400 def EndComponent(self, aComponent): |
|
401 scanner.iEndTime = datetime.datetime.now().ctime() |
|
402 return |
|
403 |
|
404 |
|
405 class CXmlComponentSummaryFile: |
|
406 # ######################################################### |
|
407 # CXmlComponentSummaryFile |
|
408 # Encapsulates the script (problem) summary for XML output. |
|
409 # For each script, there is a listing for occurrences |
|
410 # of that script's problem and location of each occurrence. |
|
411 |
|
412 def CreateSummary(self, aXmlRenderer): |
|
413 try: |
|
414 outputPath = os.path.normpath(os.path.join(aXmlRenderer.iOutputDirectory, "problemIndex.xml")) |
|
415 outputFile = file(outputPath, "w") |
|
416 except IOError: |
|
417 scanner.ReportError("IOError : Unable to create output file " + outputPath) |
|
418 else: |
|
419 errors = aXmlRenderer.iErrors |
|
420 level = 0 |
|
421 indent = " " |
|
422 outputFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") |
|
423 outputFile.write(level * indent + "<problemIndex>\n") |
|
424 level += 1 |
|
425 for category in KCategoryHtmlDisplayOrder: |
|
426 found = False |
|
427 if scanner.iCategoriedScripts.iScripts.has_key(category): |
|
428 for script in scanner.iCategoriedScripts.iScripts[category]: |
|
429 if errors.has_key(script.iScriptName): |
|
430 found = True |
|
431 break |
|
432 if found: |
|
433 outputFile.write(level * indent + "<category") |
|
434 outputFile.write(" name=\"" + KCategoryConfigMap[category] + "\">\n") |
|
435 level += 1 |
|
436 for script in scanner.iCategoriedScripts.iScripts[category]: |
|
437 if errors.has_key(script.iScriptName): |
|
438 outputFile.write(level * indent + "<problem") |
|
439 outputFile.write(" name=\"" + script.iScriptName + "\"") |
|
440 outputFile.write(" severity=\"" + KSeverityConfigMap[script.iSeverity] + "\">\n") |
|
441 level += 1 |
|
442 for fileName, lines in errors[script.iScriptName].items(): |
|
443 outputFile.write(level * indent + "<file") |
|
444 outputFile.write(" path=\"" + fileName + "\">\n") |
|
445 level += 1 |
|
446 for lineNo in lines: |
|
447 outputFile.write(level * indent + str(lineNo) + "\n") |
|
448 level -= 1 |
|
449 outputFile.write(level * indent + "</file>\n") |
|
450 level -= 1 |
|
451 outputFile.write(level * indent + "</problem>\n") |
|
452 level -= 1 |
|
453 outputFile.write(level * indent + "</category>\n") |
|
454 level -= 1 |
|
455 outputFile.write(level * indent + "</problemIndex>\n") |
|
456 outputFile.close() |
|
457 |
|
458 |
|
459 class CXmlRenderer(CRendererBase): |
|
460 |
|
461 # ######################################## |
|
462 # CXmlRenderer - a renderer for XML output |
|
463 |
|
464 def __init__(self, aRendererManager, aOutputDirectory): |
|
465 self.RegisterSelf("xml", "XML renderer", aRendererManager) |
|
466 self.iOutputDirectory = aOutputDirectory |
|
467 if os.path.isdir(self.iOutputDirectory) != True : |
|
468 os.makedirs(self.iOutputDirectory) |
|
469 self.iErrors = {} |
|
470 print |
|
471 print KVersion |
|
472 print KCopyrightLine1 |
|
473 print KCopyrightLine2 |
|
474 |
|
475 def BeginComponent(self, aComponent): |
|
476 return |
|
477 |
|
478 def BeginFile(self, aFilename): |
|
479 self.iFilename = aFilename |
|
480 scanner.ReportAction("Scanning file " + aFilename) |
|
481 |
|
482 def ReportError(self, aLineContext, aScript): |
|
483 scriptName = aScript.iScriptName |
|
484 fileName = aLineContext.iFileName |
|
485 lineNumber = aLineContext.iLineNumber |
|
486 if (not self.iErrors.has_key(scriptName)): |
|
487 self.iErrors[scriptName] = {} |
|
488 if (not self.iErrors[scriptName].has_key(fileName)): |
|
489 self.iErrors[scriptName][fileName] = [] |
|
490 self.iErrors[scriptName][fileName].append(lineNumber) |
|
491 |
|
492 def EndFile(self): |
|
493 #tbd |
|
494 return |
|
495 |
|
496 def EndComponent(self, aComponent): |
|
497 relativeComponentName = scanner.iComponentManager.RelativeComponentName(aComponent.iFullPath) |
|
498 if len(relativeComponentName) < 1: # root component - final component |
|
499 scanner.iEndTime = datetime.datetime.now().ctime() |
|
500 componentSummaryFile = CXmlComponentSummaryFile() |
|
501 componentSummaryFile.CreateSummary(self) |
|
502 |
|
503 |
|
504 class CHtmlOutputFileBase: |
|
505 |
|
506 # ####################################################### |
|
507 # CHtmlOutputFileBase - base class for HTML output files |
|
508 |
|
509 def WriteHeader(self, aOutputFile): |
|
510 aOutputFile.write("<html><body>") |
|
511 |
|
512 def Write(self, aOutputFile, aText): |
|
513 aOutputFile.write(aText) |
|
514 |
|
515 def WriteLink(self, aOutputFile, aHref, aText): |
|
516 aHref = self.CleanupLink(aHref) |
|
517 aOutputFile.write("<a href=\"" + aHref + "\">" + aText + "</a>") |
|
518 |
|
519 def WriteElement(self, aOutputFile, aElementName, aElementValue): |
|
520 aOutputFile.write("<"+aElementName+">"+aElementValue+"</"+aElementName+">") |
|
521 |
|
522 def WriteBreak(self, aOutputFile): |
|
523 aOutputFile.write("<br>") |
|
524 |
|
525 def WriteFooter(self, aOutputFile): |
|
526 aOutputFile.write("<br><hr><center><h5>"+KCopyrightLine1Html+"</h5>") |
|
527 aOutputFile.write("<h5>") |
|
528 CHtmlOutputFileBase.WriteLink(self, aOutputFile, "http://"+KWww, KWww) |
|
529 aOutputFile.write("</h5></center></body></html>") |
|
530 |
|
531 def CleanupLink(self, aHref): |
|
532 # Mozilla Firefox does not handle link with the '#' character correctly, |
|
533 # so we need to replace it with the equivalent URL encoding "%23" |
|
534 aHref = aHref.replace("#", "%23") |
|
535 # Mozilla Firefox sometimes does not handle link with '\' correctly, |
|
536 # so we need to replace it with '/' |
|
537 aHref = aHref.replace('\\', '/') |
|
538 return aHref |
|
539 |
|
540 |
|
541 class CHtmlOutputFile(CHtmlOutputFileBase): |
|
542 |
|
543 # ####################################################### |
|
544 # CHtmlOutputFile - simplified access to HTML output file |
|
545 |
|
546 def __init__(self, aOutputPath): |
|
547 if not os.path.isdir(os.path.dirname(aOutputPath)): |
|
548 os.makedirs(os.path.dirname(aOutputPath)) |
|
549 self.iOutputFile = file(aOutputPath, "w") |
|
550 self.WriteHeader(self.iOutputFile) |
|
551 |
|
552 def Write(self, aText): |
|
553 CHtmlOutputFileBase.Write(self, self.iOutputFile, aText) |
|
554 |
|
555 def WriteLink(self, aHref, aText): |
|
556 CHtmlOutputFileBase.WriteLink(self, self.iOutputFile, aHref, aText) |
|
557 |
|
558 def WriteElement(self, aElementName, aElementValue): |
|
559 CHtmlOutputFileBase.WriteElement(self, self.iOutputFile, aElementName, aElementValue) |
|
560 |
|
561 def WriteBreak(self): |
|
562 CHtmlOutputFileBase.WriteBreak(self, self.iOutputFile) |
|
563 |
|
564 def Close(self): |
|
565 self.WriteFooter(self.iOutputFile) |
|
566 self.iOutputFile.close() |
|
567 |
|
568 |
|
569 class CHtmlComponentSummaryFiles: |
|
570 |
|
571 # ####################################################### |
|
572 # CHtmlComponentSummaryFiles |
|
573 # Encapsulates the component summary files for HTML output. |
|
574 # For each component, there is a component report file listing the number |
|
575 # of occurrences of each problem type. There is also a single index or |
|
576 # summary file with links to each of the component report files. |
|
577 |
|
578 def CreateSummaries(self, aHtmlRenderer, aOutputDirectory): |
|
579 totalErrorCount = 0 |
|
580 outputPath = os.path.normpath(os.path.join(aOutputDirectory, "componentIndex.html")) |
|
581 componentSummaryFile = CHtmlOutputFile(outputPath) |
|
582 componentSummaryFile.Write("<font face=verdana>") |
|
583 componentSummaryFile.WriteElement("h2", "Component Summary") |
|
584 componentSummaryFile.Write("Source: "+scanner.iSource) |
|
585 componentSummaryFile.WriteBreak() |
|
586 componentSummaryFile.Write("Scan started at: " + scanner.iStartTime) |
|
587 componentSummaryFile.WriteBreak() |
|
588 componentSummaryFile.Write("Scan completed at: " + scanner.iEndTime) |
|
589 componentSummaryFile.WriteBreak() |
|
590 componentSummaryFile.WriteBreak() |
|
591 componentSummaryFile.WriteLink("problemIndex.html", "View problems by type") |
|
592 componentSummaryFile.WriteBreak() |
|
593 componentSummaryFile.Write("<hr>") |
|
594 componentSummaryFile.WriteBreak() |
|
595 componentSummaryFile.Write("<table border=\"1\" width=\"100%\">") |
|
596 componentSummaryFile.Write("<tr bgcolor=\"#0099ff\">") |
|
597 componentSummaryFile.WriteElement("th width=\"75%\"", "Component") |
|
598 componentSummaryFile.WriteElement("th", "Items Found") |
|
599 componentSummaryFile.WriteElement("th", "Lines of Code") |
|
600 componentSummaryFile.WriteElement("th", "Possible Defects/KLOC") |
|
601 componentSummaryFile.Write("</tr>") |
|
602 for component in scanner.iComponentManager.iCompletedComponents: |
|
603 componentName = scanner.iComponentManager.ComponentName(component.iFullPath) |
|
604 outputPath = os.path.normpath(os.path.join(aOutputDirectory, "byComponent")) |
|
605 outputPath = os.path.normpath(os.path.join(outputPath, componentName)) |
|
606 outputPath = os.path.normpath(os.path.join(outputPath, "componentSummary.html")) |
|
607 errorCount = self.WriteComponentReport(aHtmlRenderer, outputPath, component.iFullPath, componentName) |
|
608 if (errorCount > 0): |
|
609 totalErrorCount = totalErrorCount + errorCount |
|
610 numberOfLinesScanned = component.iNumberOfLinesScanned |
|
611 if (numberOfLinesScanned > 0): |
|
612 defectsPerKLOC = int((1000.0 / numberOfLinesScanned) * errorCount) |
|
613 else: |
|
614 defectsPerKLOC = 0 |
|
615 componentSummaryFile.Write("<tr>") |
|
616 componentSummaryFile.Write("<td>") |
|
617 relOutputPath = os.path.normpath(os.path.join("byComponent", componentName)) |
|
618 relOutputPath = os.path.normpath(os.path.join(relOutputPath, "componentSummary.html")) |
|
619 componentSummaryFile.WriteLink(relOutputPath, component.iFullPath) |
|
620 componentSummaryFile.Write("</td>") |
|
621 componentSummaryFile.Write("<td>") |
|
622 componentSummaryFile.WriteElement("center",str(errorCount)) |
|
623 componentSummaryFile.Write("</td>") |
|
624 componentSummaryFile.Write("<td>") |
|
625 componentSummaryFile.WriteElement("center",str(numberOfLinesScanned)) |
|
626 componentSummaryFile.Write("</td>") |
|
627 componentSummaryFile.Write("<td>") |
|
628 componentSummaryFile.WriteElement("center",str(defectsPerKLOC)) |
|
629 componentSummaryFile.Write("</td>") |
|
630 componentSummaryFile.Write("</tr>") |
|
631 |
|
632 componentSummaryFile.Write("<tr>") |
|
633 componentSummaryFile.Write("<td>") |
|
634 componentSummaryFile.WriteElement("b", "Total") |
|
635 componentSummaryFile.Write("</td>") |
|
636 componentSummaryFile.Write("<td><center>") |
|
637 componentSummaryFile.WriteElement("b", str(totalErrorCount)) |
|
638 componentSummaryFile.Write("</center></td>") |
|
639 componentSummaryFile.Write("</tr>") |
|
640 |
|
641 componentSummaryFile.Write("</table>") |
|
642 componentSummaryFile.Close() |
|
643 |
|
644 def WriteComponentReport(self, aHtmlRenderer, aOutputPath, aComponentFullPath, aComponentName): |
|
645 totalErrorCount = 0 |
|
646 componentReportFile = CHtmlOutputFile(aOutputPath) |
|
647 componentReportFile.Write("<font face=verdana>") |
|
648 componentReportFile.WriteElement("h2", "Component Report") |
|
649 componentReportFile.WriteElement("h3", "Component: "+aComponentFullPath) |
|
650 componentReportFile.Write("<font face=verdana color=black>") |
|
651 found = False |
|
652 for category in KCategoryHtmlDisplayOrder: |
|
653 if scanner.iCategoriedScripts.iScripts.has_key(category): |
|
654 for script in scanner.iCategoriedScripts.iScripts[category]: |
|
655 errorCount = scanner.iComponentManager.ScriptComponentErrorCount(aComponentFullPath, script.iScriptName) |
|
656 if errorCount > 0: |
|
657 found = True |
|
658 break |
|
659 |
|
660 if found: |
|
661 componentReportFile.Write("<table border=\"1\" width=\"100%\">") |
|
662 componentReportFile.Write("<tr bgcolor=\"#0099ff\">") |
|
663 componentReportFile.WriteElement("th width=\"75%\"", "Problem") |
|
664 componentReportFile.WriteElement("th", "Items Found") |
|
665 componentReportFile.WriteElement("th", "Severity") |
|
666 componentReportFile.Write("</tr>") |
|
667 for category in KCategoryHtmlDisplayOrder: |
|
668 if scanner.iCategoriedScripts.iScripts.has_key(category): |
|
669 for script in scanner.iCategoriedScripts.iScripts[category]: |
|
670 errorCount = scanner.iComponentManager.ScriptComponentErrorCount(aComponentFullPath, script.iScriptName) |
|
671 if errorCount > 0: |
|
672 componentReportFile.Write("<tr>") |
|
673 componentReportFile.Write("<td>") |
|
674 #scriptComponentPath = aHtmlRenderer.ScriptComponentPath(aComponentFullPath, script.iScriptName) |
|
675 #componentReportFile.WriteLink(scriptComponentPath, script.iTitle) |
|
676 componentReportFile.WriteLink(script.iScriptName+".html", script.iTitle) |
|
677 componentReportFile.Write("</td>") |
|
678 componentReportFile.Write("<td>") |
|
679 componentReportFile.WriteElement("center", str(errorCount)) |
|
680 componentReportFile.Write("</td>") |
|
681 componentReportFile.Write("<td>") |
|
682 componentReportFile.WriteElement("center", KSeverityHTMLMap[script.iSeverity]) |
|
683 componentReportFile.Write("</td>") |
|
684 componentReportFile.Write("</tr>") |
|
685 totalErrorCount = totalErrorCount + errorCount |
|
686 componentReportFile.Write("<tr>") |
|
687 componentReportFile.Write("<td>") |
|
688 componentReportFile.WriteElement("b", "Total") |
|
689 componentReportFile.Write("</td>") |
|
690 componentReportFile.Write("<td><center>") |
|
691 componentReportFile.WriteElement("b", str(totalErrorCount)) |
|
692 componentReportFile.Write("</center></td>") |
|
693 componentReportFile.Write("</tr>") |
|
694 componentReportFile.Write("</table>") |
|
695 else: |
|
696 componentReportFile.WriteBreak() |
|
697 componentReportFile.WriteElement("i", "There are no items to report for this component.") |
|
698 componentReportFile.WriteBreak() |
|
699 componentReportFile.Close() |
|
700 return totalErrorCount |
|
701 |
|
702 |
|
703 class CHtmlScriptSummaryFiles: |
|
704 |
|
705 # ####################################################### |
|
706 # CHtmlScriptSummaryFiles |
|
707 # Encapsulates the script (problem) summary files for HTML output. |
|
708 # For each script, there is a file listing the number of occurrences |
|
709 # of that script's problem for each component. There is also a single |
|
710 # index or summary file with links to each of the problem report file. |
|
711 |
|
712 def CreateSummaries(self, aHtmlRenderer, aOutputDirectory): |
|
713 |
|
714 totalErrorCount = 0 |
|
715 |
|
716 outputPath = os.path.normpath(os.path.join(aOutputDirectory, "problemIndex.html")) |
|
717 scriptSummaryFile = CHtmlOutputFile(outputPath) |
|
718 scriptSummaryFile.Write("<font face=verdana>") |
|
719 scriptSummaryFile.WriteElement("h2", "Problem Summary") |
|
720 scriptSummaryFile.Write("Source: "+scanner.iSource) |
|
721 scriptSummaryFile.WriteBreak() |
|
722 scriptSummaryFile.Write("Scan started at: " + scanner.iStartTime) |
|
723 scriptSummaryFile.WriteBreak() |
|
724 scriptSummaryFile.Write("Scan completed at: " + scanner.iEndTime) |
|
725 scriptSummaryFile.WriteBreak() |
|
726 scriptSummaryFile.WriteBreak() |
|
727 scriptSummaryFile.WriteLink("componentIndex.html", "View problems by component") |
|
728 scriptSummaryFile.WriteBreak() |
|
729 scriptSummaryFile.Write("<hr>") |
|
730 scriptSummaryFile.WriteBreak() |
|
731 for category in KCategoryHtmlDisplayOrder: |
|
732 if scanner.iCategoriedScripts.iScripts.has_key(category): |
|
733 scriptSummaryFile.WriteElement("h3", "Category: "+category) |
|
734 scriptSummaryFile.Write("<table border=\"1\" width=\"100%\">") |
|
735 scriptSummaryFile.Write("<tr bgcolor=\"#0099ff\">") |
|
736 scriptSummaryFile.WriteElement("th width=\"75%\"", "Problem") |
|
737 scriptSummaryFile.WriteElement("th", "Items Found") |
|
738 scriptSummaryFile.WriteElement("th", "Severity") |
|
739 scriptSummaryFile.Write("</tr>") |
|
740 categoryErrorCount = 0 |
|
741 for script in scanner.iCategoriedScripts.iScripts[category]: |
|
742 outputPath = os.path.normpath(os.path.join(aOutputDirectory, "byProblem")) |
|
743 outputPath = os.path.normpath(os.path.join(outputPath, script.iScriptName+"Summary.html")) |
|
744 errorCount = self.WriteScriptReport(aHtmlRenderer, outputPath, script) |
|
745 categoryErrorCount = categoryErrorCount + errorCount |
|
746 scriptSummaryFile.Write("<tr>") |
|
747 scriptSummaryFile.Write("<td>") |
|
748 relOutputPath = os.path.normpath(os.path.join("byProblem", script.iScriptName+"Summary.html")) |
|
749 scriptSummaryFile.WriteLink(relOutputPath, script.iTitle) |
|
750 scriptSummaryFile.Write("</td>") |
|
751 scriptSummaryFile.Write("<td>") |
|
752 scriptSummaryFile.WriteElement("center", str(errorCount)) |
|
753 scriptSummaryFile.Write("</td>") |
|
754 scriptSummaryFile.Write("<td>") |
|
755 scriptSummaryFile.WriteElement("center", KSeverityHTMLMap[script.iSeverity]) |
|
756 scriptSummaryFile.Write("</td>") |
|
757 scriptSummaryFile.Write("</tr>") |
|
758 totalErrorCount = totalErrorCount + categoryErrorCount |
|
759 scriptSummaryFile.Write("<tr>") |
|
760 scriptSummaryFile.Write("<td>") |
|
761 scriptSummaryFile.WriteElement("b", "Category Total") |
|
762 scriptSummaryFile.Write("</td>") |
|
763 scriptSummaryFile.Write("<td>") |
|
764 scriptSummaryFile.WriteElement("center", "<b>"+str(categoryErrorCount)+"</b>") |
|
765 scriptSummaryFile.Write("</td>") |
|
766 scriptSummaryFile.Write("</tr>") |
|
767 scriptSummaryFile.Write("</table>") |
|
768 |
|
769 scriptSummaryFile.WriteBreak() |
|
770 scriptSummaryFile.WriteElement("b", "Total: " + str(totalErrorCount)) |
|
771 scriptSummaryFile.WriteBreak() |
|
772 |
|
773 scriptSummaryFile.Close() |
|
774 |
|
775 def WriteScriptReport(self, aHtmlRenderer, aOutputPath, aScript): |
|
776 totalErrorCount = 0 |
|
777 scriptReportFile = CHtmlOutputFile(aOutputPath) |
|
778 scriptReportFile.Write("<font face=verdana>") |
|
779 scriptReportFile.WriteElement("h2", "Problem Report") |
|
780 scriptReportFile.WriteElement("h3", "Problem: " + aScript.iTitle) |
|
781 scriptReportFile.Write(aScript.iDescription) |
|
782 scriptReportFile.WriteBreak() |
|
783 scriptReportFile.WriteBreak() |
|
784 |
|
785 found = False |
|
786 for component in scanner.iComponentManager.iCompletedComponents: |
|
787 errorCount = scanner.iComponentManager.ScriptComponentErrorCount(component.iFullPath, aScript.iScriptName) |
|
788 if errorCount > 0: |
|
789 found = True |
|
790 break |
|
791 |
|
792 if found: |
|
793 scriptReportFile.Write("<table border=\"1\" width=\"100%\">") |
|
794 scriptReportFile.Write("<tr bgcolor=\"#0099ff\">") |
|
795 scriptReportFile.WriteElement("th width=\"80%\"", "Component") |
|
796 scriptReportFile.WriteElement("th", "Items Found") |
|
797 scriptReportFile.Write("</tr>") |
|
798 for component in scanner.iComponentManager.iCompletedComponents: |
|
799 errorCount = scanner.iComponentManager.ScriptComponentErrorCount(component.iFullPath, aScript.iScriptName) |
|
800 if errorCount > 0: |
|
801 scriptReportFile.Write("<tr>") |
|
802 scriptReportFile.Write("<td>") |
|
803 scriptComponentPath = aHtmlRenderer.ScriptComponentPath(component.iFullPath, aScript.iScriptName, "..") |
|
804 scriptReportFile.WriteLink(scriptComponentPath, component.iFullPath) |
|
805 scriptReportFile.Write("</td>") |
|
806 scriptReportFile.Write("<td>") |
|
807 scriptReportFile.WriteElement("center", str(errorCount)) |
|
808 scriptReportFile.Write("</td>") |
|
809 scriptReportFile.Write("</tr>") |
|
810 totalErrorCount = totalErrorCount + errorCount |
|
811 scriptReportFile.Write("<tr>") |
|
812 scriptReportFile.Write("<td>") |
|
813 scriptReportFile.WriteElement("b", "Total") |
|
814 scriptReportFile.Write("</td>") |
|
815 scriptReportFile.Write("<td><center>") |
|
816 scriptReportFile.WriteElement("b", str(totalErrorCount)) |
|
817 scriptReportFile.Write("</center></td>") |
|
818 scriptReportFile.Write("</tr>") |
|
819 scriptReportFile.Write("</table>") |
|
820 else: |
|
821 scriptReportFile.WriteBreak() |
|
822 scriptReportFile.WriteElement("i", "There are no items of this problem type to report.") |
|
823 scriptReportFile.WriteBreak() |
|
824 |
|
825 scriptReportFile.Close() |
|
826 return totalErrorCount |
|
827 |
|
828 |
|
829 class CHtmlScriptComponentFile: |
|
830 |
|
831 # ####################################################### |
|
832 # CHtmlScriptComponentFile |
|
833 # Encapsulates access to the HTML output files with the greatest amount of detail. |
|
834 # Each of these files is for a specific problem (script) and for a specific component. |
|
835 |
|
836 # The file handle is closed between each call to avoid exhausting the system |
|
837 # limit for open file handles. Many of these files may be open at one time, |
|
838 # and the number of open files is dependent on both the directory structure |
|
839 # being traversed and the number of types of problems found. |
|
840 |
|
841 def __init__(self, aLxrUrl, aLxrVersion): |
|
842 self.iLxrUrl = aLxrUrl |
|
843 self.iLxrVersion = aLxrVersion |
|
844 |
|
845 def BeginOutputFile(self, aOutputPath, aScript, aComponentName): |
|
846 if not os.path.isdir(os.path.dirname(aOutputPath)): |
|
847 os.makedirs(os.path.dirname(aOutputPath)) |
|
848 outputFile = file(aOutputPath, "w") |
|
849 self.iScriptComponentFile = CHtmlOutputFileBase() |
|
850 self.iScriptComponentFile.Write(outputFile, "<font face=verdana>") |
|
851 self.iScriptComponentFile.WriteHeader(outputFile) |
|
852 self.iScriptComponentFile.WriteElement(outputFile, "h2", "Detailed Problem Report") |
|
853 self.iScriptComponentFile.WriteElement(outputFile, "h3", "Component: "+aComponentName) |
|
854 self.iScriptComponentFile.WriteElement(outputFile, "h3", "Problem: "+aScript.iTitle) |
|
855 self.iScriptComponentFile.Write(outputFile, aScript.iDescription) |
|
856 self.iScriptComponentFile.WriteBreak(outputFile) |
|
857 self.iScriptComponentFile.Write(outputFile, "<hr>") |
|
858 self.iScriptComponentFile.WriteBreak(outputFile) |
|
859 outputFile.close() |
|
860 |
|
861 def ReportError(self, aOutputPath, aLineContext): |
|
862 outputFile = file(aOutputPath, "a") |
|
863 if self.iLxrUrl == None: |
|
864 # Mozilla Firefox cannot open links to local files, |
|
865 # so it is necessary to convert local file path |
|
866 filePath = "file:///" + aLineContext.iFileName |
|
867 else: |
|
868 # generate link to LXR server instead of local file system |
|
869 filePath = self.iLxrUrl + aLineContext.iFileName[len(scanner.iComponentManager.iRootPath):] |
|
870 if self.iLxrVersion <> None: |
|
871 filePath = filePath + "?v="+self.iLxrVersion |
|
872 filePath = filePath + '#%03d'%aLineContext.iLineNumber |
|
873 self.iScriptComponentFile.WriteLink(outputFile, filePath, self.TrimFileName(aLineContext)) |
|
874 self.iScriptComponentFile.Write(outputFile, "(" + str(aLineContext.iLineNumber) + ") ") |
|
875 self.iScriptComponentFile.Write(outputFile, aLineContext.iClassName+"::"+aLineContext.iMethodName+" ") |
|
876 self.iScriptComponentFile.Write(outputFile, "<code><font color=red>"+self.CleanUpText(aLineContext.iLineText)) |
|
877 self.iScriptComponentFile.Write(outputFile, "</font></code>") |
|
878 self.iScriptComponentFile.WriteBreak(outputFile) |
|
879 if len(scanner.iRendererManager.iAnnotation)>0: |
|
880 self.iScriptComponentFile.Write(outputFile, scanner.iRendererManager.iAnnotation) |
|
881 self.iScriptComponentFile.WriteBreak(outputFile) |
|
882 scanner.iRendererManager.iAnnotation = "" |
|
883 outputFile.close() |
|
884 |
|
885 def EndOutputFile(self, aOutputPath): |
|
886 outputFile = file(aOutputPath, "a") |
|
887 self.iScriptComponentFile.WriteFooter(outputFile) |
|
888 outputFile.close() |
|
889 |
|
890 def TrimFileName(self, aLineContext): |
|
891 filename = aLineContext.iFileName |
|
892 componentNameLen = len(aLineContext.iComponentName) |
|
893 if len(filename) > componentNameLen: |
|
894 if filename[0:componentNameLen] == aLineContext.iComponentName: |
|
895 filename = filename[componentNameLen:] |
|
896 if filename[0] == os.path.sep: |
|
897 filename = filename[1:] |
|
898 return filename |
|
899 |
|
900 def CleanUpText(self, aLineText): |
|
901 # check for sub-strings that look like HTML tags and preform clean up if needed |
|
902 reTag = re.compile(r"""(<.+>)""", re.VERBOSE) |
|
903 foundTag = reTag.search(aLineText) |
|
904 if foundTag: |
|
905 aNewLineText = aLineText.replace("<", "<") |
|
906 aNewLineText = aNewLineText.replace(">", ">") |
|
907 return aNewLineText |
|
908 else: |
|
909 return aLineText |
|
910 |
|
911 |
|
912 def ComponentCompare(a, b): |
|
913 return cmp(os.path.normcase(a.iFullPath), os.path.normcase(b.iFullPath)) |
|
914 |
|
915 |
|
916 class CHtmlRenderer(CRendererBase): |
|
917 |
|
918 # ####################################################### |
|
919 # CHtmlRenderer - a renderer for HTML output |
|
920 |
|
921 # I have nothing to offer but blood, toil, tears and sweat. |
|
922 # - Winston Churchill, 1940 |
|
923 |
|
924 def __init__(self, aRendererManager, aOutputDirectory, aLxrUrl, aLxrVersion): |
|
925 self.RegisterSelf("html", "HTML renderer", aRendererManager) |
|
926 self.iOutputDirectory = aOutputDirectory |
|
927 if os.path.isdir(self.iOutputDirectory) != True : |
|
928 os.makedirs(self.iOutputDirectory) |
|
929 self.iScriptComponentFile = CHtmlScriptComponentFile(aLxrUrl, aLxrVersion) |
|
930 self.iScriptComponentFilePaths = {} |
|
931 print |
|
932 print KVersion |
|
933 print KCopyrightLine1 |
|
934 print KCopyrightLine2 |
|
935 |
|
936 def BeginFile(self, aFilename): |
|
937 self.iFilename = aFilename |
|
938 scanner.ReportAction("Scanning file " + aFilename) |
|
939 |
|
940 def ReportError(self, aLineContext, aScript): |
|
941 outputPath = self.ScriptComponentPath(aLineContext.iComponentName, aScript.iScriptName) |
|
942 if not os.path.isfile(outputPath): |
|
943 self.iScriptComponentFilePaths[aLineContext.iComponentName].append(outputPath) |
|
944 self.iScriptComponentFile.BeginOutputFile(outputPath, aScript, aLineContext.iComponentName) |
|
945 self.iScriptComponentFile.ReportError(outputPath, aLineContext) |
|
946 |
|
947 def EndFile(self): |
|
948 return |
|
949 |
|
950 def BeginComponent(self, aComponent): |
|
951 self.iScriptComponentFilePaths[aComponent.iFullPath] = [] |
|
952 |
|
953 def EndComponent(self, aComponent): |
|
954 if self.iScriptComponentFilePaths.has_key(aComponent.iFullPath): |
|
955 for outputPath in self.iScriptComponentFilePaths[aComponent.iFullPath]: |
|
956 self.iScriptComponentFile.EndOutputFile(outputPath) |
|
957 del self.iScriptComponentFilePaths[aComponent.iFullPath] |
|
958 relativeComponentName = scanner.iComponentManager.RelativeComponentName(aComponent.iFullPath) |
|
959 if len(relativeComponentName) < 1: # root component - final component |
|
960 scanner.iEndTime = datetime.datetime.now().ctime() |
|
961 scanner.iComponentManager.iCompletedComponents.sort(ComponentCompare) |
|
962 scriptSummaryFiles = CHtmlScriptSummaryFiles() |
|
963 scriptSummaryFiles.CreateSummaries(self, self.iOutputDirectory) |
|
964 componentSummaryFiles = CHtmlComponentSummaryFiles() |
|
965 componentSummaryFiles.CreateSummaries(self, self.iOutputDirectory) |
|
966 |
|
967 def ScriptComponentPath(self, aComponentName, aScriptName, aRel=None): |
|
968 componentName = scanner.iComponentManager.ComponentName(aComponentName) |
|
969 if aRel==None: |
|
970 aRel = self.iOutputDirectory |
|
971 outputPath = os.path.normpath(os.path.join(aRel, "byComponent")) |
|
972 outputPath = os.path.normpath(os.path.join(outputPath, componentName)) |
|
973 outputPath = os.path.normpath(os.path.join(outputPath, aScriptName+".html")) |
|
974 return outputPath |
|
975 |
|
976 |
|
977 class CRendererManager: |
|
978 |
|
979 # ####################################################### |
|
980 # CRendererManager |
|
981 # this class handles all the renderers |
|
982 |
|
983 def __init__(self): |
|
984 # declare associative list of renderers: iRendererList[name]=renderer |
|
985 self.iRendererList = {} |
|
986 self.iAnnotation = "" |
|
987 |
|
988 def AddRenderer(self, aRenderer): |
|
989 self.iRendererList[aRenderer.iName.lower()] = aRenderer |
|
990 |
|
991 def PrintListOfRenderers(self): |
|
992 print("Renderers:") |
|
993 for name, renderer in self.iRendererList.items(): |
|
994 print("\t" + name + "\t" + renderer.iDescription) |
|
995 |
|
996 print("") |
|
997 |
|
998 def BeginFile(self, aFilename): |
|
999 for name, renderer in self.iRendererList.items(): |
|
1000 renderer.BeginFile(aFilename) |
|
1001 |
|
1002 def ReportError(self, aLineContext, aScript): |
|
1003 for name, renderer in self.iRendererList.items(): |
|
1004 renderer.ReportError(aLineContext, aScript) |
|
1005 |
|
1006 def ReportAnnotation(self, aAnnotation): |
|
1007 self.iAnnotation = aAnnotation |
|
1008 |
|
1009 def EndFile(self): |
|
1010 for name, renderer in self.iRendererList.items(): |
|
1011 renderer.EndFile() |
|
1012 |
|
1013 def BeginComponent(self, aComponent): |
|
1014 for name, renderer in self.iRendererList.items(): |
|
1015 renderer.BeginComponent(aComponent) |
|
1016 |
|
1017 def EndComponent(self, aComponent): |
|
1018 for name, renderer in self.iRendererList.items(): |
|
1019 renderer.EndComponent(aComponent) |
|
1020 |
|
1021 |
|
1022 class CComponent: |
|
1023 |
|
1024 # ####################################################### |
|
1025 # CComponent - a single component, identified by the |
|
1026 # directory path to its source code |
|
1027 |
|
1028 def __init__(self, aPath): |
|
1029 self.iFullPath = aPath |
|
1030 self.iScriptErrorCounts = {} |
|
1031 self.iHasGroupDir = False |
|
1032 self.iNumberOfLinesScanned = 0 |
|
1033 |
|
1034 def appendComponent(self, aComponent): |
|
1035 for scriptName in aComponent.iScriptErrorCounts.keys(): |
|
1036 if self.iScriptErrorCounts.has_key(scriptName): |
|
1037 self.iScriptErrorCounts[scriptName] += aComponent.iScriptErrorCounts[scriptName] |
|
1038 else: |
|
1039 self.iScriptErrorCounts[scriptName] = aComponent.iScriptErrorCounts[scriptName] |
|
1040 self.iNumberOfLinesScanned += aComponent.iNumberOfLinesScanned |
|
1041 return |
|
1042 |
|
1043 |
|
1044 class CComponentManager: |
|
1045 |
|
1046 # ####################################################### |
|
1047 # CComponentManager - controls access to components |
|
1048 |
|
1049 def __init__(self): |
|
1050 self.iComponentStack = [] |
|
1051 self.iCompletedComponents = [] |
|
1052 self.iRootComponent = CComponent("") |
|
1053 self.iUseFullComponentPath = False |
|
1054 |
|
1055 def SetRoot(self, aRootPath): |
|
1056 # set the list of root directories - used to left-trim component names |
|
1057 self.iRootPath = self.SanitizePath(aRootPath) |
|
1058 |
|
1059 def BeginDirectory(self, aPath): |
|
1060 aPath = self.SanitizePath(aPath) |
|
1061 if os.path.isdir(aPath): |
|
1062 newComponent = CComponent(aPath) |
|
1063 contents = os.listdir(aPath) |
|
1064 for entry in contents: |
|
1065 if (entry.upper() == "GROUP"): |
|
1066 entryPath = os.path.normpath(os.path.join(aPath, entry)) |
|
1067 if os.path.isdir(entryPath): |
|
1068 newComponent.iHasGroupDir = True |
|
1069 break |
|
1070 if len(self.iComponentStack) > 0: |
|
1071 topComponent = self.iComponentStack[len(self.iComponentStack)-1] |
|
1072 if (newComponent.iHasGroupDir or (not topComponent.iHasGroupDir)): |
|
1073 self.BeginComponent(newComponent) |
|
1074 else: |
|
1075 scanner.iLog.Write(aPath + " taken as part of " + topComponent.iFullPath) |
|
1076 else: |
|
1077 self.BeginComponent(newComponent) |
|
1078 else: |
|
1079 scanner.iLog.Write("ERROR: CComponentManager::BeginDirectory: bad path "+aPath) |
|
1080 return aPath |
|
1081 |
|
1082 def EndDirectory(self, aPath, numberOfLinesScanned): |
|
1083 aPath = self.SanitizePath(aPath) |
|
1084 if len(self.iComponentStack) > 0: |
|
1085 topComponent = self.iComponentStack[len(self.iComponentStack)-1] |
|
1086 topComponent.iNumberOfLinesScanned += numberOfLinesScanned |
|
1087 if (topComponent.iFullPath == aPath): |
|
1088 self.EndComponent() |
|
1089 |
|
1090 def ReportError(self, aLineContext, aScript): |
|
1091 scanner.iRendererManager.ReportError(aLineContext, aScript) |
|
1092 for component in self.iComponentStack: |
|
1093 if component.iFullPath == aLineContext.iComponentName: |
|
1094 if component.iScriptErrorCounts.has_key(aScript.iScriptName): |
|
1095 component.iScriptErrorCounts[aScript.iScriptName] = component.iScriptErrorCounts[aScript.iScriptName] + 1 |
|
1096 else: |
|
1097 component.iScriptErrorCounts[aScript.iScriptName] = 1 |
|
1098 |
|
1099 def ScriptComponentErrorCount(self, aComponentName, aScriptName): |
|
1100 for component in self.iCompletedComponents: |
|
1101 if component.iFullPath == aComponentName: |
|
1102 if component.iScriptErrorCounts.has_key(aScriptName): |
|
1103 return component.iScriptErrorCounts[aScriptName] |
|
1104 else: |
|
1105 return 0 |
|
1106 return 0 |
|
1107 |
|
1108 def BeginComponent(self, aComponent): |
|
1109 scanner.iRendererManager.BeginComponent(aComponent) |
|
1110 scanner.ReportAction("Begin component: " + aComponent.iFullPath) |
|
1111 self.iComponentStack.append(aComponent) |
|
1112 |
|
1113 def EndComponent(self): |
|
1114 previousComponent = self.iComponentStack.pop() |
|
1115 matchingComponent = self.MatchingComponent(previousComponent) |
|
1116 if (matchingComponent <> None): |
|
1117 matchingComponent.appendComponent(previousComponent) |
|
1118 else: |
|
1119 self.iCompletedComponents.append(previousComponent) |
|
1120 scanner.ReportAction("End component: " + previousComponent.iFullPath) |
|
1121 scanner.iRendererManager.EndComponent(previousComponent) |
|
1122 |
|
1123 def MatchingComponent(self, aComponent): |
|
1124 for component in self.iCompletedComponents: |
|
1125 if (ComponentCompare(component, aComponent) == 0): |
|
1126 return component |
|
1127 return None |
|
1128 |
|
1129 def CurrentComponentName(self): |
|
1130 if len(self.iComponentStack) < 1: |
|
1131 return None |
|
1132 return self.iComponentStack[len(self.iComponentStack)-1].iFullPath |
|
1133 |
|
1134 def SanitizePath(self, aPath): |
|
1135 # Translate an unspecified or relative pathname into an absolute pathname |
|
1136 if len(aPath) < 1: |
|
1137 aPath = "." |
|
1138 aPath = os.path.normpath(aPath) |
|
1139 # translate "." and ".." into absolute paths |
|
1140 aPath = os.path.abspath(aPath) |
|
1141 return aPath |
|
1142 |
|
1143 def ComponentName(self, aFullComponentName): |
|
1144 if (self.iUseFullComponentPath): |
|
1145 (unused, componentName) = os.path.splitdrive(aFullComponentName) |
|
1146 if len(componentName) > 0: |
|
1147 if componentName[0] == os.path.sep and len(componentName) > 1: |
|
1148 componentName = componentName[1:] |
|
1149 return componentName |
|
1150 else: |
|
1151 return self.RelativeComponentName(aFullComponentName) |
|
1152 |
|
1153 def RelativeComponentName(self, aFullComponentName): |
|
1154 # Remove the root path from the specified component name |
|
1155 rootLen = len(self.iRootPath) |
|
1156 if aFullComponentName[0:rootLen] == self.iRootPath: |
|
1157 relativeComponentName = aFullComponentName[rootLen:] |
|
1158 else: |
|
1159 # this case is unexpected...but we'll try to make the best of it |
|
1160 (unused, relativeComponentName) = os.path.splitdrive(aFullComponentName) |
|
1161 # trim leading path separator, if present |
|
1162 if len(relativeComponentName) > 0: |
|
1163 if relativeComponentName[0] == os.path.sep and len(relativeComponentName) > 1: |
|
1164 relativeComponentName = relativeComponentName[1:] |
|
1165 return relativeComponentName |
|
1166 |
|
1167 |
|
1168 class CLineContext: |
|
1169 |
|
1170 # ####################################################### |
|
1171 # CLineContext |
|
1172 # A description of the line of source code currently being scanned |
|
1173 |
|
1174 iComponentName = "" |
|
1175 iFileName = "" |
|
1176 iLineNumber = 0 |
|
1177 iClassName = "" |
|
1178 iMethodName = "" |
|
1179 iLineText = "" |
|
1180 |
|
1181 |
|
1182 class CCodeScanner: |
|
1183 |
|
1184 # ####################################################### |
|
1185 # CCodeScanner - main application class |
|
1186 |
|
1187 def __init__(self): |
|
1188 self.iCategoriedScripts = CCategorisedScripts() |
|
1189 self.iRendererManager = CRendererManager() |
|
1190 self.iComponentManager = CComponentManager() |
|
1191 self.iLineContext = CLineContext() |
|
1192 self.iDomConfig = None |
|
1193 self.iVerbose = False |
|
1194 self.iLog = None |
|
1195 self.iSource = None |
|
1196 self.iEncodedFileList = None |
|
1197 self.iOutputDirectory = None |
|
1198 self.iStartTimeObj = None |
|
1199 self.iStartTime = None |
|
1200 self.iEndTime = None |
|
1201 self.iLxrUrl = None |
|
1202 self.iLxrVersion = None |
|
1203 self.iConfigFilename = "" |
|
1204 self.iInputFilenames = "" |
|
1205 self.iLogFilename = "" |
|
1206 self.iOutputFormat = "" |
|
1207 self.iTimeStampedOutput = "" |
|
1208 self.iReMethod = re.compile(r""" |
|
1209 ((?P<class>\w+)::~?)? |
|
1210 (?P<method>[A-Za-z0-9<>=!*\-+/]+) |
|
1211 [\s\n]* |
|
1212 \( |
|
1213 [^;]* |
|
1214 $ |
|
1215 """, re.VERBOSE) |
|
1216 |
|
1217 def ReportError(self, aErrorMsg): |
|
1218 self.iLog.Write(aErrorMsg) |
|
1219 print aErrorMsg |
|
1220 |
|
1221 def ReportAction(self, aAction): |
|
1222 self.iLog.Write(aAction) |
|
1223 if self.iVerbose: |
|
1224 print aAction |
|
1225 |
|
1226 def ReportInfo(self, aInfoMsg): |
|
1227 self.iLog.Write(aInfoMsg) |
|
1228 print aInfoMsg |
|
1229 |
|
1230 def CleanOutputDirectory(self): |
|
1231 self.iLog.Write("Deleting existing contents of output directory " + self.iOutputDirectory) |
|
1232 for root, dirs, files in os.walk(self.iOutputDirectory, topdown = False): |
|
1233 for name in files: |
|
1234 os.remove(os.path.join(root, name)) |
|
1235 for name in dirs: |
|
1236 os.rmdir(os.path.join(root, name)) |
|
1237 |
|
1238 def CheckSourceIncluded(self, aSourceFileName): |
|
1239 if (self.iDomConfig <> None): |
|
1240 for sourceNode in self.iDomConfig.getElementsByTagName("sources"): |
|
1241 for excludeSourceNode in sourceNode.getElementsByTagName("exclude"): |
|
1242 reExcludeSourceStr = excludeSourceNode.firstChild.nodeValue |
|
1243 reExcludeSource = re.compile(reExcludeSourceStr, re.IGNORECASE) |
|
1244 if reExcludeSource.search(aSourceFileName): |
|
1245 self.ReportInfo("Note: excluding " + aSourceFileName + " : " + reExcludeSourceStr) |
|
1246 return False |
|
1247 return True |
|
1248 |
|
1249 def CheckScriptEnabled(self, aScript): |
|
1250 if (self.iDomConfig <> None): |
|
1251 for scriptsNode in self.iDomConfig.getElementsByTagName("scripts"): |
|
1252 for scriptNode in scriptsNode.getElementsByTagName(aScript.iScriptName): |
|
1253 enabledAttr = scriptNode.getAttribute("enable") |
|
1254 if (enabledAttr.lower() == "false"): |
|
1255 return False |
|
1256 for severitiesNode in self.iDomConfig.getElementsByTagName("severities"): |
|
1257 for severityNode in severitiesNode.getElementsByTagName(KSeverityConfigMap[aScript.iSeverity]): |
|
1258 enabledAttr = severityNode.getAttribute("enable") |
|
1259 if (enabledAttr.lower() == "false"): |
|
1260 return False |
|
1261 for categoriesNode in self.iDomConfig.getElementsByTagName("categories"): |
|
1262 for categoryNode in categoriesNode.getElementsByTagName(KCategoryConfigMap[aScript.iCategory]): |
|
1263 enabledAttr = categoryNode.getAttribute("enable") |
|
1264 if (enabledAttr.lower() == "false"): |
|
1265 return False |
|
1266 return True |
|
1267 |
|
1268 def UpdateScriptCategory(self, aScript): |
|
1269 if (self.iDomConfig <> None): |
|
1270 for scriptsNode in self.iDomConfig.getElementsByTagName("scripts"): |
|
1271 for scriptNode in scriptsNode.getElementsByTagName(aScript.iScriptName): |
|
1272 if scriptNode.hasAttribute("category"): |
|
1273 newCategory = scriptNode.getAttribute("category").lower() |
|
1274 if (newCategory <> KCategoryConfigMap[aScript.iCategory]): |
|
1275 for name, value in KCategoryConfigMap.items(): |
|
1276 if (newCategory == value): |
|
1277 return name |
|
1278 # no update needed, return original category |
|
1279 return aScript.iCategory |
|
1280 |
|
1281 def UpdateScriptSeverity(self, aScript): |
|
1282 if (self.iDomConfig <> None): |
|
1283 for scriptsNode in self.iDomConfig.getElementsByTagName("scripts"): |
|
1284 for scriptNode in scriptsNode.getElementsByTagName(aScript.iScriptName): |
|
1285 if scriptNode.hasAttribute("severity"): |
|
1286 newSeverity = scriptNode.getAttribute("severity").lower() |
|
1287 if (newSeverity <> KSeverityConfigMap[aScript.iSeverity]): |
|
1288 for name, value in KSeverityConfigMap.items(): |
|
1289 if (newSeverity == value): |
|
1290 return name |
|
1291 # no update needed, return original severity |
|
1292 return aScript.iSeverity |
|
1293 |
|
1294 def ScanFile(self, aSourceFile): |
|
1295 self.iLineContext.iFileName = aSourceFile |
|
1296 self.iLineContext.iLineNumber = 0 |
|
1297 self.iLineContext.iClassName = "" |
|
1298 self.iLineContext.iMethodName = "" |
|
1299 self.iLineContext.iComponentName = self.iComponentManager.CurrentComponentName() |
|
1300 |
|
1301 self.iRendererManager.BeginFile(aSourceFile) |
|
1302 |
|
1303 # note source file extension - used for filtering later on |
|
1304 (unused, sourceFileExt) = os.path.splitext(aSourceFile) |
|
1305 if len(sourceFileExt) > 0 and sourceFileExt[0] == '.': |
|
1306 sourceFileExt = sourceFileExt[1:] |
|
1307 |
|
1308 # open, read, and preparse source file |
|
1309 inputFileHandle = file(aSourceFile, "r") |
|
1310 inputFileLines = inputFileHandle.readlines() |
|
1311 inputFileHandle.close() |
|
1312 |
|
1313 (noQuoteFileLines, noCommentFileLines, noCommentOrQuoteFileLines, csCommands) = self.PreParseSourceFile(inputFileLines) |
|
1314 |
|
1315 # bundle all the filtered versions of the file contents into |
|
1316 # a hash to re-factor code |
|
1317 fileContentsToTest = { KIgnoreNothing : inputFileLines, |
|
1318 KIgnoreComments : noCommentFileLines, |
|
1319 KIgnoreQuotes : noQuoteFileLines, |
|
1320 KIgnoreCommentsAndQuotes : noCommentOrQuoteFileLines |
|
1321 } |
|
1322 |
|
1323 # now apply test scripts to source file |
|
1324 iBraceCount = 0 |
|
1325 iBraceCountList = [] |
|
1326 newCurrentClassName = "" |
|
1327 newCurrentMethodName = "" |
|
1328 self.iCurrentClassName = "" |
|
1329 self.iCurrentMethodName = "" |
|
1330 self.iCurrentMethodStart = -1 |
|
1331 |
|
1332 totalNumberOfLines = len(inputFileLines) |
|
1333 reConstant = re.compile(r""" |
|
1334 ^\s* |
|
1335 (static\s+)? |
|
1336 const |
|
1337 \s+ |
|
1338 \w+ # type |
|
1339 \s* |
|
1340 [\*&]? # reference or pointer |
|
1341 \s* |
|
1342 \w+ # name |
|
1343 \s* |
|
1344 (=|\() |
|
1345 """, re.VERBOSE) |
|
1346 reInheritance = re.compile("[\s:]*(public|protected|private)\s*([\w:]+)") |
|
1347 rePreprocessorIf = re.compile("^\s*\#(el)*if(.*)") |
|
1348 rePreprocessorElse = re.compile("^\s*\#else") |
|
1349 rePreprocessorEnd = re.compile("^\s*\#endif") |
|
1350 reTypedef = re.compile("^\s*typedef") |
|
1351 i = 0 |
|
1352 while (i < totalNumberOfLines): |
|
1353 # for extra open braces in #if blocks |
|
1354 if (rePreprocessorIf.search(noCommentOrQuoteFileLines[i])): |
|
1355 iBraceCountList.append(iBraceCount) |
|
1356 if (rePreprocessorElse.search(noCommentOrQuoteFileLines[i])): |
|
1357 if (len(iBraceCountList) > 0): |
|
1358 iBraceCount = iBraceCountList.pop() |
|
1359 if (rePreprocessorEnd.search(noCommentOrQuoteFileLines[i])): |
|
1360 if (len(iBraceCountList) > 0): |
|
1361 iBraceCountList.pop() |
|
1362 |
|
1363 if (newCurrentMethodName == ""): |
|
1364 methodString = noCommentOrQuoteFileLines[i] |
|
1365 currentLine = i |
|
1366 m = self.iReMethod.search(methodString) |
|
1367 if not m and (i + 1 < totalNumberOfLines): |
|
1368 currentLine = i + 1 |
|
1369 methodString += noCommentOrQuoteFileLines[currentLine] |
|
1370 m = self.iReMethod.search(methodString) |
|
1371 |
|
1372 if m and (iBraceCount == 0) and (methodString.find("#") == -1) and (methodString.find("_LIT") == -1): # must be at root level and not a preprocessor directive or a _LIT |
|
1373 if not reTypedef.match(methodString) and not reConstant.match(methodString): # must not be typedef or constant declaration |
|
1374 # check for cases where macros are used to declare a class |
|
1375 # by searching for the inheritance part |
|
1376 # eg. NONSHARABLE_CLASS(CMyClass) : public CBase |
|
1377 isClass = reInheritance.search(methodString) |
|
1378 if not isClass and (currentLine + 1 < totalNumberOfLines): |
|
1379 methodString += noCommentOrQuoteFileLines[currentLine + 1] |
|
1380 isClass = reInheritance.search(methodString) |
|
1381 if not isClass: |
|
1382 newCurrentMethodName = m.group('method') |
|
1383 if m.group('class'): |
|
1384 newCurrentClassName = m.group('class') |
|
1385 else: |
|
1386 newCurrentClassName = "" |
|
1387 |
|
1388 iBraceCount += noCommentOrQuoteFileLines[i].count("{") |
|
1389 if (iBraceCount > 0) and (newCurrentMethodName <> ""): |
|
1390 self.iCurrentClassName = newCurrentClassName |
|
1391 self.iCurrentMethodName = newCurrentMethodName |
|
1392 self.iCurrentMethodStart = i |
|
1393 newCurrentClassName = "" |
|
1394 newCurrentMethodName = "" |
|
1395 |
|
1396 self.iLineContext.iLineNumber = i+1 |
|
1397 self.iLineContext.iClassName = self.iCurrentClassName |
|
1398 self.iLineContext.iMethodName = self.iCurrentMethodName |
|
1399 |
|
1400 # perform all test scripts onto source file |
|
1401 for script in self.iCategoriedScripts.AllScripts(): |
|
1402 if (script.iFileExts.count(sourceFileExt) > 0 |
|
1403 and fileContentsToTest[script.iIgnore][i] != "\n" |
|
1404 and script.iCompare(fileContentsToTest[script.iIgnore], i, script.iReMatch, aSourceFile)): |
|
1405 # skip any script that has been disabled via CodeScanner command(s) in sources |
|
1406 if script.iScriptName.lower() in csCommands[i].lower(): |
|
1407 continue |
|
1408 self.iLineContext.iLineText = fileContentsToTest[script.iIgnore][i] |
|
1409 self.iComponentManager.ReportError(self.iLineContext, script) |
|
1410 |
|
1411 iBraceCount -= noCommentOrQuoteFileLines[i].count("}") |
|
1412 if (iBraceCount < 0): # for extra close braces in #if blocks |
|
1413 iBraceCount = 0 |
|
1414 |
|
1415 if (iBraceCount == 0): |
|
1416 self.iCurrentClassName = "" |
|
1417 self.iCurrentMethodName = "" |
|
1418 self.iCurrentMethodStart = -1 |
|
1419 |
|
1420 i = i + 1 |
|
1421 |
|
1422 self.iRendererManager.EndFile() |
|
1423 return totalNumberOfLines |
|
1424 |
|
1425 def TraverseDirectory(self, aDirectory): |
|
1426 # skip folders marked to be excluded in configuration file |
|
1427 aPath = self.iComponentManager.SanitizePath(aDirectory) |
|
1428 if (not self.CheckSourceIncluded(aPath)) or (not self.CheckSourceIncluded(aPath + os.path.sep)): |
|
1429 return |
|
1430 aDirectory = self.iComponentManager.BeginDirectory(aDirectory) |
|
1431 contents = os.listdir(aDirectory) |
|
1432 numberOfLinesScanned = 0 |
|
1433 for entry in contents: |
|
1434 entryPath = os.path.normpath(os.path.join(aDirectory, entry)) |
|
1435 if os.path.isdir(entryPath): |
|
1436 self.TraverseDirectory(entryPath) |
|
1437 else: |
|
1438 if self.CheckSourceIncluded(entryPath): |
|
1439 numberOfLinesScanned += self.ScanFile(entryPath) |
|
1440 self.iComponentManager.EndDirectory(aDirectory, numberOfLinesScanned) |
|
1441 |
|
1442 def AddScript(self, aScript): |
|
1443 enabled = self.CheckScriptEnabled(script) |
|
1444 if enabled: |
|
1445 aScript.iCategory = self.UpdateScriptCategory(aScript) |
|
1446 aScript.iSeverity = self.UpdateScriptSeverity(aScript) |
|
1447 self.iCategoriedScripts.AddScript(aScript) |
|
1448 else: |
|
1449 self.ReportInfo("Note: script '" + aScript.iScriptName + "' DISABLED") |
|
1450 |
|
1451 def AddCustomScript(self, aScript): |
|
1452 self.ReportInfo("Note: custom rule '" + aScript.iScriptName + "' ADDED") |
|
1453 self.iCategoriedScripts.AddScript(aScript) |
|
1454 |
|
1455 def PreParseSourceFile(self, aLines): |
|
1456 # it provides 3 versions of input: |
|
1457 # 1. without quotes |
|
1458 # 2. without comments |
|
1459 # 3. without quotes and without comments |
|
1460 |
|
1461 inCommentBlock = 0 |
|
1462 noQuoteLines = [] |
|
1463 noCommentLines = [] |
|
1464 noCommentOrQuoteLines = [] |
|
1465 csCommands = [] |
|
1466 reCSCommand = re.compile("codescanner((::\w+)+)") # CodeScanner command(s) in comments |
|
1467 |
|
1468 for line in aLines: |
|
1469 noQuoteLine = "" |
|
1470 noCommentLine = "" |
|
1471 noCommentOrQuoteLine = "" |
|
1472 csCommand = "\n" |
|
1473 |
|
1474 i = 0 |
|
1475 startQuote = 0 |
|
1476 b = 0 |
|
1477 escCount = 0 |
|
1478 |
|
1479 while i < len(line): |
|
1480 # skip quotes |
|
1481 if not inCommentBlock and ((line[i] == "\"") or (line[i] == "\'")): |
|
1482 startQuote = i |
|
1483 i += 1 |
|
1484 while (i < len(line)): |
|
1485 endIndex = line[i:].find(line[startQuote]) |
|
1486 if (endIndex <> -1): |
|
1487 b = i + endIndex - 1 |
|
1488 escCount = 0 |
|
1489 while (line[b] == "\\"): |
|
1490 escCount += 1 |
|
1491 b -= 1 |
|
1492 |
|
1493 i += endIndex + 1 |
|
1494 if (escCount % 2 == 0): |
|
1495 noQuoteLine += "\"\"" |
|
1496 noCommentOrQuoteLine += "\"\"" |
|
1497 noCommentLine += line[startQuote:i] |
|
1498 break |
|
1499 else: |
|
1500 # print "Unterminated quote : " + line |
|
1501 break |
|
1502 continue |
|
1503 |
|
1504 # parse comments |
|
1505 if not inCommentBlock: |
|
1506 if (line[i] == "/"): |
|
1507 if (i < (len(line)-1)): |
|
1508 if (line[i + 1] == "/"): |
|
1509 noCommentLine += "\n" |
|
1510 noCommentOrQuoteLine += "\n" |
|
1511 noQuoteLine += line[i:] |
|
1512 # look for CodeScanner command(s) in comments |
|
1513 m = reCSCommand.search(line[i:]) |
|
1514 if m: |
|
1515 csCommand = m.group(1) |
|
1516 break |
|
1517 elif (line[i + 1] == "*"): |
|
1518 inCommentBlock = 1 |
|
1519 i += 2 |
|
1520 noQuoteLine += "/*" |
|
1521 continue |
|
1522 |
|
1523 noCommentLine += line[i] |
|
1524 noCommentOrQuoteLine += line[i] |
|
1525 noQuoteLine += line[i] |
|
1526 else: |
|
1527 # look for CodeScanner command(s) in comments |
|
1528 m = reCSCommand.search(line[i:]) |
|
1529 if m: |
|
1530 csCommand = m.group(1) |
|
1531 endIndex = line[i:].find("*/") |
|
1532 if (endIndex <> -1): |
|
1533 inCommentBlock = 0 |
|
1534 noQuoteLine += line[i:i + endIndex + 2] |
|
1535 i += endIndex + 2 |
|
1536 continue |
|
1537 else: |
|
1538 noCommentLine += "\n" |
|
1539 noCommentOrQuoteLine += "\n" |
|
1540 noQuoteLine = line[i:] |
|
1541 break |
|
1542 |
|
1543 i += 1 |
|
1544 |
|
1545 noCommentLines.append(noCommentLine) |
|
1546 noCommentOrQuoteLines.append(noCommentOrQuoteLine) |
|
1547 noQuoteLines.append(noQuoteLine) |
|
1548 csCommands.append(csCommand) |
|
1549 |
|
1550 return [noQuoteLines, noCommentLines, noCommentOrQuoteLines, csCommands] |
|
1551 |
|
1552 def ReadConfigFile(self): |
|
1553 if self.iConfigFilename <> "": |
|
1554 if (os.path.isfile(self.iConfigFilename)): |
|
1555 self.iDomConfig = xml.dom.minidom.parse(self.iConfigFilename) |
|
1556 if self.iVerbose: |
|
1557 print "Note: using configuration file " + self.iConfigFilename |
|
1558 else: |
|
1559 self.ReportInfo("Unable to open specified configuration file: " + self.iConfigFilename) |
|
1560 self.iLog.Close() |
|
1561 sys.exit(2) |
|
1562 |
|
1563 def ReadArgumentsFromConfigFile(self): |
|
1564 if (self.iDomConfig <> None): |
|
1565 for argumentsNode in self.iDomConfig.getElementsByTagName("arguments"): |
|
1566 # read input file names |
|
1567 for inputFileNode in argumentsNode.getElementsByTagName("input"): |
|
1568 self.iInputFilenames += inputFileNode.firstChild.nodeValue + "::" |
|
1569 # read output format |
|
1570 for outputFormatNode in argumentsNode.getElementsByTagName("outputformat"): |
|
1571 self.iOutputFormat += outputFormatNode.firstChild.nodeValue |
|
1572 # read lxr URL |
|
1573 for lxrURLNode in argumentsNode.getElementsByTagName("lxr"): |
|
1574 self.iLxrUrl = lxrURLNode.firstChild.nodeValue |
|
1575 # read lxr version |
|
1576 for lxrVersionNode in argumentsNode.getElementsByTagName("lxrversion"): |
|
1577 self.iLxrVersion = lxrVersionNode.firstChild.nodeValue |
|
1578 # read time stamped output option |
|
1579 for timeStampedOutputNode in argumentsNode.getElementsByTagName("timestampedoutput"): |
|
1580 self.iTimeStampedOutput = timeStampedOutputNode.firstChild.nodeValue |
|
1581 |
|
1582 def ReadCustomRulesFromConfigFile(self): |
|
1583 if (self.iDomConfig <> None): |
|
1584 for customRulesNode in self.iDomConfig.getElementsByTagName("customrules"): |
|
1585 for customRuleNode in customRulesNode.getElementsByTagName("customrule"): |
|
1586 ignoreComments = True |
|
1587 |
|
1588 # read the name of the rule |
|
1589 ruleName = "" |
|
1590 for ruleNameNode in customRuleNode.getElementsByTagName("name"): |
|
1591 if (ruleNameNode == None) or (ruleNameNode.firstChild == None) or (ruleNameNode.firstChild.nodeValue == None): |
|
1592 continue |
|
1593 else: |
|
1594 ruleName = ruleNameNode.firstChild.nodeValue |
|
1595 if len(ruleName) == 0: |
|
1596 self.ReportError("Missing custom rule name in configuration file: " + self.iConfigFilename) |
|
1597 continue |
|
1598 |
|
1599 # read the keywords associated with the rule |
|
1600 keywordList = [] |
|
1601 badKeywordElement = False |
|
1602 for keywordNode in customRuleNode.getElementsByTagName("keyword"): |
|
1603 # read keyword content |
|
1604 if (keywordNode == None) or (keywordNode.firstChild == None) or (keywordNode.firstChild.nodeValue == None): |
|
1605 badKeywordElement = True |
|
1606 continue |
|
1607 newKeyword = CCustomRuleKeyword() |
|
1608 newKeyword.iContent = keywordNode.firstChild.nodeValue |
|
1609 |
|
1610 # read keyword type |
|
1611 if not keywordNode.hasAttribute("type"): |
|
1612 badKeywordElement = True |
|
1613 continue |
|
1614 type = keywordNode.getAttribute("type").lower() |
|
1615 if type in KCustomRuleKeywordMap.values(): |
|
1616 if type == KKeywordComment: |
|
1617 ignoreComments = False |
|
1618 else: |
|
1619 type = KCustomRuleKeywordMap[KKeywordUnknown] |
|
1620 newKeyword.iType = type |
|
1621 keywordList.append(newKeyword) |
|
1622 if (len(keywordList) == 0) or (badKeywordElement == True): |
|
1623 self.ReportBadCustomRuleElement(ruleName, "keyword") |
|
1624 continue |
|
1625 |
|
1626 # read the file types associated with the rule |
|
1627 fileTypeList = [] |
|
1628 badFileTypeElement = False |
|
1629 for fileTypeNode in customRuleNode.getElementsByTagName("filetype"): |
|
1630 if (fileTypeNode == None) or (fileTypeNode.firstChild == None) or (fileTypeNode.firstChild.nodeValue == None): |
|
1631 badFileTypeElement = True |
|
1632 continue |
|
1633 newFileType = fileTypeNode.firstChild.nodeValue |
|
1634 fileTypeList.append(newFileType.lower()) |
|
1635 if (len(fileTypeList) == 0) or (badFileTypeElement == True): |
|
1636 self.ReportBadCustomRuleElement(ruleName, "file type") |
|
1637 continue |
|
1638 |
|
1639 # read the severity level of the rule |
|
1640 severity = KSeverityLow |
|
1641 for severityNode in customRuleNode.getElementsByTagName("severity"): |
|
1642 if (severityNode == None) or (severityNode.firstChild == None) or (severityNode.firstChild.nodeValue == None): |
|
1643 self.ReportBadCustomRuleElement(ruleName, "severity") |
|
1644 continue |
|
1645 severityValue = severityNode.firstChild.nodeValue |
|
1646 for severityKey in KSeverityConfigMap.keys(): |
|
1647 if severityValue == KSeverityConfigMap[severityKey]: |
|
1648 severity = severityKey |
|
1649 |
|
1650 # read the tile of the rule |
|
1651 title = "" |
|
1652 for titleNode in customRuleNode.getElementsByTagName("title"): |
|
1653 if (titleNode == None) or (titleNode.firstChild == None) or (titleNode.firstChild.nodeValue == None): |
|
1654 continue |
|
1655 title = titleNode.firstChild.nodeValue |
|
1656 if len(title) == 0: |
|
1657 self.ReportBadCustomRuleElement(ruleName, "title") |
|
1658 continue |
|
1659 |
|
1660 # read the description of the rule |
|
1661 description = "" |
|
1662 for descriptionNode in customRuleNode.getElementsByTagName("description"): |
|
1663 if (descriptionNode == None) or (descriptionNode.firstChild == None) or (descriptionNode.firstChild.nodeValue == None): |
|
1664 continue |
|
1665 description = descriptionNode.firstChild.nodeValue |
|
1666 if len(description) == 0: |
|
1667 self.ReportBadCustomRuleElement(ruleName, "description") |
|
1668 continue |
|
1669 |
|
1670 # read the optional link of the rule |
|
1671 link = None |
|
1672 for linkNode in customRuleNode.getElementsByTagName("link"): |
|
1673 if (linkNode == None) or (linkNode.firstChild == None) or (linkNode.firstChild.nodeValue == None): |
|
1674 self.ReportBadCustomRuleElement(ruleName, "link") |
|
1675 continue |
|
1676 link = linkNode.firstChild.nodeValue |
|
1677 |
|
1678 # create the RE string for the custom rule |
|
1679 keywordMap = self.ConstructCustomRuleKeywordMap(keywordList) |
|
1680 reString = self.ConstructCustomRuleREString(keywordMap) |
|
1681 if len(reString) == 0: |
|
1682 continue |
|
1683 |
|
1684 # create a script based on the custom rule |
|
1685 aScript = CCustomScript(ruleName) |
|
1686 aScript.iReString = reString |
|
1687 aScript.iReMatch = re.compile(reString) |
|
1688 aScript.iFileExts = fileTypeList |
|
1689 aScript.iCategory = KCategoryOther |
|
1690 if keywordMap.has_key(KKeywordBaseClass): |
|
1691 aScript.iBaseClass = keywordMap[KKeywordBaseClass] |
|
1692 aScript.iCompare = aScript.DefaultInheritanceCompare |
|
1693 if ignoreComments: |
|
1694 aScript.iIgnore = KIgnoreComments |
|
1695 else: |
|
1696 aScript.iIgnore = KIgnoreQuotes |
|
1697 aScript.iSeverity = severity |
|
1698 aScript.iTitle = title |
|
1699 aScript.iIdeTitle = title |
|
1700 aScript.iDescription = description |
|
1701 if link <> None: |
|
1702 aScript.iLink = link |
|
1703 self.AddCustomScript(aScript) |
|
1704 return |
|
1705 |
|
1706 def ReportBadCustomRuleElement(self, name, element): |
|
1707 self.ReportError("<customrule> element '" + name + "' has bad <" + element + "> child element in configuration file: " + self.iConfigFilename) |
|
1708 |
|
1709 def ConstructCustomRuleKeywordMap(self, keywordList): |
|
1710 reString = "" |
|
1711 keywordMap = {} |
|
1712 for keyword in keywordList: |
|
1713 if keywordMap.has_key(keyword.iType): |
|
1714 keywordMap[keyword.iType] = keywordMap[keyword.iType] + "|" + keyword.iContent |
|
1715 else: |
|
1716 keywordMap[keyword.iType] = keyword.iContent |
|
1717 return keywordMap |
|
1718 |
|
1719 def ConstructCustomRuleREString(self, keywordMap): |
|
1720 # generate RE string based on the keyword types |
|
1721 if keywordMap.has_key(KKeywordBaseClass): |
|
1722 reString = "^\s*class\s+(\w+::)?(\w+)\s*:(.*)" |
|
1723 elif keywordMap.has_key(KKeywordCall): |
|
1724 reString = "(" + keywordMap[KKeywordCall] + ")\s*\(.*\)\s*;" |
|
1725 elif keywordMap.has_key(KKeywordClassName): |
|
1726 if keywordMap.has_key(KKeywordMethod): |
|
1727 reString = "([A-Za-z0-9]+\s+" + keywordMap[KKeywordClassName] + "::)?(" + keywordMap[KKeywordMethod] + ")\s*\(.*\)\s*[^;]" |
|
1728 else: |
|
1729 reString = "^\s*class\s+(\w+::)?(" + keywordMap[KKeywordClassName] + ")" |
|
1730 elif keywordMap.has_key(KKeywordComment): |
|
1731 reString = "/(/|\*).*(" + keywordMap[KKeywordComment] + ")" |
|
1732 elif keywordMap.has_key(KKeywordGeneric): |
|
1733 reString = "(" + keywordMap[KKeywordGeneric] + ")" |
|
1734 elif keywordMap.has_key(KKeywordLocal): |
|
1735 reString = "^\s*[A-Z]\w*\s*[\*&\s]\s*(" + keywordMap[KKeywordLocal] + ")\w*\s*[;\(=]" |
|
1736 elif keywordMap.has_key(KKeywordMacro): |
|
1737 reString = "^\s*\#define\s+(" + keywordMap[KKeywordMacro] + ")" |
|
1738 elif keywordMap.has_key(KKeywordMember): |
|
1739 reString = "^\s*[A-Z]\w*\s*[\*&\s]\s*(" + keywordMap[KKeywordMember] + ")\w*\s*[;\(=]" |
|
1740 elif keywordMap.has_key(KKeywordMethod): |
|
1741 reString = "[A-Za-z0-9]+\s+[C|T|R][A-Za-z0-9]+::(" + keywordMap[KKeywordMethod] + ")\s*\(.*\)\s*[^;]" |
|
1742 elif keywordMap.has_key(KKeywordParameter): |
|
1743 reString = "({)*\s*(" + keywordMap[KKeywordParameter] + ")\s*=\s*(.*);" |
|
1744 return reString |
|
1745 |
|
1746 |
|
1747 class CCustomRuleKeyword: |
|
1748 # ####################################################### |
|
1749 # CCustomRuleKeyword - keyword associated with custom rules |
|
1750 |
|
1751 def __init__(self): |
|
1752 iContent = "" |
|
1753 iType = "unknown" |
|
1754 |
|
1755 |
|
1756 # ####################################################### |
|
1757 |
|
1758 class CEncodedFile: |
|
1759 def Extract(self, aBaseDirectory): |
|
1760 outputFileHandle = open(os.path.join(aBaseDirectory, self.iFilename), 'wb') |
|
1761 outputFileBinary = zlib.decompress(base64.decodestring(self.iFileBody)) |
|
1762 outputFileHandle.write(outputFileBinary) |
|
1763 outputFileHandle.close() |
|
1764 |
|
1765 iFilename = "" |
|
1766 iFileBody = "" |
|
1767 |
|
1768 # ####################################################### |
|
1769 |
|
1770 |
|
1771 class CEncodedFileList: |
|
1772 def AddEncodedFile(self, aEncodedFile): |
|
1773 self.iEncodedFileList[aEncodedFile.iFilename.lower()] = aEncodedFile |
|
1774 |
|
1775 def ExtractEncodedFile(self, aFilename, aBaseDirectory): |
|
1776 # look for the filename in our list of files |
|
1777 filename = aFilename.lower() |
|
1778 if (self.iEncodedFileList.has_key(filename)): |
|
1779 self.iEncodedFileList[filename].Extract(aBaseDirectory) |
|
1780 else: |
|
1781 scanner.iLog.Write("Missing "+filename) |
|
1782 |
|
1783 def ExtractAllEncodedFiles(self, aBaseDirectory): |
|
1784 # run through associative array and extract everything |
|
1785 for filename in self.iEncodedFileList.keys(): |
|
1786 self.ExtractEncodedFile(filename, aBaseDirectory) |
|
1787 |
|
1788 # declare iEncodedFileList is an associative array |
|
1789 iEncodedFileList = {} |
|
1790 |
|
1791 |
|
1792 # ####################################################### |
|
1793 # main() |
|
1794 scanner = CCodeScanner() |
|
1795 |
|
1796 # process command line arguments |
|
1797 opts, args = getopt.getopt(sys.argv[1:], "hvc:i:l:o:x:r:t:", ["help", "verbose", "config=", "input=", "logfile=", "outputformat=", "lxr=", "lxrversion=", "timestampedoutput="]) |
|
1798 for o, a in opts: |
|
1799 if o in ("-h", "--help"): |
|
1800 Usage(0) |
|
1801 if o in ("-v", "--verbose"): |
|
1802 scanner.iVerbose = True |
|
1803 if o in ("-c", "--config"): |
|
1804 scanner.iConfigFilename = a |
|
1805 if o in ("-i", "--input"): |
|
1806 scanner.iInputFilenames += a + "::" |
|
1807 if o in ("-l", "--logfile"): |
|
1808 scanner.iLogFilename = a |
|
1809 if o in ("-o", "--outputformat"): |
|
1810 scanner.iOutputFormat += a |
|
1811 if o in ("-x", "--lxr"): |
|
1812 scanner.iLxrUrl = a |
|
1813 if o in ("-r", "--lxrversion"): |
|
1814 scanner.iLxrVersion = a |
|
1815 if o in ("-t", "--timestampedoutput"): |
|
1816 scanner.iTimeStampedOutput = a |
|
1817 |
|
1818 if len(args) < 1: |
|
1819 Usage(1) |
|
1820 |
|
1821 scanner.iLog = CLogger(scanner.iLogFilename) |
|
1822 scanner.iLog.Write("Command line: " + str(sys.argv[1:])) |
|
1823 scanner.iLog.Write("Current working directory: " + os.getcwd()) |
|
1824 |
|
1825 scanner.ReadConfigFile() |
|
1826 scanner.ReadArgumentsFromConfigFile() |
|
1827 scanner.ReadCustomRulesFromConfigFile() |
|
1828 |
|
1829 scanner.iSource = args[0] |
|
1830 scanner.iEncodedFileList = CEncodedFileList() |
|
1831 scanner.iStartTimeObj = datetime.datetime.now() |
|
1832 scanner.iStartTime = scanner.iStartTimeObj.ctime() |
|
1833 scanner.iOutputDirectory = scanner.iStartTimeObj.strftime("%a-%b-%d-%H-%M-%S-%Y") |
|
1834 |
|
1835 # invoke the pysco module to improve performance |
|
1836 psyco.full() |
|
1837 |
|
1838 # choose renderer based on command line arguments |
|
1839 if len(args) > 1: |
|
1840 if ("off" in scanner.iTimeStampedOutput.lower()): |
|
1841 scanner.iOutputDirectory = args[1] |
|
1842 else: |
|
1843 scanner.iOutputDirectory = os.path.normpath(os.path.join(args[1], scanner.iOutputDirectory)) |
|
1844 scanner.CleanOutputDirectory() |
|
1845 if scanner.iOutputFormat <> "": |
|
1846 #user specified output format |
|
1847 if ("xml" in scanner.iOutputFormat.lower()): |
|
1848 CXmlRenderer(scanner.iRendererManager, scanner.iOutputDirectory) |
|
1849 if ("html" in scanner.iOutputFormat.lower()): |
|
1850 CHtmlRenderer(scanner.iRendererManager, scanner.iOutputDirectory, scanner.iLxrUrl, scanner.iLxrVersion) |
|
1851 if ("std" in scanner.iOutputFormat.lower()): |
|
1852 CStdOutRenderer(scanner.iRendererManager) |
|
1853 else: |
|
1854 #default output format |
|
1855 CHtmlRenderer(scanner.iRendererManager, scanner.iOutputDirectory, scanner.iLxrUrl, scanner.iLxrVersion) |
|
1856 else: |
|
1857 CStdOutRenderer(scanner.iRendererManager) |
|
1858 |
|
1859 #!PARSE |
|
1860 |
|
1861 if (scanner.iVerbose): |
|
1862 scanner.iCategoriedScripts.PrintListOfTestScripts() |
|
1863 scanner.iRendererManager.PrintListOfRenderers() |
|
1864 |
|
1865 print |
|
1866 print "Scanning inititated : " + scanner.iStartTime |
|
1867 |
|
1868 if scanner.iInputFilenames <> "": |
|
1869 scanner.iComponentManager.iUseFullComponentPath = True |
|
1870 #additional input files |
|
1871 inputFiles = scanner.iInputFilenames.split("::") |
|
1872 for inputFile in inputFiles: |
|
1873 if inputFile <> "": |
|
1874 ScanDirOrFile(inputFile) |
|
1875 |
|
1876 argument = args[0] |
|
1877 ScanDirOrFile(argument) |
|
1878 |
|
1879 print "Scanning finished : " + scanner.iEndTime |
|
1880 scanner.iLog.Close() |
|
1881 |
|
1882 if (scanner.iDomConfig <> None): |
|
1883 scanner.iDomConfig.unlink() |
|
1884 |
|
1885 sys.exit(0) |