///|
pub(all) struct ScriptAudit {
warnings : Array[Diagnostic]
commands : Int
predicates : Int
max_depth : Int
literal_redirects : Array[String]
literal_mailboxes : Array[String]
} derive(Eq, Debug)
///|
priv struct Auditor {
warnings : Array[Diagnostic]
mut commands : Int
mut predicates : Int
mut max_depth : Int
redirects : Array[String]
mailboxes : Array[String]
}
///|
fn constant_condition(expr : Test) -> Bool? {
match expr {
Call("true", _, _) => Some(true)
Call("false", _, _) => Some(false)
Call(_, _, _) => None
Not(child, _) => constant_condition(child).map(value => !value)
AnyOf(children, _) => {
let mut unknown = false
for child in children {
match constant_condition(child) {
Some(true) => return Some(true)
None => unknown = true
_ => ()
}
}
if unknown {
None
} else {
Some(false)
}
}
AllOf(children, _) => {
let mut unknown = false
for child in children {
match constant_condition(child) {
Some(false) => return Some(false)
None => unknown = true
_ => ()
}
}
if unknown {
None
} else {
Some(true)
}
}
}
}
///|
fn test_span(expr : Test) -> Span {
match expr {
Call(_, _, span) | Not(_, span) | AnyOf(_, span) | AllOf(_, span) => span
}
}
///|
fn Auditor::warn(
self : Auditor,
code : String,
message : String,
span : Span,
) -> Unit {
self.warnings.push({ code, message, span })
}
///|
fn Auditor::condition(self : Auditor, expr : Test, depth : Int) -> Unit {
self.predicates += 1
if depth > self.max_depth {
self.max_depth = depth
}
match expr {
Not(child, _) => self.condition(child, depth + 1)
AnyOf(children, _) | AllOf(children, _) =>
for child in children {
self.condition(child, depth + 1)
}
Call(name, args, span) =>
if name == "header" || name == "address" || name == "envelope" {
let mut wildcard = false
for arg in args {
match arg {
Flag("matches") => wildcard = true
_ => ()
}
}
if wildcard {
match args.last() {
Some(Literal("*")) | Some(Strings(["*"])) =>
self.warn(
"lint.broad_match", "通配符 * 会匹配该字段的任意值;确认是否只想检查存在性。",
span,
)
_ => ()
}
}
}
}
}
///|
fn Auditor::body(
self : Auditor,
body : Array[Statement],
depth : Int,
guarded : Bool,
initially_reachable : Bool,
) -> Bool {
if depth > self.max_depth {
self.max_depth = depth
}
let mut reachable = initially_reachable
let mut stopped = false
for statement in body {
match statement {
Command(name, args, span) => {
self.commands += 1
if !reachable {
self.warn(
"lint.unreachable", "此语句位于已终止或恒假路径中。",
span,
)
}
if name == "stop" {
reachable = false
stopped = true
}
if reachable && !guarded && name == "discard" {
self.warn(
"lint.unconditional_discard", "此路径无邮件条件保护地取消默认保留;部署前应回放样本。",
span,
)
}
if name == "redirect" || name == "fileinto" {
match args.last() {
Some(Literal(target)) if !target.contains("${") => {
let collection = if name == "redirect" {
self.redirects
} else {
self.mailboxes
}
if !collection.contains(target) {
collection.push(target)
}
}
_ => ()
}
if name == "redirect" && reachable && !guarded {
self.warn(
"lint.unconditional_redirect", "转发动作没有邮件条件保护;请确认目的地和循环转发策略。",
span,
)
}
}
}
Branch(branches, otherwise, _) => {
let mut remaining = reachable
let mut all_stop = true
for branch in branches {
self.condition(branch.condition, depth + 1)
let constant = constant_condition(branch.condition)
if constant == Some(false) {
self.warn(
"lint.constant_false",
"此条件恒为假。",
test_span(branch.condition),
)
}
let possible = remaining && constant != Some(false)
let branch_stops = self.body(
branch.body,
depth + 1,
guarded || constant != Some(true),
possible,
)
if possible && !branch_stops {
all_stop = false
}
if constant == Some(true) {
remaining = false
}
}
let else_stops = self.body(otherwise, depth + 1, true, remaining)
if remaining && !else_stops {
all_stop = false
}
if reachable && all_stop {
reachable = false
stopped = true
}
}
}
}
stopped
}
///|
pub fn Program::audit(self : Program) -> ScriptAudit {
let state : Auditor = {
warnings: [],
commands: 0,
predicates: 0,
max_depth: 0,
redirects: [],
mailboxes: [],
}
ignore(state.body(self.syntax.statements, 0, false, true))
{
warnings: state.warnings,
commands: state.commands,
predicates: state.predicates,
max_depth: state.max_depth,
literal_redirects: state.redirects,
literal_mailboxes: state.mailboxes,
}
}