| 1 | module checker |
| 2 | |
| 3 | import v.ast |
| 4 | |
| 5 | enum RootMutationVisibility { |
| 6 | none |
| 7 | direct |
| 8 | private_path |
| 9 | public_path |
| 10 | } |
| 11 | |
| 12 | @[inline] |
| 13 | fn is_visible_root_mutation(vis RootMutationVisibility) bool { |
| 14 | return vis in [.direct, .public_path] |
| 15 | } |
| 16 | |
| 17 | fn (mut c Checker) visible_param_mutation_cache_key(func ast.Fn, param_idx int) string { |
| 18 | return '${func.fkey()}|${param_idx}' |
| 19 | } |
| 20 | |
| 21 | fn (mut c Checker) fn_has_visible_mutation_for_param(func ast.Fn, param_idx int) bool { |
| 22 | if param_idx < 0 || param_idx >= func.params.len || !func.params[param_idx].is_mut { |
| 23 | return false |
| 24 | } |
| 25 | cache_key := c.visible_param_mutation_cache_key(func, param_idx) |
| 26 | if cache_key in c.visible_param_mutation_cache { |
| 27 | return c.visible_param_mutation_cache[cache_key] |
| 28 | } |
| 29 | if cache_key in c.visible_param_mutation_in_progress { |
| 30 | return true |
| 31 | } |
| 32 | if func.source_fn == unsafe { nil } || func.no_body || func.language != .v { |
| 33 | c.visible_param_mutation_cache[cache_key] = true |
| 34 | return true |
| 35 | } |
| 36 | fn_decl := unsafe { &ast.FnDecl(func.source_fn) } |
| 37 | if fn_decl == unsafe { nil } || param_idx >= fn_decl.params.len { |
| 38 | c.visible_param_mutation_cache[cache_key] = true |
| 39 | return true |
| 40 | } |
| 41 | c.visible_param_mutation_in_progress[cache_key] = true |
| 42 | res := c.fn_decl_has_visible_mutation_for_param(fn_decl, param_idx) |
| 43 | c.visible_param_mutation_in_progress.delete(cache_key) |
| 44 | c.visible_param_mutation_cache[cache_key] = res |
| 45 | return res |
| 46 | } |
| 47 | |
| 48 | fn (mut c Checker) fn_decl_has_visible_mutation_for_param(fn_decl &ast.FnDecl, param_idx int) bool { |
| 49 | if param_idx < 0 || param_idx >= fn_decl.params.len || !fn_decl.params[param_idx].is_mut { |
| 50 | return false |
| 51 | } |
| 52 | root_name := fn_decl.params[param_idx].name |
| 53 | root_type := fn_decl.params[param_idx].typ |
| 54 | for stmt in fn_decl.stmts { |
| 55 | if c.stmt_has_visible_mutation(stmt, root_name, root_type) { |
| 56 | return true |
| 57 | } |
| 58 | } |
| 59 | return false |
| 60 | } |
| 61 | |
| 62 | fn (mut c Checker) stmt_has_visible_mutation(stmt ast.Stmt, root_name string, root_type ast.Type) bool { |
| 63 | match stmt { |
| 64 | ast.FnDecl { |
| 65 | return false |
| 66 | } |
| 67 | ast.ExprStmt { |
| 68 | return c.expr_has_visible_mutation(stmt.expr, root_name, root_type) |
| 69 | } |
| 70 | ast.AssignStmt { |
| 71 | for left_expr in stmt.left { |
| 72 | if is_visible_root_mutation(c.expr_mutation_visibility(left_expr, root_name, |
| 73 | root_type)) |
| 74 | { |
| 75 | return true |
| 76 | } |
| 77 | } |
| 78 | if c.assign_stmt_aliases_visible_state(stmt, root_name, root_type) { |
| 79 | return true |
| 80 | } |
| 81 | } |
| 82 | else {} |
| 83 | } |
| 84 | |
| 85 | return c.node_children_have_visible_mutation(ast.Node(stmt), root_name, root_type) |
| 86 | } |
| 87 | |
| 88 | fn (mut c Checker) expr_has_visible_mutation(expr ast.Expr, root_name string, root_type ast.Type) bool { |
| 89 | match expr { |
| 90 | ast.AnonFn, ast.LambdaExpr { |
| 91 | return false |
| 92 | } |
| 93 | ast.CallExpr { |
| 94 | if c.call_has_visible_root_mutation(expr, root_name, root_type) { |
| 95 | return true |
| 96 | } |
| 97 | } |
| 98 | ast.PrefixExpr { |
| 99 | if expr.op == .amp |
| 100 | && is_visible_root_mutation(c.expr_mutation_visibility(expr.right, root_name, root_type)) { |
| 101 | return true |
| 102 | } |
| 103 | } |
| 104 | ast.PostfixExpr { |
| 105 | if is_visible_root_mutation(c.expr_mutation_visibility(expr.expr, root_name, root_type)) { |
| 106 | return true |
| 107 | } |
| 108 | } |
| 109 | ast.InfixExpr { |
| 110 | if expr.op == .left_shift |
| 111 | && is_visible_root_mutation(c.expr_mutation_visibility(expr.left, root_name, root_type)) { |
| 112 | return true |
| 113 | } |
| 114 | } |
| 115 | else {} |
| 116 | } |
| 117 | |
| 118 | return c.node_children_have_visible_mutation(ast.Node(expr), root_name, root_type) |
| 119 | } |
| 120 | |
| 121 | fn (mut c Checker) node_children_have_visible_mutation(node ast.Node, root_name string, root_type ast.Type) bool { |
| 122 | for child in node.children() { |
| 123 | match child { |
| 124 | ast.Expr { |
| 125 | if c.expr_has_visible_mutation(child, root_name, root_type) { |
| 126 | return true |
| 127 | } |
| 128 | } |
| 129 | ast.Stmt { |
| 130 | if child is ast.FnDecl { |
| 131 | continue |
| 132 | } |
| 133 | if c.stmt_has_visible_mutation(child, root_name, root_type) { |
| 134 | return true |
| 135 | } |
| 136 | } |
| 137 | ast.CallArg { |
| 138 | if c.expr_has_visible_mutation(child.expr, root_name, root_type) { |
| 139 | return true |
| 140 | } |
| 141 | } |
| 142 | ast.IfBranch { |
| 143 | if c.expr_has_visible_mutation(child.cond, root_name, root_type) { |
| 144 | return true |
| 145 | } |
| 146 | for stmt in child.stmts { |
| 147 | if c.stmt_has_visible_mutation(stmt, root_name, root_type) { |
| 148 | return true |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | ast.MatchBranch { |
| 153 | for branch_expr in child.exprs { |
| 154 | if c.expr_has_visible_mutation(branch_expr, root_name, root_type) { |
| 155 | return true |
| 156 | } |
| 157 | } |
| 158 | for stmt in child.stmts { |
| 159 | if c.stmt_has_visible_mutation(stmt, root_name, root_type) { |
| 160 | return true |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | ast.SelectBranch { |
| 165 | if c.stmt_has_visible_mutation(child.stmt, root_name, root_type) { |
| 166 | return true |
| 167 | } |
| 168 | for stmt in child.stmts { |
| 169 | if c.stmt_has_visible_mutation(stmt, root_name, root_type) { |
| 170 | return true |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | else {} |
| 175 | } |
| 176 | } |
| 177 | return false |
| 178 | } |
| 179 | |
| 180 | fn (mut c Checker) expr_mutation_visibility(expr ast.Expr, root_name string, root_type ast.Type) RootMutationVisibility { |
| 181 | mut reduced := expr |
| 182 | reduced = reduced.remove_par() |
| 183 | current := reduced |
| 184 | if current is ast.Ident { |
| 185 | return if current.name == root_name { |
| 186 | .direct |
| 187 | } else { |
| 188 | .none |
| 189 | } |
| 190 | } |
| 191 | if current is ast.SelectorExpr { |
| 192 | mut parent_expr := current.expr |
| 193 | parent_expr = parent_expr.remove_par() |
| 194 | parent := parent_expr |
| 195 | if parent is ast.Ident && parent.name == root_name { |
| 196 | root_sym := c.table.final_sym(c.unwrap_generic(root_type)) |
| 197 | field := c.table.find_field_with_embeds(root_sym, current.field_name) or { |
| 198 | return .public_path |
| 199 | } |
| 200 | return if field.is_pub { .public_path } else { .private_path } |
| 201 | } |
| 202 | return c.expr_mutation_visibility(parent, root_name, root_type) |
| 203 | } |
| 204 | if current is ast.IndexExpr { |
| 205 | return c.expr_mutation_visibility(current.left, root_name, root_type) |
| 206 | } |
| 207 | if current is ast.PrefixExpr { |
| 208 | return c.expr_mutation_visibility(current.right, root_name, root_type) |
| 209 | } |
| 210 | if current is ast.PostfixExpr { |
| 211 | return c.expr_mutation_visibility(current.expr, root_name, root_type) |
| 212 | } |
| 213 | if current is ast.CastExpr { |
| 214 | return c.expr_mutation_visibility(current.expr, root_name, root_type) |
| 215 | } |
| 216 | if current is ast.CallExpr { |
| 217 | if current.is_method { |
| 218 | return c.expr_mutation_visibility(current.left, root_name, root_type) |
| 219 | } |
| 220 | } |
| 221 | return .none |
| 222 | } |
| 223 | |
| 224 | fn (mut c Checker) call_has_visible_root_mutation(node ast.CallExpr, root_name string, root_type ast.Type) bool { |
| 225 | mut called_fn := ast.Fn{} |
| 226 | mut has_called_fn := false |
| 227 | if func := c.find_called_fn(node) { |
| 228 | called_fn = func |
| 229 | has_called_fn = true |
| 230 | } |
| 231 | if node.is_method { |
| 232 | left_vis := c.expr_mutation_visibility(node.left, root_name, root_type) |
| 233 | if has_called_fn { |
| 234 | if called_fn.params.len > 0 && called_fn.params[0].is_mut { |
| 235 | match left_vis { |
| 236 | .direct { |
| 237 | if c.fn_has_visible_mutation_for_param(called_fn, 0) { |
| 238 | return true |
| 239 | } |
| 240 | } |
| 241 | .public_path { |
| 242 | return true |
| 243 | } |
| 244 | else {} |
| 245 | } |
| 246 | } |
| 247 | } else if is_visible_root_mutation(left_vis) { |
| 248 | return true |
| 249 | } |
| 250 | } |
| 251 | if !has_called_fn { |
| 252 | for arg in node.args { |
| 253 | if arg.is_mut |
| 254 | && is_visible_root_mutation(c.expr_mutation_visibility(arg.expr, root_name, root_type)) { |
| 255 | return true |
| 256 | } |
| 257 | } |
| 258 | return false |
| 259 | } |
| 260 | for i, arg in node.args { |
| 261 | if !arg.is_mut { |
| 262 | continue |
| 263 | } |
| 264 | param_idx := c.call_arg_param_index(called_fn, i) |
| 265 | arg_vis := c.expr_mutation_visibility(arg.expr, root_name, root_type) |
| 266 | if param_idx < 0 || param_idx >= called_fn.params.len { |
| 267 | if is_visible_root_mutation(arg_vis) { |
| 268 | return true |
| 269 | } |
| 270 | continue |
| 271 | } |
| 272 | if !called_fn.params[param_idx].is_mut { |
| 273 | continue |
| 274 | } |
| 275 | match arg_vis { |
| 276 | .direct { |
| 277 | if c.fn_has_visible_mutation_for_param(called_fn, param_idx) { |
| 278 | return true |
| 279 | } |
| 280 | } |
| 281 | .public_path { |
| 282 | return true |
| 283 | } |
| 284 | else {} |
| 285 | } |
| 286 | } |
| 287 | return false |
| 288 | } |
| 289 | |
| 290 | fn (mut c Checker) find_called_fn(node ast.CallExpr) ?ast.Fn { |
| 291 | if node.is_method { |
| 292 | mut candidate_types := []ast.Type{} |
| 293 | for typ in [node.receiver_type, node.left_type, c.unwrap_generic(node.receiver_type), |
| 294 | c.unwrap_generic(node.left_type)] { |
| 295 | if typ != 0 && typ !in candidate_types { |
| 296 | candidate_types << typ |
| 297 | } |
| 298 | } |
| 299 | for typ in candidate_types { |
| 300 | sym := c.table.sym(c.unwrap_generic(typ)) |
| 301 | if method := c.table.find_method(sym, node.name) { |
| 302 | return method |
| 303 | } |
| 304 | if method := c.table.find_method_with_embeds(sym, node.name) { |
| 305 | return method |
| 306 | } |
| 307 | } |
| 308 | return none |
| 309 | } |
| 310 | return c.table.find_fn(node.name) |
| 311 | } |
| 312 | |
| 313 | fn (c &Checker) call_arg_param_index(func ast.Fn, arg_idx int) int { |
| 314 | offset := if func.is_method { 1 } else { 0 } |
| 315 | if func.is_variadic && func.params.len > 0 && arg_idx + offset >= func.params.len - 1 { |
| 316 | return func.params.len - 1 |
| 317 | } |
| 318 | param_idx := arg_idx + offset |
| 319 | if param_idx >= func.params.len { |
| 320 | return -1 |
| 321 | } |
| 322 | return param_idx |
| 323 | } |
| 324 | |
| 325 | fn (mut c Checker) assign_stmt_aliases_visible_state(node ast.AssignStmt, root_name string, root_type ast.Type) bool { |
| 326 | mut pair_count := node.left.len |
| 327 | if node.right.len < pair_count { |
| 328 | pair_count = node.right.len |
| 329 | } |
| 330 | for i in 0 .. pair_count { |
| 331 | right_vis := c.expr_mutation_visibility(node.right[i], root_name, root_type) |
| 332 | if !is_visible_root_mutation(right_vis) { |
| 333 | continue |
| 334 | } |
| 335 | right_type := if i < node.right_types.len { |
| 336 | node.right_types[i] |
| 337 | } else { |
| 338 | ast.no_type |
| 339 | } |
| 340 | if !c.type_may_share_mutable_storage(right_type) { |
| 341 | continue |
| 342 | } |
| 343 | mut left_expr := node.left[i] |
| 344 | left_expr = left_expr.remove_par() |
| 345 | if left_expr is ast.Ident && left_expr.is_mut() { |
| 346 | return true |
| 347 | } |
| 348 | } |
| 349 | return false |
| 350 | } |
| 351 | |
| 352 | fn (mut c Checker) type_may_share_mutable_storage(typ ast.Type) bool { |
| 353 | if typ == 0 || typ == ast.no_type { |
| 354 | return false |
| 355 | } |
| 356 | unwrapped := c.unwrap_generic(typ) |
| 357 | if unwrapped.is_any_kind_of_pointer() || unwrapped.has_flag(.shared_f) { |
| 358 | return true |
| 359 | } |
| 360 | return c.table.final_sym(unwrapped).kind in [.array, .map, .chan, .interface, .thread, .function] |
| 361 | } |
| 362 | |