Skip to content

Commit c480594

Browse files
author
Z User
committed
Merge PR #3759: hy-check(const_overflow) untyped overflow in any slots, fmt/print/panic/any() calls and some() (extends #3625, fixes #3624)
CREDIT: @Burapa-J Verification (ALL-GREEN, battery on trial /tmp/verify-wt @133a5b46): - merge gate: clean vs d15137b - cargo test --workspace: 450 suites ok, 2916 tests, 0 failed (const_overflow_any_slots_3624: 5/5) - corpus harness: 135/135 verdict agreement, tripwire 1.5%/2.2% < 25% (not fired) - differential FULL: accept 45/45, reject 90/90, FULL AGREEMENT some()/option.some() inner-element checks, any/interface{} default-float overflow, precise has_explicit_fixed_int_ty gating with lexical-binder shadow defense, curated fmt/log/print/panic/any() argument overflow with same-span HYB-E6002 dedup.
2 parents d15137b + 133a5b4 commit c480594

2 files changed

Lines changed: 235 additions & 13 deletions

File tree

‎compiler/crates/hy-check/src/const_overflow.rs‎

Lines changed: 169 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ impl<'a> Checker<'a> {
3030
st: Option<&FnState>,
3131
) {
3232
let resolved = self.resolve_type_alias(ty);
33+
if let TypeExpr::Option { elem, .. } = &resolved {
34+
if let Some(inner) = self.unwrap_some_call_arg(value, st) {
35+
self.check_const_overflow_init(name, elem, inner, st);
36+
}
37+
return;
38+
}
3339
let TypeExpr::Named {
3440
name: ty_name,
3541
span: ty_span,
@@ -39,6 +45,7 @@ impl<'a> Checker<'a> {
3945
};
4046
if ty_name == "any" || ty_name == "interface{}" {
4147
self.check_default_int_overflow(name, value, st);
48+
self.check_default_float_overflow(name, value, st);
4249
return;
4350
}
4451
if let Some(is_f32) = is_float_type(ty_name) {
@@ -187,6 +194,12 @@ impl<'a> Checker<'a> {
187194
st: Option<&FnState>,
188195
) {
189196
let resolved = self.resolve_type_alias(ty);
197+
if let TypeExpr::Option { elem, .. } = &resolved {
198+
if let Some(inner) = self.unwrap_some_call_arg(value, st) {
199+
self.check_const_overflow_assign(target, elem, inner, st);
200+
}
201+
return;
202+
}
190203
let TypeExpr::Named {
191204
name: ty_name,
192205
span: ty_span,
@@ -196,6 +209,7 @@ impl<'a> Checker<'a> {
196209
};
197210
if ty_name == "any" || ty_name == "interface{}" {
198211
self.check_default_int_overflow(target, value, st);
212+
self.check_default_float_overflow(target, value, st);
199213
return;
200214
}
201215
if let Some(is_f32) = is_float_type(ty_name) {
@@ -333,10 +347,9 @@ impl<'a> Checker<'a> {
333347
value: &Expr,
334348
st: Option<&FnState>,
335349
) {
336-
if let Some(st) = st {
337-
if self.fixed_width_int_ty_of(st, value).is_some() {
338-
return;
339-
}
350+
let value = self.unwrap_some_call_arg(value, st).unwrap_or(value);
351+
if self.has_explicit_fixed_int_ty(st, value) {
352+
return;
340353
}
341354
let (lo, hi) = target_int_range();
342355
match self.const_int_fold_expr(value, st) {
@@ -346,6 +359,9 @@ impl<'a> Checker<'a> {
346359
}
347360

348361
let span = value.span();
362+
if self.diags.iter().any(|d| d.code == "HYB-E6002" && d.span == span) {
363+
return;
364+
}
349365
let msg = format!(
350366
"constant expression `{}` overflows default type `int` of `{}` (HYB-E6002)",
351367
value.render(),
@@ -372,12 +388,16 @@ impl<'a> Checker<'a> {
372388
value: &Expr,
373389
st: Option<&FnState>,
374390
) {
375-
if !is_float_const_expr(value, &self.const_values, 0, st) {
391+
let value = self.unwrap_some_call_arg(value, st).unwrap_or(value);
392+
if self.expr_has_local_or_binder(value) || !is_float_const_expr(value, &self.const_values, 0, st) {
376393
return;
377394
}
378395
if let Some(f) = self.const_float_fold(value, st) {
379396
if f.is_infinite() {
380397
let span = value.span();
398+
if self.diags.iter().any(|d| d.code == "HYB-E6002" && d.span == span) {
399+
return;
400+
}
381401
let msg = format!(
382402
"constant expression `{}` overflows default type `float64` of `{}` (HYB-E6002)",
383403
value.render(),
@@ -459,7 +479,9 @@ impl<'a> Checker<'a> {
459479
}
460480
return self.const_int_fold_at(init_expr, st, depth + 1);
461481
}
462-
if st.is_some_and(|s| s.env_lookup(name).is_some()) {
482+
if st.is_some_and(|s| s.env_lookup(name).is_some())
483+
|| self.is_lexical_binder_at(name, id_span.start)
484+
{
463485
return ConstFold::NotConst;
464486
}
465487
if let Some(v) = self.const_values.get(name) {
@@ -685,16 +707,13 @@ impl<'a> Checker<'a> {
685707
self.check_expr_type_conversions(hi);
686708
}
687709
}
688-
Expr::Call {
689-
callee,
690-
args,
691-
span,
692-
} => {
710+
Expr::Call { callee, args, span } => {
693711
self.check_expr_type_conversions(callee);
694712
for a in args {
695713
self.check_expr_type_conversions(&a.value);
696714
}
697715
self.check_conversion_call(callee, args, *span);
716+
self.check_any_call_arg_overflow(callee, args);
698717
}
699718
Expr::StructLit { fields, .. } => {
700719
for f in fields {
@@ -758,9 +777,17 @@ impl<'a> Checker<'a> {
758777
return;
759778
}
760779
}
761-
let Expr::Ident { name, .. } = callee else {
780+
let Expr::Ident { name, span: id_span } = callee else {
762781
return;
763782
};
783+
if matches!(self.resolve_type_alias(&TypeExpr::Named { name: name.clone(), span: *id_span }), TypeExpr::Named { name: ref rn, .. } if matches!(rn.as_str(), "any" | "interface{}"))
784+
&& !self.is_shadowed_ident_at(name, id_span.start)
785+
{
786+
let desc = format!("conversion to `{}`", name);
787+
self.check_default_int_overflow(&desc, arg, None);
788+
self.check_default_float_overflow(&desc, arg, None);
789+
return;
790+
}
764791
if !is_conversion_target(name) {
765792
return;
766793
}
@@ -770,6 +797,136 @@ impl<'a> Checker<'a> {
770797
self.check_conv_against_target(name, &c, arg, span);
771798
}
772799

800+
fn unwrap_some_call_arg<'b>(&self, expr: &'b Expr, st: Option<&FnState>) -> Option<&'b Expr> {
801+
let Expr::Call { callee, args, .. } = unwrap_parens(expr) else { return None };
802+
if args.len() != 1 || args[0].spread { return None; }
803+
let is_some = match unwrap_parens(callee) {
804+
Expr::Ident { name, span } if name == "some" => st.map_or(true, |s| s.env_lookup("some").is_none()) && !self.is_shadowed_ident_at("some", span.start),
805+
Expr::Field { base, name, .. } if name == "some" => matches!(unwrap_parens(base), Expr::Ident { name: pkg, span } if pkg == "option" && st.map_or(true, |s| s.env_lookup("option").is_none()) && !self.is_shadowed_ident_at("option", span.start)),
806+
_ => false,
807+
};
808+
is_some.then_some(&args[0].value)
809+
}
810+
811+
fn has_explicit_fixed_int_ty(&self, st: Option<&FnState>, e: &Expr) -> bool {
812+
self.has_explicit_fixed_int_ty_at(st, e, 0)
813+
}
814+
815+
fn has_explicit_fixed_int_ty_at(&self, st: Option<&FnState>, e: &Expr, depth: u32) -> bool {
816+
if depth >= 16 { return false; }
817+
if st.is_some_and(|st| self.fixed_width_int_ty_of(st, e).is_some()) { return true; }
818+
let is_fixed = |ty: &TypeExpr| matches!(self.resolve_type_alias(ty), TypeExpr::Named { name: ref tn, .. } if fixed_width_int_range(tn).is_some());
819+
match unwrap_parens(e) {
820+
Expr::Call { callee, args, .. } if args.len() == 1 && !args[0].spread => {
821+
let Expr::Ident { name, span } = unwrap_parens(callee) else { return false };
822+
!self.is_shadowed_ident_at(name, span.start) && is_fixed(&TypeExpr::Named { name: name.clone(), span: *span })
823+
}
824+
Expr::Ident { name, span } => {
825+
if let Some(sight) = self.resolve_local_binding_at(name, span.start) {
826+
if let Some(ty) = &sight.ty { return is_fixed(ty); }
827+
return sight.constant_initializer().is_some_and(|init| (sight.pos.line, sight.pos.col) != (span.start.line, span.start.col) && self.has_explicit_fixed_int_ty_at(st, init, depth + 1));
828+
}
829+
if st.is_some_and(|s| s.env_lookup(name).is_some()) || self.is_lexical_binder_at(name, span.start) { return false; }
830+
self.file.decls.iter().any(|d| matches!(d, hy_syntax::Decl::Const(c) if c.name == *name && c.ty.as_ref().map_or_else(|| self.has_explicit_fixed_int_ty_at(st, &c.value, depth + 1), is_fixed)))
831+
}
832+
Expr::Unary { op: UnOp::Neg, operand, .. } => self.has_explicit_fixed_int_ty_at(st, operand, depth + 1),
833+
Expr::Binary { op: BinOp::Shl | BinOp::Shr, lhs, .. } => self.has_explicit_fixed_int_ty_at(st, lhs, depth + 1),
834+
Expr::Binary { lhs, rhs, .. } => self.has_explicit_fixed_int_ty_at(st, lhs, depth + 1) || self.has_explicit_fixed_int_ty_at(st, rhs, depth + 1),
835+
_ => false,
836+
}
837+
}
838+
839+
fn is_lexical_binder_at(&self, name: &str, use_pos: hy_diag::Pos) -> bool {
840+
fn has(s: Span, p: hy_diag::Pos) -> bool { (p.line, p.col) >= (s.start.line, s.start.col) && (p.line, p.col) <= (s.end.line, s.end.col) }
841+
fn expr_binds(e: &Expr, name: &str, p: hy_diag::Pos) -> bool {
842+
has(e.span(), p) && match e {
843+
Expr::FnLit { params, results, body, .. } => params.iter().any(|x| x.name == name) || results.iter().any(|r| r.name.as_deref() == Some(name)) || block_binds(body, name, p),
844+
Expr::Binary { lhs, rhs, .. } | Expr::Index { base: lhs, index: rhs, .. } => expr_binds(lhs, name, p) || expr_binds(rhs, name, p),
845+
Expr::Unary { operand: x, .. } | Expr::AddrOf { operand: x, .. } | Expr::Move { operand: x, .. } | Expr::Try { operand: x, .. } | Expr::Paren { inner: x, .. } | Expr::Field { base: x, .. } | Expr::Receive { chan: x, .. } => expr_binds(x, name, p),
846+
Expr::Slice { base, lo, hi, .. } => expr_binds(base, name, p) || lo.as_deref().is_some_and(|x| expr_binds(x, name, p)) || hi.as_deref().is_some_and(|x| expr_binds(x, name, p)),
847+
Expr::Call { callee, args, .. } => expr_binds(callee, name, p) || args.iter().any(|a| expr_binds(&a.value, name, p)),
848+
Expr::StructLit { fields, .. } => fields.iter().any(|f| expr_binds(&f.value, name, p)),
849+
Expr::SliceLit { items, .. } => items.iter().any(|i| expr_binds(i, name, p)),
850+
Expr::MapLit { entries, .. } => entries.iter().any(|(k, v)| expr_binds(k, name, p) || expr_binds(v, name, p)),
851+
Expr::Interp { parts, .. } => parts.iter().any(|pt| matches!(pt, InterpPart::Expr(x) if expr_binds(x, name, p))),
852+
Expr::Ident { .. } | Expr::Lit { .. } => false,
853+
}
854+
}
855+
fn block_binds(b: &hy_syntax::Block, name: &str, p: hy_diag::Pos) -> bool { b.stmts.iter().any(|s| stmt_binds(s, name, p)) }
856+
fn stmt_binds(s: &Stmt, name: &str, p: hy_diag::Pos) -> bool {
857+
match s {
858+
Stmt::Local(l) => l.values.iter().any(|v| expr_binds(v, name, p)),
859+
Stmt::Assign { values, .. } | Stmt::Return { values, .. } => values.iter().any(|v| expr_binds(v, name, p)),
860+
Stmt::Expr { expr: x, .. } | Stmt::Defer { call: x, .. } => expr_binds(x, name, p),
861+
Stmt::Send { chan, value, .. } => expr_binds(chan, name, p) || expr_binds(value, name, p),
862+
Stmt::Task { callee, args, .. } => expr_binds(callee, name, p) || args.iter().any(|a| expr_binds(&a.value, name, p)),
863+
Stmt::If { init, cond, then, otherwise, span } if has(*span, p) => {
864+
init.as_deref().is_some_and(|i| matches!(i, Stmt::Local(l) if l.names.iter().any(|n| n == name)) || stmt_binds(i, name, p)) || expr_binds(cond, name, p) || (has(then.span, p) && block_binds(then, name, p)) || otherwise.as_deref().is_some_and(|o| stmt_binds(o, name, p))
865+
}
866+
Stmt::For { kind, body, span, .. } if has(*span, p) => {
867+
let bound = match kind {
868+
ForKind::Range { var, lo, hi, .. } => expr_binds(lo, name, p) || expr_binds(hi, name, p) || (has(body.span, p) && var == name),
869+
ForKind::Iterate { vars, expr } => expr_binds(expr, name, p) || (has(body.span, p) && vars.iter().any(|v| v == name)),
870+
ForKind::While { cond } => expr_binds(cond, name, p),
871+
ForKind::Infinite => false,
872+
};
873+
bound || (has(body.span, p) && block_binds(body, name, p))
874+
}
875+
Stmt::Match { subject, arms, span } if has(*span, p) => expr_binds(subject, name, p) || arms.iter().any(|a| (has(a.body.span, p) || a.guard.as_ref().is_some_and(|g| has(g.span(), p))) && (matches!(&a.pattern, hy_syntax::MatchPattern::Path { binding: Some(b), .. } if b == name) || a.guard.as_ref().is_some_and(|g| expr_binds(g, name, p)) || block_binds(&a.body, name, p))),
876+
Stmt::Select { arms, span } if has(*span, p) => arms.iter().any(|a| has(a.body.span, p) && (matches!(&a.kind, SelectArmKind::Recv { names, .. } if names.iter().any(|n| n == name)) || block_binds(&a.body, name, p))),
877+
Stmt::Managed { body, span, .. } | Stmt::Block { block: body, span, .. } if has(*span, p) => block_binds(body, name, p),
878+
_ => false,
879+
}
880+
}
881+
self.file.decls.iter().any(|d| matches!(d, hy_syntax::Decl::Fn(f) if has(f.span, use_pos) && (f.receiver.as_ref().is_some_and(|r| r.name == name) || f.params.iter().any(|p| p.name == name) || f.results.iter().any(|r| r.name.as_deref() == Some(name)) || f.body.as_ref().is_some_and(|b| block_binds(b, name, use_pos)))))
882+
}
883+
884+
fn expr_has_local_or_binder(&self, e: &Expr) -> bool {
885+
match unwrap_parens(e) {
886+
Expr::Ident { name, span } => self.resolve_local_binding_at(name, span.start).is_some() || self.is_lexical_binder_at(name, span.start),
887+
Expr::Unary { operand, .. } => self.expr_has_local_or_binder(operand),
888+
Expr::Binary { lhs, rhs, .. } => self.expr_has_local_or_binder(lhs) || self.expr_has_local_or_binder(rhs),
889+
_ => false,
890+
}
891+
}
892+
893+
fn is_shadowed_ident_at(&self, name: &str, use_pos: hy_diag::Pos) -> bool {
894+
self.resolve_local_binding_at(name, use_pos).is_some() || self.is_lexical_binder_at(name, use_pos) || self.fns.contains_key(name) || self.const_values.contains_key(name)
895+
}
896+
897+
fn check_any_call_arg_overflow(&mut self, callee: &Expr, args: &[CallArg]) {
898+
match unwrap_parens(callee) {
899+
Expr::Ident { name, span } if matches!(name.as_str(), "print" | "println" | "panic") && !self.is_shadowed_ident_at(name, span.start) => {
900+
let desc = format!("argument to `{}`", name);
901+
for a in args.iter().filter(|a| !a.spread) {
902+
self.check_default_int_overflow(&desc, &a.value, None);
903+
self.check_default_float_overflow(&desc, &a.value, None);
904+
}
905+
}
906+
Expr::Field { base, name: member, .. } => {
907+
let Expr::Ident { name: pkg, span: pkg_span } = unwrap_parens(base) else { return };
908+
if self.is_shadowed_ident_at(pkg, pkg_span.start) { return; }
909+
let m = member.to_ascii_lowercase();
910+
let skip = match (pkg.as_str(), m.as_str()) {
911+
("fmt", "print" | "println" | "sprint" | "sprintln" | "eprint" | "eprintln") => Some(0),
912+
("fmt", "printf" | "sprintf" | "eprintf" | "errorf" | "fprint" | "fprintln" | "append" | "appendln") => Some(1),
913+
("fmt", "fprintf" | "appendf") => Some(2),
914+
("log", "print" | "println" | "fatal" | "fatalln" | "panic" | "panicln") => Some(0),
915+
("log", "printf" | "fatalf" | "panicf") => Some(1),
916+
_ => None,
917+
};
918+
if let Some(skip) = skip {
919+
let desc = format!("argument to `{}.{}`", pkg, member);
920+
for a in args.iter().skip(skip).filter(|a| !a.spread) {
921+
self.check_default_int_overflow(&desc, &a.value, None);
922+
self.check_default_float_overflow(&desc, &a.value, None);
923+
}
924+
}
925+
}
926+
_ => {}
927+
}
928+
}
929+
773930
fn check_slice_conversion(&mut self, elem: &TypeExpr, arg: &Expr, span: Span) {
774931
let TypeExpr::Named { name: slice_elem, .. } = elem else {
775932
return; // []T with a non-named element + a run-time operand

0 commit comments

Comments
 (0)