~/bend-docscommunity

src/rules/suspicious/index.bend source

src/rules/suspicious/index.bend on the hub · documented module

# rule index: a def that calls itself also calls `List.get(..)` or# `String.get(..)`. Both walk the cons list from the head to the index, so a# per-index loop is quadratic (AppSprout's sort went from 39 s to 0.9 s# walking the list itself). Walk the list in the recursion, or materialize# what the loop needs in one pass. A get anywhere in the def is reported,# one in a base arm that runs once included. A literal index of any size# (`List.get(.., xs, 0n)`, `5000n`) is exempt, as are laws and proofs (a def# with no type at all fills a law: it is a proof). A# `List.get` on a fixed table (a literal, a sized array, a constant# `List.replicate`) is `table`'s finding instead, so the two rules do not# both report that call. `String.get` is never `table`'s: it stays here.import Baseimport ../../src.bend as Srcimport ../../finding.bend as Fimport ../../syntax/lex.bend as Leximport ../../syntax/tree.bend as Treeimport ../calls.bend as Callsimport ../../lazy/lazy.bend as Lazyimport ./table.bend as Table# is the chain exactly one number?def literal(nn: Tree.Node) -> Bool:  match nn:    case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, Tree.NNil{}}:      True{}    case other:      False{}# a fixed table is reported by `table`, not heredef held(+list: Bool, coll: Tree.Node, +fixed: List<&2, String>) -> Bool:  match list:    case False{}:      False{}    case True{}:      Table.owns(coll, fixed)# every List.get / String.get call at a computed indexdef walk(nn: Tree.Node, +path: String, +fixed: List<&2, String>) -> List<&2, F.Finding>:  match nn:    case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}:      +list = String.eq(t, "List.get")      +as = Calls.args(kids)      +get = Bool.and(String.eq(o, "("), Bool.or(list, String.eq(t, "String.get")))      +fire = Bool.and(get, Bool.and(Bool.not(literal(Calls.arg(as, Bool.pick(Nat, list, 3n, 1n)))),        Bool.not(held(list, Calls.arg(as, 2n), fixed))))      +more = List.concat(&2, F.Finding, [walk(kids, path, fixed), walk(rest, path, fixed)])      Bool.pick(List<&2, F.Finding>, fire,        F.Finding{path, l, c, U32.from_nat(String.length(t)), "index",          t ++ " in a recursive def walks the list from its head on every step, which is quadratic; recurse over the list itself."} <> more,        more)    case Tree.NCons{Tree.Group{open, +kids, close}, rest}:      List.concat(&2, F.Finding, [walk(kids, path, fixed), walk(rest, path, fixed)])    case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}:      List.concat(&2, F.Finding, [walk(kids, path, fixed), walk(body, path, fixed), walk(rest, path, fixed)])    case Tree.NCons{h, rest}:      walk(rest, path, fixed)    case other:      Nil{}def check.go(  ds: List<&2, Calls.Def>,  +mods: List<&2, String>,  +path: String,  acc: List<&2, List<&2, F.Finding>>) -> List<&2, F.Finding>:  match ds:    case Nil{}:      List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc))    case Con{Calls.Def{+name, +sig, +body}, rest}:      check.go(rest, mods, path,        Lazy.stop(List<&2, F.Finding>,          Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [],          _u => walk(body, path, Table.scope(body, mods))) <> acc)# the ruledef check(ss: Src.Src) -> List<&2, F.Finding>:  Src.Src{path, text, toks, tree, bound, items} = ss  +ds = Calls.defs(tree)  check.go(ds, Table.modules(ds, []), path, [])