1(* 2 * Copyright 2014, NICTA 3 * 4 * This software may be distributed and modified according to the terms of 5 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 6 * See "LICENSE_BSD2.txt" for details. 7 * 8 * @TAG(NICTA_BSD) 9 *) 10 11structure Main = struct 12 13open OS.Process 14 15 16(* takes a file name on the command-line, and attempts to parse it *) 17fun die s = (print s; print "\n"; exit failure) 18fun warn s = (TextIO.output(TextIO.stdErr, s^"\n"); 19 TextIO.flushOut TextIO.stdErr) 20val execname = CommandLine.name 21 22 23val _ = Feedback.errorThreshold := NONE; 24val _ = Feedback.informf := (fn s => (TextIO.output(TextIO.stdOut, s); 25 TextIO.flushOut TextIO.stdOut)) 26 27fun quote s = "\"" ^ s ^ "\"" 28 29 30val commas = String.concat o separate ", " 31fun writeln s = Feedback.informStr(0,s) 32 33fun print_addressed_vars cse = let 34 open ProgramAnalysis 35 val globs = get_globals cse 36 val pfx1 = "There are "^Int.toString (length globs)^ " globals: " 37 val _ = writeln (String.concat 38 (separate "\n " (pfx1 :: map srcname globs))) 39 val addressed = get_addressed cse 40 val addr_vars = map MString.dest (MSymTab.keys addressed) 41 val pfx2 = "There are "^Int.toString (length addr_vars)^ 42 " addressed variables: " 43 val _ = writeln (String.concatWith "\n " (pfx2 :: addr_vars)) 44in 45 () 46end 47 48fun print_embedded_fncalls cse = let 49 open ProgramAnalysis 50 val calls = get_embedded_fncalls cse 51 fun filter call = 52 case call of 53 DirectCall s => let 54 in 55 case get_modifies cse s of 56 NONE => SOME s 57 | SOME varset => if not (Binaryset.isEmpty varset) then SOME s 58 else NONE 59 end 60 | _ => NONE 61 val call_list = List.mapPartial filter (Binaryset.listItems calls) 62 val pfx = "These " ^ Int.toString (length call_list) ^ 63 " functions MUST be shown to be side-effect free (and don't look as if they are): " 64in 65 writeln (String.concat (separate "\n " (pfx :: call_list))) 66end 67 68val printmv = ProgramAnalysis.mvar_toString 69 70fun print_modifies cse = let 71 open ProgramAnalysis 72 val functions = get_functions cse 73 fun print f = let 74 val fnm = if is_recursivefn cse f then f ^ " (recursive)" else f 75 in 76 writeln (" " ^ StringCvt.padRight #" " 50 fnm ^ ": " ^ 77 (case get_modifies cse f of 78 NONE => "<is or calls unannotated proto>" 79 | SOME varset => 80 String.concat 81 (separate " " (map printmv (Binaryset.listItems varset))))) 82 end 83in 84 writeln "Modifies info:"; 85 app print functions 86end 87 88fun print_reads cse = let 89 open ProgramAnalysis 90 val functions = get_functions cse 91 val reads = get_read_globals cse 92 fun print f = let 93 val vars = Binaryset.foldr (fn (mv,acc) => printmv mv :: acc) 94 [] 95 (valOf (Symtab.lookup reads f)) 96 handle Option => ["<is or calls unannotated proto>"] 97 in 98 writeln (" " ^ StringCvt.padRight #" " 50 f ^ ": "^ 99 String.concat (separate " " vars)) 100 end 101in 102 writeln "Function reads:"; 103 app print functions 104end 105 106fun calc_protoes cse = let 107 open ProgramAnalysis 108 val functions = get_functions cse 109 val defined_functions = get_defined_functions cse 110 fun foldthis (f, acc) = 111 if isSome (Symtab.lookup defined_functions f) then acc 112 else Binaryset.add(acc, f) 113in 114 List.foldl foldthis (Binaryset.empty String.compare) functions 115end 116 117fun print_protoes cse = let 118in 119 writeln "Protoes (only):"; 120 Binaryset.app (fn v => writeln (" " ^ v)) (calc_protoes cse) 121end 122 123fun print_unmodified_globals cse = let 124in 125 writeln "Unmodifed, unaddressed globals:"; 126 writeln (" " ^ 127 (cse |> ProgramAnalysis.calc_untouched_globals 128 |> MSymTab.keys 129 |> map MString.dest 130 |> commas)) 131end 132 133val filename = ref "" 134fun produce_dotfile cse = let 135 open ProgramAnalysis 136 val os = TextIO.openOut (!filename ^ ".dot") 137 fun print s = TextIO.output(os, s ^ "\n") 138 val fns = get_functions cse 139 val {callgraph,...} = compute_callgraphs cse 140 fun print_fn f = let 141 val callees = case Symtab.lookup callgraph f of 142 NONE => Binaryset.empty String.compare 143 | SOME s => s 144 in 145 Binaryset.app (fn c => print (f ^ " -> " ^ c)) callees 146 end 147in 148 print "digraph {"; 149 print "graph[fontsize=8]"; 150 print "node[shape=plaintext]"; 151 List.app print_fn fns; 152 print "}"; 153 TextIO.closeOut os 154end 155 156fun produce_toposort cse = let 157 open ProgramAnalysis 158 fun lift f fnm = case Symtab.lookup f fnm of 159 NONE => [] 160 | SOME s => Binaryset.listItems s 161 val {callgraph,callers} = compute_callgraphs cse 162 val toposort = Topo_Sort.topo_sort {cmp = String.compare, 163 graph = lift callgraph, 164 converse = lift callers} 165 (get_functions cse) 166 fun printcomp [] = raise Fail "Empty SCC??" 167 | printcomp [x] = writeln (" "^x) 168 | printcomp list = writeln (" " ^ commas list) 169in 170 writeln "Topological sort of functions in callgraph"; 171 List.app printcomp toposort 172end 173 174 175fun print_uncalledfns cse = let 176 open ProgramAnalysis 177 val {callgraph = graph,...} = compute_callgraphs cse 178 val fns = get_functions cse 179 fun foldthis (fname, acc) = 180 case Symtab.lookup graph fname of 181 NONE => fname :: acc 182 | SOME s => if Binaryset.isEmpty s then fname::acc 183 else acc 184 val uncalled = List.foldl foldthis [] fns 185in 186 writeln "Uncalled functions"; 187 List.app (fn s => writeln (" "^s)) (List.rev uncalled) 188end 189 190fun print_fnspecs cse = let 191 open ProgramAnalysis Absyn 192 val specdb = function_specs cse 193 val _ = writeln "Function specification information:" 194 fun doit (fname, specs) () = let 195 in 196 writeln fname ; 197 List.app (fn fs => writeln (" "^fnspec2string fs)) specs 198 end 199in 200 Symtab.fold doit specdb () 201end 202 203 204datatype pc_problem = Reads of ProgramAnalysis.modify_var 205 | Writes of ProgramAnalysis.modify_var 206 | IsProto 207 208fun mapOne f s = 209 case Binaryset.find (fn _ => true) s of 210 NONE => NONE 211 | SOME i => SOME (f i) 212 213fun print_bogus_pures cse = let 214 open ProgramAnalysis Absyn 215 val specdb = function_specs cse 216 open Feedback 217 fun foldthis (fname, fspecs) () = let 218 open Binaryset 219 val idset = all_IDattributes fspecs 220 val pure_problem = 221 case get_modifies cse fname of 222 NONE => SOME IsProto 223 | SOME s => mapOne Writes s 224 val pc_attr = member(idset, "pure") orelse member(idset, "const") 225 in 226 if member(idset, "noreturn") then () 227 else 228 case pure_problem of 229 NONE => if not pc_attr then 230 informStr(2, "NOTE: "^fname^ 231 " is pure, but not annotated pure or const") 232 else () 233 | SOME (Writes mv) => 234 if pc_attr then 235 informStr(1, "WARNING: "^fname^ " writes "^printmv mv^ 236 " but is annotated pure or const") 237 else () 238 | SOME IsProto => 239 if pc_attr then 240 informStr(1, "WARNING: "^fname^" is annotated pure or const, but \ 241 \is an unannotated prototype") 242 else () 243 | SOME _ => (* can't happen *) () 244 end 245in 246 Symtab.fold foldthis specdb () 247end 248 249fun print_bogus_consts cse = let 250 open ProgramAnalysis Absyn 251 val specdb = function_specs cse 252 val read_globals = get_read_globals cse 253 fun foldthis (fname, fspecs) () = let 254 val reads_prob = 255 case Symtab.lookup read_globals fname of 256 NONE => SOME IsProto 257 | SOME s => mapOne Reads s 258 val prob = 259 case reads_prob of 260 NONE => let 261 in 262 case get_modifies cse fname of 263 NONE => SOME IsProto 264 | SOME s => mapOne Writes s 265 end 266 | x => x 267 val idset = all_IDattributes fspecs 268 open Binaryset Feedback 269 fun prob2str IsProto = "is or calls a proto" 270 | prob2str (Reads mv) = "reads "^printmv mv 271 | prob2str (Writes mv) = "writes "^printmv mv 272 in 273 if member(idset, "noreturn") then () 274 else 275 case prob of 276 NONE => if not (member(idset, "const")) then 277 informStr(2, "NOTE: "^fname^ 278 " is const, but not annotated const") 279 else () 280 | SOME p => if member (idset, "const") then 281 informStr(1, "WARNING: "^fname^" is declared const but "^ 282 prob2str p) 283 else () 284 end 285in 286 Symtab.fold foldthis specdb () 287end 288 289 290fun print_unannotated_protoes cse = let 291 open ProgramAnalysis 292 val protoes = calc_protoes cse 293 fun foldthis (fnm, acc) = 294 case get_modifies cse fnm of 295 NONE => fnm::acc 296 | SOME _ => acc 297in 298 writeln "Unannotated protoes:"; 299 List.app (fn s => writeln (" "^s)) 300 (List.rev (Binaryset.foldl foldthis [] protoes)) 301end 302 303fun mmsizes cse = let 304 open Absyn ProgramAnalysis 305 val fns = get_fninfo cse 306 fun foldthis (name,(rettype,_,pvis)) _ = let 307 fun bytesize ty = case ty of Void => 0 | _ => sizeof cse ty 308 val retsize = bytesize rettype 309 val psizes = map (bytesize o get_vi_type) pvis 310 in 311 print (String.concatWith " " (Int.toString retsize :: name :: 312 map Int.toString psizes)); 313 print "\n" 314 end 315in 316 Symtab.fold foldthis fns () 317end 318 319 320fun equal x y = (x = y) 321 322fun cmdline_options hdlr args = let 323 fun recurse args = 324 case args of 325 [] => args 326 | h::t => if h = "--" then t 327 else if String.isPrefix "--" h then let 328 val h' = String.extract (h, 2, NONE) 329 val flds = String.fields (equal #"=") h' 330 in 331 if length flds = 1 then (hdlr (h', NONE); recurse t) 332 else let 333 val a = hd flds 334 val () = hdlr (a, SOME (String.extract(h',size a,NONE))) 335 in 336 recurse t 337 end 338 end 339 else if String.isPrefix "-" h andalso size h > 1 then let 340 in 341 if size h > 2 then 342 hdlr(String.substring(h,1,1), 343 SOME (String.extract(h,2,NONE))) 344 else 345 hdlr(String.substring(h,1,1), NONE); 346 recurse t 347 end 348 else h::t 349in 350 recurse args 351end 352 353fun decl_toString d = let 354 open Absyn 355in 356 case d of 357 VarDecl (_, sw, _, _, _) => "declaration of global variable "^node sw 358 | StructDecl (sw, _) => "declaration of struct "^node sw 359 | TypeDecl tynms => "typedef of "^ 360 String.concatWith ", " (map (node o #2) tynms) 361 | ExtFnDecl {name,...} => "declaration of function "^node name 362 | EnumDecl (sow,_) => "declaration of "^(case node sow of 363 NONE => "anonymous" 364 | SOME s => s)^ 365 " enum" 366end 367 368fun print_fnslocs cse ast = let 369 open Absyn 370 fun recurse [] = () 371 | recurse (Decl dw :: t) = 372 (warn ("Ignoring "^decl_toString (node dw)); recurse t) 373 | recurse (FnDefn ((retty,fnm),params,specs,body) :: t) = 374 (print (node fnm^" " ^ 375 SourcePos.toString (left fnm) ^ " " ^ 376 SourcePos.toString (right body)^"\n"); 377 recurse t) 378in 379 recurse ast 380end 381 382fun print_ast cse ast = let 383 open Absyn_Serial 384 385 fun serial_defn (FnDefn ((retty,fnm),params,specs,body)) 386 = Nm ("Function", [varspec_serial (retty,fnm), 387 list_serial varspec_serial params, 388 list_serial fnspec_serial specs, 389 list_serial bi_serial (node body)]) 390 | serial_defn (Decl dw) = decl_serial (node dw) 391 fun print_lines ss = app print (map (fn s => s ^ "\n") ss) 392in 393 app (print_lines o lines_serial o serial_defn) ast 394end 395 396fun adjusted_complex_fncalls cse ast = let 397 open Absyn_Serial 398 399 fun is_adjusted s = case snode s of 400 Assign(_,e) => (case enode e of EFnCall _ => true | _ => false) 401 | Return(SOME e) => (case enode e of EFnCall _ => true | _ => false) 402 | _ => false 403 fun note_adjusteds s = if is_adjusted s 404 then print ("adjusted fncall at: " ^ 405 SourcePos.toString (sleft s) ^ " " ^ 406 SourcePos.toString (sright s)^"\n") 407 else app note_adjusteds (sub_stmts s) 408 fun note_bi (BI_Stmt s) = note_adjusteds s 409 | note_bi _ = () 410 fun note_defn (FnDefn (_,_,_,body)) 411 = app note_bi (node body) 412 | note_defn _ = () 413in app note_defn ast end 414 415fun unhandled_asm cse ast = let 416 open Absyn 417 fun warn_asm asm = K () (ProgramAnalysis.split_asm_stmt asm) 418 handle Fail s => print s 419 fun warn_stmt s = case snode s of 420 (AsmStmt asm) => warn_asm (AsmStmt asm) 421 | _ => app warn_stmt (sub_stmts s) 422 fun warn_bi (BI_Stmt s) = warn_stmt s 423 | warn_bi _ = () 424 fun warn_defn (FnDefn (_,_,_,body)) 425 = app warn_bi (node body) 426 | warn_defn _ = () 427in app warn_defn ast end 428 429val analyses = ref ([] : (ProgramAnalysis.csenv -> Absyn.ext_decl list -> unit) list) 430val includes = ref ([] : string list) 431val error_lookahead = ref 15 432val verbosity = Feedback.verbosity_level 433 434fun add_analysis f = analyses := f :: !analyses 435fun add_cse_analysis f = analyses := (fn cse => fn ast => f cse) :: !analyses 436 437val cpp = ref (SOME "/usr/bin/cpp") 438val parse_only = ref false 439val underscore_idents = ref false 440val show_help = ref false 441val bad_option = ref false 442val munge_info_fname = ref (NONE : string option) 443 444val options = let 445 open GetOpt 446 fun intref r s = 447 case Int.fromString s of 448 NONE => (show_help := true; bad_option := true) 449 | SOME i => r := i 450 fun cse_analysis nm f = 451 {short = "", long = [nm], help = "Do "^nm^" analysis", 452 desc = NoArg (fn () => add_cse_analysis f)} 453 fun ast_analysis nm f = 454 {short = "", long = [nm], help = "Do "^nm^" analysis", 455 desc = NoArg (fn () => add_analysis f)} 456in 457 [{short = "h?", long = ["help"], help = "Show usage information", 458 desc = NoArg (fn () => show_help := true)}, 459 {short = "I", long = [], help = "Add include directory (repeatable)", 460 desc = ReqArg ((fn dir => includes := dir :: !includes), "directory")}, 461 {short = "v", long = [], help = "Set parser error verbosity", 462 desc = ReqArg (intref verbosity, "number")}, 463 {short = "l", long = [], help = "Set parser lookahead", 464 desc = ReqArg (intref error_lookahead, "number")}, 465 cse_analysis "addressed_vars" print_addressed_vars, 466 cse_analysis "bogus_const" print_bogus_consts, 467 cse_analysis "bogus_pure" print_bogus_pures, 468 {short = "", long = ["cpp"], help = "Set cpp path (see also --nocpp)", 469 desc = ReqArg ((fn p => cpp := SOME p), "path")}, 470 cse_analysis "embedded_fncalls" print_embedded_fncalls, 471 ast_analysis "fnslocs" print_fnslocs, 472 cse_analysis "fnspecs" print_fnspecs, 473 cse_analysis "mmbytes" mmsizes, 474 cse_analysis "modifies" print_modifies, 475 {short = "", long = ["munge_info_fname"], help = "Path for munge info", 476 desc = ReqArg ((fn p => munge_info_fname := SOME p), "path")}, 477 {short = "", long = ["nocpp"], help = "Don't use cpp", 478 desc = NoArg (fn () => cpp := NONE)}, 479 {short = "", long = ["nolinedirectives"], help = "Ignore #line directives", 480 desc = NoArg (fn () => SourceFile.observe_line_directives := false)}, 481 cse_analysis "protoes" print_protoes, 482 {short = "", long = ["rawsyntaxonly"], help = "Don't perform any analyses", 483 desc = NoArg (fn () => parse_only := true)}, 484 {short = "", long = ["underscore_idents"], help = "Allow identifiers starting with underscores", 485 desc = NoArg (fn () => underscore_idents := true)}, 486 cse_analysis "reads" print_reads, 487 cse_analysis "toposort" produce_toposort, 488 cse_analysis "produce dotfile" produce_dotfile, 489 cse_analysis "unannotated_protoes" print_unannotated_protoes, 490 cse_analysis "uncalledfns" print_uncalledfns, 491 cse_analysis "unmodifiedglobs" print_unmodified_globals, 492 ast_analysis "ast" print_ast, 493 ast_analysis "adjusted_complex_fncalls" adjusted_complex_fncalls, 494 ast_analysis "unhandled_asm" unhandled_asm 495 ] 496end 497 498 499 500 501fun docpp (SOME p) {includes, filename} = 502 let 503 val includes_string = String.concat (map (fn s => "-I\""^s^"\" ") includes) 504 open OS.FileSys OS.Process 505 val tmpname = tmpName() 506 val cmdline = 507 p ^ " " ^ includes_string ^ " -CC \"" ^ filename ^ "\" > " ^ tmpname 508 in 509 if isSuccess (system cmdline) then tmpname 510 else raise Feedback.WantToExit ("cpp failed on "^filename) 511 end 512 | docpp NONE {filename, ...} = filename 513 514val usage_msg = GetOpt.usageInfo { 515 header = 516 "Usage: \n "^execname()^" [options] filename\n\n\ 517 \Use -l to adjust error lookahead. (The higher the number, the more the parser\n\ 518 \will try to make sense of stuff with parse errors.)\n\n\ 519 \For no analyses at all (not even typechecking), use --rawsyntaxonly.\n\n\ 520 \All options:\n", 521 options = options 522 } 523 524 525 526fun doit args = 527 let 528 val (_, realargs) = 529 GetOpt.getOpt {argOrder = GetOpt.RequireOrder, options = options, 530 errFn = die} args 531 val _ = if !show_help then 532 (print usage_msg ; OS.Process.exit OS.Process.success) 533 else if !bad_option then die usage_msg 534 else () 535 in 536 case realargs of 537 [] => die usage_msg 538 | [fname] => 539 let 540 val (ast,n) = StrictCParser.parse (docpp (!cpp)) (!error_lookahead) 541 (List.rev (!includes)) fname 542 in 543 if !parse_only then () 544 else 545 let 546 val ((ast', inits), cse) = 547 ProgramAnalysis.process_decls 548 {anon_vars = false, owners = [], 549 munge_info_fname = !munge_info_fname, 550 allow_underscore_idents = !underscore_idents} 551 (SyntaxTransforms.remove_typedefs ast) 552 val _ = ProgramAnalysis.export_mungedb cse 553 val _ = filename := fname 554 fun do_analyses alist = 555 case alist of 556 [] => exit (if !Feedback.numErrors = 0 then success else failure) 557 | f::fs => let 558 val () = f cse ast' 559 in 560 do_analyses fs 561 end 562 in 563 do_analyses (List.rev (!analyses)) 564 end 565 end 566 | _ => die usage_msg 567 end 568 569end; 570