OxL Справочник OxL
РусскийEnglish

Static analyzer (ч. III.4) — status of every rule

The analyzer runs in oxl check, oxl build and the language server. A diagnostic of level E makes the command fail (oxl check and oxl build exit with code 3); level W is reported and can be tuned in [lint]. Most rules are ordinary passes of compiler/src/sema; the flow-sensitive ones live in compiler/src/sema/Analyzer.cpp (Sema::runAnalyzer, after ownership checking, before the lints).

Every code has a page docs/diagnostics/<code>.md.

Level E#

rulecodelevelimplementedwheretests
incompatible schema change (§9.2)schema-breakEyesSerial.cpp, SerialLock.cpp§09.2/break-*, tests/unit/driver/schema_lock_test.cpp
non-transferable value sent to another task (§7.3)sendEyesOwnership.cpp§07.3/send-*, ownership_test.cpp
call with a disallowed effect (§6.2)effectsEyesPasses.cpp, CheckCall.cpp§06.2/*, §III.4/effects-fails-build
unhandled result with an error (§6.5)unhandled-errorEyesCheckControl.cpp§03.5/error-result-cannot-be-ignored, §III.4/unhandled-error-fails-build
@must_close value leaves scope without close() (§4.3)must-closeEyesOwnership.cpp§II.1/*-stream-must-close, §III.4/must-close-fails-build
secret.reveal() reaches a log, a string or a responsesecret-leakEyes (D-3200)Analyzer.cpp§III.4/secret-leak-*, §III.4/secret-show-is-safe, analyzer_test.cpp
mixing kinds of time (§17.2)time-mixEyes (D-3201)CheckOps.cpp§III.4/time-mix-time-instant, §III.4/time-same-kind-ok, analyzer_test.cpp
@retry without @idempotent/@readonly (§10.2)retry-unsafeEyes, under the code retry-not-idempotent (services task)ServiceCheck.cpp§10.2/retry-without-idempotent-or-readonly-is-error
rpc nobody can call: no allow/@public/@allow, not @internal, never called (§13.4)missing-policyEyes (D-3203)ServiceCheck.cpp§13.4/missing-policy-*, analyzer_test.cpp
service, actor, topic or saga without a role (§12.1)unhostedEyes, under four codes (D-6553): service-not-served (ServiceCheck.cpp), actor-not-hosted (sharded actors), topic-without-journal, saga-without-role (Placement.cpp); checked when the program has a cluster with rolesServiceCheck.cpp, Placement.cpp§12.1/service-without-role, §12.1/actor-not-hosted, §12.1/topic-without-journal-role, §12.1/saga-without-*, §III.4/unhosted-actor-without-role
saga step or compensation without @idempotentsaga-idempotencyEyes (D-5105)SagaSema.cpp§11.3/saga-rpc-without-idempotent-is-an-error

Three names of the Standard's table are reported under more specific codes (D-6553): retry-unsafe = retry-not-idempotent, unhosted = service-not-served / actor-not-hosted / topic-without-journal / saga-without-role, durable-non-idempotent = pipeline-idempotency. The codes are not renamed (DiagCodes.def: never rename a code); [lint] accepts the Standard's names and applies the level to the codes behind them (diagCodesForLintName).

Level W#

rulecodeimplementedwheretests
unused import, variable, depunusedyes (imports, variables; dep with services)Passes.cpp§02.2/unused-import-warns
blocking FFI call without blockingblocking-in-taskyesCheckCall.cpp
possible cyclic wait between actorsactor-cycleyes (D-6556)LintRules.cpp§III.4/actor-cycle-*, lint_rules_test.cpp
repeated literal in attributes/argumentsmagic-literalyes (D-6554), LSP quick fix (D-6559)LintRules.cpp§III.4/magic-literal-*, lint_rules_test.cpp, server_test.cpp
naming convention (IV.2)namingyes (D-6555)LintRules.cpp§III.4/naming-*, lint_rules_test.cpp
query in a loop over the result of another queryn-plus-oneyes (D-6557)LintRules.cpp§III.4/n-plus-one-*, lint_rules_test.cpp
query on a sharded table without shard keyscatter-gatheryes (D-6558)LintRules.cpp§III.4/scatter-gather-*, lint_rules_test.cpp
durable pipeline step calls a non-idempotent rpcpipeline-idempotency (durable-non-idempotent)yes (D-5105)SagaSema.cpp§10.6/pipeline-durable-step-without-idempotent-rpc-warns, §III.4/durable-non-idempotent-lint-name
user rule ([lint] plugins)lint-pluginyes (D-6560)LintPlugins.cpplint_rules_test.cpp
glob import in a library, use of a deprecated itemglob-import, deprecatedyesextra rules, not in the table of III.4

The W rules check the main package only (not std, not dependencies, not the plugin files).

[lint] (oxl.toml)#

toml
[lint]
magic-literal = "error"        # "warn" | "error" | "off" ("allow" is the same as "off")
scatter-gather = "off"
unhosted = "error"             # the Standard's names stand for the codes behind them (D-6553)
plugins = ["lints/no_todo.oxl"]
no_todo = "error"              # a plugin's rule: the file name without .oxl, default "warn"

A single-file program (oxl build app.oxl) takes the [lint] table of an oxl.toml in the same directory, and only that table (D-6551).

Suppression on a declaration (D-7830)#

@allow(lint: <rule>, reason: "…") silences one rule for the declaration that carries it (item, method, field, variant, rpc signature, actor message): the diagnostics of the rule inside its attributes, signature and body are dropped. <rule> is a name of the tables above (magic-literal, scatter-gather, written with hyphens or as a string), a diagnostic code or the name of a [lint] plugins rule. reason is required (bad-allow), an unknown rule is unknown-lint, rules that report errors cannot be suppressed. oxl fix (D-7820) applies the quick fixes of the diagnostics that remain.

The rules in detail#

  • magic-literal (D-6554, IV.1). Counted: duration and size literals in any attribute argument or call argument; integer literals greater than 1 in the arguments of the limit attributes @retry @ratelimit @window @shard @cache @timeout @deadline @idle, in call or attribute arguments named like a limit (count:, max:, limit:, size:, capacity:, retries:, attempts:, burst:, ttl:, timeout:, …), and the retry n / within d suffixes of a chain. @tag/@v/@since/@removed numbers, literals in const/static initializers, list elements and tests are not counted. Every occurrence of a literal used 2+ times is reported (with a note at the first one). The diagnostic carries the quick fix Extract to constant NAME: const NAME = lit appended to the file and NAME at every occurrence in that file; NAME is the shared context (attribute or argument name) in UPPER_SNAKE_CASE plus the literal (TIMEOUT_3S), or DURATION_…/SIZE_…/LIMIT_….
  • naming (D-6555, IV.2). UpperCamelCase: data, enums, interfaces, type aliases, services, actors, topics, configs, attr declarations, enum variants. lowerCamelCase: functions, methods, msgs, rpcs, fields, actor vars, parameters, locals (let / for / match bindings, lambda parameters). UPPER_SNAKE_CASE: const and static (module level, impl and interface consts). snake_case: module directories; lower_snake_case: cluster roles. A leading _ is ignored; extern c / export c functions, operators, init and generated names are skipped. The root module of a single-file program is named after the file and is not checked; the package name is not checked (it comes from oxl.toml and may follow the registry's rules).
  • actor-cycle (D-6556, §8.8). Nodes: actor types of the package. Edge A → B: a msg of A — or a function of the package it calls, transitively — calls a msg of B through an ActorRef and waits for it (not under spawn, a ~> step or a closure). Every call that is an edge inside a strongly connected component (including a self-loop through a ref) is reported, with the cycle in the message. It is a type-level approximation: two instances of one actor type form a self-loop.
  • n-plus-one (D-6557). A query is a call of std.orm Db.find/all/count, Table.all/count, Query.run/count (a from x in db.T query lowers to Query.run). Reported: a query inside the body (or where) of a for whose iterable contains a query or names a local that was bound to an expression containing a query, and inside the arguments of map/forEach/each/filter/ flatMap/filterMap/any/every/fold called on such a value. Loops over other lists are not reported.
  • scatter-gather (D-6558, IV.6). For tables with @shard(by: .f): a Query.run/count whose sqlWhere texts have no top-level "f" = ?N conjunct (the runtime's routing test, D-4388), Table.all/count, Db.all/count, and Db.find when f is not the @pk field.

Lint plugins (std.comptime, D-6560..D-6564)#

A plugin is an OxL file listed in [lint] plugins (paths relative to the package root, or to the directory of a single-file program). It is compiled as a module lint_<stem> of the package, so it is type-checked like other code, and its functions run in the comptime interpreter. Every comptime fn name(d: Decl) of the file (any name; the result may be () or () | CompileError) is called once for every declaration and member of the package:

oxl
use std.comptime { Decl, CompileError, warn }

comptime fn noTodo(d: Decl) {
  if d.kind == "fn" && d.name.startsWith("todo") { warn(d.span, "unfinished function `{d.name}`") }
}

comptime fn noLongSleeps(d: Decl) -> () | CompileError {
  for e in d.exprs where e.kind == "duration" && e.text == "10s" {
    fail CompileError{ at: e.span, message: "no 10s sleeps in {d.name}" }
  }
}

The API (std/comptime/comptime.oxl) is plain read-only data:

typefields
Declkind, name, parent (enclosing declaration, "" at the top), isPub, span, attrs: List<Attr>, exprs: List<Expr>
Attrname ("timeout", "std.foo"), args: List<AttrArg>, span
AttrArgname ("" when positional), kind (an expression kind), text (source text), span
Exprkind, name, text (source text, ≤ 200 bytes), depth, span — every expression of the body in pre-order
Spanfile, line, col (1-based), offset, length
CompileErrorat: Span, message: str — fail it to report one finding and stop
warn(at, message)report a finding and go on

Declaration kinds: fn const static data field enum variant interface method impl rpc-impl service rpc actor var msg type topic config dynamic cluster provide attr test fixture mock pipeline saga policy dep import assert operator. Expression kinds: int float bool char string bytes duration size date prefixed none unit path implicit-member it self tuple list map struct lambda call method-call field tuple-field computed-field opt-field index unary binary try unwrap cast is block if match for while loop break continue return fail yield respond chain spawn await scope select atomic unsafe blocking comptime emit query. name: the path of a path/call/struct, the method of a method-call, the field of a field, the operator of a binary, the prefix of a prefixed literal.

The comptime interpreter supports, besides the constant expressions: let, assignment, if, while, loop, for x in list [where c], xs[i], xs.len()/isEmpty()/contains(x), the str methods (len, contains, startsWith, endsWith, slice, lower, upper, trim…), calls of other comptime functions, string interpolation, fail, assert, panic.

Findings are diagnostics lint-plugin with the message [<rule>] <message>; the rule's level is [lint] <rule> = "warn" | "error" | "off" (default warn). A plugin that cannot be evaluated (unsupported construct, panic, step limit) is comptime-error at the plugin function, once; a plugin file without a matching function is comptime-error; a missing file is bad-manifest. Plugins run only when the program has no errors.

How secret-leak works (D-3200)#

An intra-function taint analysis over typed bodies.

  • Sources: a call s.reveal() whose receiver has type secret<T>.
  • Carriers: let x = <tainted> and x = <tainted> taint the local x (an assignment of a clean value clears it); +, casts, ?, ! and methods that return str/bytes/String from a tainted receiver (reveal().trim()) stay tainted.
  • Sinks: an interpolation "{x}", .show() of a tainted value, + that builds a string, arguments of print/eprint and of functions of std.obs/std.log or methods of Logger/Log, and respond x.
  • Not a leak: comparing, hashing, passing the plain text to a function that needs it, "{secret}" (prints ***).
  • Limits: it does not follow values through calls, fields or collections; an rpc body returning the value is checked once services are merged (the response sink is respond today).

How time-mix works (D-3201)#

For + - == != < <= > >= <=> whose left operand is a kind of time (Time, Instant, Date, LocalDateTime, ZonedTime), the right operand is inferred first; a different kind of time is time-mix (instead of a generic type-mismatch), and so is Time ± Period (a Time has no calendar). Date/ZonedTime with Period is calendar arithmetic and is allowed.

Исходник: docs/analyzer.md

OxL 1.0.0-beta · Версия документации 1.0.0-beta© OxwaldLink · сгенерировано из Стандарта, исходников std и руководства