|
| 1 | +(** C-2PO: A Weakly-Relational Pointer Analysis for C based on 2 Pointer Logic. The analysis can infer equalities and disequalities between terms which are built from pointer variables, with the addition of constants and dereferencing. ([c2po])*) |
| 2 | + |
| 3 | +open Analyses |
| 4 | +open GoblintCil |
| 5 | +open C2poDomain |
| 6 | +open CongruenceClosure |
| 7 | +open Batteries |
| 8 | +open SingleThreadedLifter |
| 9 | +open DuplicateVars |
| 10 | + |
| 11 | +module Spec = |
| 12 | +struct |
| 13 | + include DefaultSpec |
| 14 | + include Analyses.IdentitySpec |
| 15 | + module D = D |
| 16 | + module C = D |
| 17 | + |
| 18 | + let name () = "c2po" |
| 19 | + let startcontext () = D.top () |
| 20 | + |
| 21 | + (** Find reachable variables in a function *) |
| 22 | + let reachable_from_args ctx args = |
| 23 | + let collect_reachable_from_exp acc e = |
| 24 | + let reachable_from_exp = ctx.ask (ReachableFrom e) in |
| 25 | + let reachable_from_exp = Queries.AD.to_var_may reachable_from_exp in |
| 26 | + reachable_from_exp @ acc |
| 27 | + in |
| 28 | + let res = List.fold collect_reachable_from_exp [] args in |
| 29 | + if M.tracing then M.tracel "c2po-reachable" "reachable vars: %s\n" (List.fold_left (fun s v -> s ^ v.vname ^"; ") "" res); |
| 30 | + res |
| 31 | + |
| 32 | + (* Returns Some true if we know for sure that it is true, |
| 33 | + and Some false if we know for sure that it is false, |
| 34 | + and None if we don't know anyhing. *) |
| 35 | + let eval_guard ask cc e ik = |
| 36 | + let open Queries in |
| 37 | + let prop_list = T.prop_of_cil ask e true in |
| 38 | + match split prop_list with |
| 39 | + | [], [], [] -> |
| 40 | + ID.top() |
| 41 | + | x::xs, _, [] -> |
| 42 | + if fst (eq_query cc x) then |
| 43 | + ID.of_bool ik true |
| 44 | + else if neq_query cc x then |
| 45 | + ID.of_bool ik false |
| 46 | + else |
| 47 | + ID.top() |
| 48 | + | _, y::ys, [] -> |
| 49 | + if neq_query cc y then |
| 50 | + ID.of_bool ik true |
| 51 | + else if fst (eq_query cc y) then |
| 52 | + ID.of_bool ik false |
| 53 | + else |
| 54 | + ID.top() |
| 55 | + | _ -> |
| 56 | + ID.top() (*there should never be block disequalities here...*) |
| 57 | + |
| 58 | + (** Convert a conjunction to an invariant.*) |
| 59 | + let conj_to_invariant ask conjs = |
| 60 | + let f a prop = |
| 61 | + try |
| 62 | + let exp = T.prop_to_cil prop in (* May raise UnsupportedCilExpression *) |
| 63 | + if M.tracing then M.trace "c2po-invariant" "Adding invariant: %a" d_exp exp; |
| 64 | + Invariant.(a && of_exp exp) |
| 65 | + with T.UnsupportedCilExpression _ -> |
| 66 | + a |
| 67 | + in |
| 68 | + List.fold f (Invariant.top()) conjs |
| 69 | + |
| 70 | + let query ctx (type a) (q: a Queries.t): a Queries.result = |
| 71 | + let open Queries in |
| 72 | + match ctx.local with |
| 73 | + | `Bot -> Result.top q |
| 74 | + | `Lifted cc -> |
| 75 | + match q with |
| 76 | + | EvalInt e -> |
| 77 | + let ik = Cilfacade.get_ikind_exp e in |
| 78 | + eval_guard (ask_of_man ctx) cc e ik |
| 79 | + | Queries.Invariant context -> |
| 80 | + let scope = Node.find_fundec ctx.node in |
| 81 | + let t = D.remove_vars_not_in_scope scope cc in |
| 82 | + let conj = get_conjunction t in |
| 83 | + let ask = ask_of_man ctx in |
| 84 | + conj_to_invariant ask conj |
| 85 | + | _ -> |
| 86 | + Result.top q |
| 87 | + |
| 88 | + (** Assign the right hand side rhs (that is already |
| 89 | + converted to a C-2PO term) to the term `lterm`. *) |
| 90 | + let assign_term cc ask lterm rhs lval_t = |
| 91 | + (* ignore assignments to values that are not 64 bits *) |
| 92 | + match T.get_element_size_in_bits lval_t, rhs with |
| 93 | + (* Indefinite assignment *) |
| 94 | + | lval_size, (None, _) -> |
| 95 | + D.remove_may_equal_terms ask lval_size lterm cc |
| 96 | + (* Definite assignment *) |
| 97 | + | lval_size, (Some rterm, Some roffset) -> |
| 98 | + let dummy_var = MayBeEqual.dummy_var lval_t in |
| 99 | + |
| 100 | + if M.tracing then M.trace "c2po-assign" "assigning: var: %s; expr: %s + %s. \nTo_cil: lval: %a; expr: %a\n" (T.show lterm) (T.show rterm) (Z.to_string roffset) d_exp (T.to_cil lterm) d_exp (T.to_cil rterm); |
| 101 | + |
| 102 | + let equal_dummy_rterm = [Equal (dummy_var, rterm, roffset)] in |
| 103 | + let equal_dummy_lterm = [Equal (lterm, dummy_var, Z.zero)] in |
| 104 | + |
| 105 | + cc |> |
| 106 | + meet_conjs_opt equal_dummy_rterm |> |
| 107 | + D.remove_may_equal_terms ask lval_size lterm |> |
| 108 | + meet_conjs_opt equal_dummy_lterm |> |
| 109 | + D.remove_terms_containing_aux_variable |
| 110 | + |
| 111 | + | _ -> (* this is impossible *) |
| 112 | + C2PODomain.top () |
| 113 | + |
| 114 | + (** Assign Cil Lval to a right hand side that is already converted to |
| 115 | + C-2PO terms.*) |
| 116 | + let assign_lval cc ask lval expr = |
| 117 | + let lval_t = typeOfLval lval in |
| 118 | + try |
| 119 | + let lterm = T.of_lval ask lval in |
| 120 | + assign_term cc ask lterm expr lval_t |
| 121 | + with T.UnsupportedCilExpression _ -> |
| 122 | + (* the assigned variables couldn't be parsed, so we don't know which addresses were written to. |
| 123 | + We have to forget all the information we had. |
| 124 | + This should almost never happen. |
| 125 | + Except if the left hand side is a complicated expression like myStruct.field1[i]->field2[z+k], and Goblint can't infer the offset.*) |
| 126 | + if M.tracing then M.trace "c2po-invalidate" "Invalidate lval: %a" d_lval lval; |
| 127 | + C2PODomain.top () |
| 128 | + |
| 129 | + let assign ctx lval expr = |
| 130 | + let ask = (ask_of_man ctx) in |
| 131 | + match ctx.local with |
| 132 | + | `Bot -> |
| 133 | + `Bot |
| 134 | + | `Lifted cc -> |
| 135 | + let cc = assign_lval cc ask lval (T.of_cil ask expr) in |
| 136 | + let cc = reset_normal_form cc in |
| 137 | + let res = `Lifted cc in |
| 138 | + if M.tracing then M.trace "c2po-assign" "assign: var: %a; expr: %a; result: %s.\n" d_lval lval d_plainexp expr (D.show res); |
| 139 | + res |
| 140 | + |
| 141 | + let branch ctx e pos = |
| 142 | + let props = T.prop_of_cil (ask_of_man ctx) e pos in |
| 143 | + let valid_props = T.filter_valid_pointers props in |
| 144 | + let res = |
| 145 | + match ctx.local with |
| 146 | + | `Bot -> `Bot |
| 147 | + | `Lifted cc -> |
| 148 | + if List.is_empty valid_props then |
| 149 | + `Lifted cc |
| 150 | + else |
| 151 | + try |
| 152 | + let meet = meet_conjs_opt valid_props cc in |
| 153 | + let t = reset_normal_form meet in |
| 154 | + `Lifted t |
| 155 | + with Unsat -> |
| 156 | + `Bot |
| 157 | + in |
| 158 | + if M.tracing then M.trace "c2po" "branch:\n Actual equality: %a; pos: %b; valid_prop_list: %s; is_bot: %b\n" d_exp e pos (show_conj valid_props) (D.is_bot res); |
| 159 | + if D.is_bot res then raise Deadcode; |
| 160 | + res |
| 161 | + |
| 162 | + let body ctx f = |
| 163 | + ctx.local |
| 164 | + |
| 165 | + let assign_return ask d return_var expr = |
| 166 | + (* the return value is not stored on the heap, therefore we don't need to remove any terms *) |
| 167 | + match T.of_cil ask expr with |
| 168 | + | (Some term, Some offset) -> |
| 169 | + let ret_var_eq_term = [Equal (return_var, term, offset)] in |
| 170 | + let assign_by_meet = meet_conjs_opt ret_var_eq_term d in |
| 171 | + reset_normal_form assign_by_meet |
| 172 | + | _ -> d |
| 173 | + |
| 174 | + let return ctx exp_opt f = |
| 175 | + let res = |
| 176 | + match exp_opt with |
| 177 | + | Some e -> |
| 178 | + begin match ctx.local with |
| 179 | + | `Bot -> |
| 180 | + `Bot |
| 181 | + | `Lifted d -> |
| 182 | + let return_var = MayBeEqual.return_var (typeOf e) in |
| 183 | + let d = assign_return (ask_of_man ctx) d return_var e in |
| 184 | + `Lifted d |
| 185 | + end |
| 186 | + | None -> ctx.local |
| 187 | + in |
| 188 | + if M.tracing then M.trace "c2po-function" "return: exp_opt: %a; state: %s; result: %s\n" d_exp (BatOption.default (MayBeEqual.dummy_lval_print (TVoid [])) exp_opt) (D.show ctx.local) (D.show res); |
| 189 | + res |
| 190 | + |
| 191 | + (** var_opt is the variable we assign to. It has type lval. v=malloc.*) |
| 192 | + let special ctx lval_opt v exprs = |
| 193 | + let desc = LibraryFunctions.find v in |
| 194 | + let ask = ask_of_man ctx in |
| 195 | + match ctx.local with |
| 196 | + | `Bot -> `Bot |
| 197 | + | `Lifted cc -> |
| 198 | + let t = |
| 199 | + begin match lval_opt with |
| 200 | + | None -> |
| 201 | + cc |
| 202 | + | Some lval -> |
| 203 | + (* forget information about var, |
| 204 | + but ignore assignments to values that are not 64 bits *) |
| 205 | + try |
| 206 | + let size = T.get_element_size_in_bits (typeOfLval lval) in |
| 207 | + let lterm = T.of_lval ask lval in |
| 208 | + let cc = D.remove_may_equal_terms ask size lterm cc in |
| 209 | + begin match desc.special exprs with |
| 210 | + | Malloc _ |
| 211 | + | Calloc _ |
| 212 | + | Alloca _ -> |
| 213 | + add_block_diseqs cc lterm |
| 214 | + | _ -> cc |
| 215 | + end |
| 216 | + with T.UnsupportedCilExpression _ -> |
| 217 | + C2PODomain.top () |
| 218 | + end |
| 219 | + in |
| 220 | + match desc.special exprs with |
| 221 | + | Assert { exp; refine; _ } -> |
| 222 | + if not refine then |
| 223 | + ctx.local |
| 224 | + else |
| 225 | + branch ctx exp true |
| 226 | + | _ -> |
| 227 | + `Lifted (reset_normal_form t) |
| 228 | + |
| 229 | + (** First all local variables of the function are duplicated, |
| 230 | + then we remember the value of each local variable at the beginning of the function by using the analysis startState. |
| 231 | + This way we can infer the relations between the local variables of the caller and the pointers that were modified by the function. *) |
| 232 | + let enter ctx var_opt f args = |
| 233 | + (* add duplicated variables, and set them equal to the original variables *) |
| 234 | + match ctx.local with |
| 235 | + | `Bot -> [`Bot, `Bot] |
| 236 | + | `Lifted cc -> |
| 237 | + let ghost_equality v = |
| 238 | + Equal (T.term_of_varinfo (DuplicVar v), T.term_of_varinfo (NormalVar v), Z.zero) |
| 239 | + in |
| 240 | + let ghost_equalities_for_params = List.map ghost_equality f.sformals in |
| 241 | + let equalities_to_add = T.filter_valid_pointers ghost_equalities_for_params in |
| 242 | + let state_with_ghosts = meet_conjs_opt equalities_to_add cc in |
| 243 | + if M.tracing then begin |
| 244 | + let dummy_lval = Var (Var.dummy_varinfo (TVoid [])), NoOffset in |
| 245 | + let lval = BatOption.default dummy_lval var_opt in |
| 246 | + M.trace "c2po-function" "enter1: var_opt: %a; state: %s; state_with_ghosts: %s\n" d_lval lval (D.show ctx.local) (C2PODomain.show state_with_ghosts); |
| 247 | + end; |
| 248 | + (* remove callee vars that are not reachable and not global *) |
| 249 | + let reachable_variables = |
| 250 | + let reachable = f.sformals @ f.slocals @ reachable_from_args ctx args in |
| 251 | + Var.from_varinfo reachable f.sformals |
| 252 | + in |
| 253 | + let new_state = D.remove_terms_not_containing_variables reachable_variables state_with_ghosts in |
| 254 | + if M.tracing then M.trace "c2po-function" "enter2: result: %s\n" (C2PODomain.show new_state); |
| 255 | + let new_state = reset_normal_form new_state in |
| 256 | + [ctx.local, `Lifted new_state] |
| 257 | + |
| 258 | + let remove_out_of_scope_vars cc f = |
| 259 | + let local_vars = f.sformals @ f.slocals in |
| 260 | + let duplicated_vars = f.sformals in |
| 261 | + let cc = D.remove_terms_containing_return_variable cc in |
| 262 | + D.remove_terms_containing_variables (Var.from_varinfo local_vars duplicated_vars) cc |
| 263 | + |
| 264 | + let combine_env ctx lval_opt expr f args t_context_opt f_d (f_ask: Queries.ask) = |
| 265 | + match ctx.local with |
| 266 | + | `Bot -> `Bot |
| 267 | + | `Lifted cc -> |
| 268 | + let caller_ask = ask_of_man ctx in |
| 269 | + (* assign function parameters to duplicated values *) |
| 270 | + let arg_assigns = GobList.combine_short f.sformals args in |
| 271 | + let assign_term st (var, arg) = |
| 272 | + let ghost_var = T.term_of_varinfo (DuplicVar var) in |
| 273 | + let arg = T.of_cil f_ask arg in |
| 274 | + assign_term st caller_ask ghost_var arg var.vtype |
| 275 | + in |
| 276 | + let state_with_assignments = List.fold_left assign_term cc arg_assigns in |
| 277 | + |
| 278 | + if M.tracing then M.trace "c2po-function" "combine_env0: state_with_assignments: %s\n" (C2PODomain.show state_with_assignments); |
| 279 | + |
| 280 | + (*remove all variables that were tainted by the function*) |
| 281 | + let tainted = f_ask.f (MayBeTainted) in |
| 282 | + if M.tracing then M.trace "c2po-tainted" "combine_env1: %a\n" MayBeEqual.AD.pretty tainted; |
| 283 | + |
| 284 | + let local = D.remove_tainted_terms caller_ask tainted state_with_assignments in |
| 285 | + match D.meet (`Lifted local) f_d with |
| 286 | + | `Bot -> `Bot |
| 287 | + | `Lifted cc -> |
| 288 | + let cc = reset_normal_form @@ remove_out_of_scope_vars cc f in |
| 289 | + if M.tracing then begin |
| 290 | + let dummy_lval = Var (Var.dummy_varinfo (TVoid[])), NoOffset in |
| 291 | + let lval = BatOption.default dummy_lval lval_opt in |
| 292 | + M.trace "c2po-function" "combine_env2: var_opt: %a; local_state: %s; f_state: %s; meeting everything: %s\n" d_lval lval (D.show ctx.local) (D.show f_d) (C2PODomain.show cc) |
| 293 | + end; |
| 294 | + `Lifted cc |
| 295 | + |
| 296 | + let combine_assign ctx var_opt expr f args t_context_opt f_d (f_ask: Queries.ask) = |
| 297 | + match ctx.local with |
| 298 | + | `Bot -> `Bot |
| 299 | + | `Lifted cc -> |
| 300 | + let caller_ask = ask_of_man ctx in |
| 301 | + (* assign function parameters to duplicated values *) |
| 302 | + let arg_assigns = GobList.combine_short f.sformals args in |
| 303 | + let assign_term st (var, arg) = |
| 304 | + let ghost_var = T.term_of_varinfo (DuplicVar var) in |
| 305 | + let arg = T.of_cil f_ask arg in |
| 306 | + assign_term st caller_ask ghost_var arg var.vtype |
| 307 | + in |
| 308 | + let state_with_assignments = List.fold_left assign_term cc arg_assigns in |
| 309 | + match D.meet (`Lifted state_with_assignments) f_d with |
| 310 | + | `Bot -> `Bot |
| 311 | + | `Lifted cc -> |
| 312 | + let cc = match var_opt with |
| 313 | + | None -> |
| 314 | + cc |
| 315 | + | Some lval -> |
| 316 | + let return_type = typeOfLval lval in |
| 317 | + let return_var = MayBeEqual.return_var return_type in |
| 318 | + let return_var = (Some return_var, Some Z.zero) in |
| 319 | + assign_lval cc f_ask lval return_var |
| 320 | + in |
| 321 | + if M.tracing then M.trace "c2po-function" "combine_assign1: assigning return value: %s\n" (C2PODomain.show cc); |
| 322 | + let cc = remove_out_of_scope_vars cc f in |
| 323 | + let cc = reset_normal_form cc in |
| 324 | + if M.tracing then M.trace "c2po-function" "combine_assign2: result: %s\n" (C2PODomain.show cc); |
| 325 | + `Lifted cc |
| 326 | + |
| 327 | + let startstate v = |
| 328 | + D.top () |
| 329 | + let threadenter ctx ~multiple lval f args = |
| 330 | + [D.top ()] |
| 331 | + let threadspawn ctx ~multiple lval f args fctx = |
| 332 | + D.top() |
| 333 | + let exitstate v = |
| 334 | + D.top () |
| 335 | + |
| 336 | +end |
| 337 | + |
| 338 | +let _ = |
| 339 | + MCP.register_analysis ~dep:["startState"; "taintPartialContexts"] (module SingleThreadedLifter(Spec) : MCPSpec) |
0 commit comments