| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 90f1db9 commit 1144bf7
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -388,8 +388,57 @@ class LintVisitor : AstVisitor { | |||
| 388 | 388 | validate_argument(arg, "LINT013", "block argument") | |
| 389 | 389 | } | |
| 390 | 390 | ||
| 391 | + // --- LINT017: int64/uint64 cast of a call that has a long_ counterpart --- | ||
| 392 | + | ||
| 393 | + def lint017_long_twin(fname : string; icall : ExprCall?) : string { | ||
| 394 | + // Receiver-gated, so a same-named user overload or the fixed-array `length` generic in daslib/builtin.das (which has no long_ twin) stays silent. | ||
| 395 | + let nargs = length(icall.arguments) | ||
| 396 | + if (nargs == 0 || icall.arguments[0]._type == null) return "" | ||
| 397 | + let bt = icall.arguments[0]._type.baseType | ||
| 398 | + if (fname == "length" && (bt == Type.tArray || bt == Type.tTable || bt == Type.tString)) return "long_length" | ||
| 399 | + if (fname == "capacity" && (bt == Type.tArray || bt == Type.tTable)) return "long_capacity" | ||
| 400 | + if (fname == "count" && (bt == Type.tArray || bt == Type.tIterator)) return "long_count" | ||
| 401 | + if (fname == "find_index" && bt == Type.tArray && nargs == 2) return "long_find_index" | ||
| 402 | + if ((fname == "fread" || fname == "fwrite") && nargs == 2) return "long_{fname}" | ||
| 403 | + return "" | ||
| 404 | + } | ||
| 405 | + | ||
| 406 | + def check_lint017_long_cast(expr : ExprCall?) : void { | ||
| 407 | + if (noLint || genericDepth > 0 | ||
| 408 | + || (expr.name != "int64" && expr.name != "uint64") | ||
| 409 | + || length(expr.arguments) != 1) return | ||
| 410 | + let inner = expr.arguments[0] | ||
| 411 | + if (!(inner is ExprCall)) return | ||
| 412 | + let icall = inner as ExprCall | ||
| 413 | + let iname = string(icall.func != null && icall.func.fromGeneric != null | ||
| 414 | + ? icall.func.fromGeneric.name | ||
| 415 | + : icall.name) | ||
| 416 | + let twin = lint017_long_twin(iname, icall) | ||
| 417 | + if (empty(twin)) return | ||
| 418 | + lint_error("LINT017: {expr.name}({iname}(...)) computes a 32-bit result and widens it, so it still wraps past 2^31; call {twin}(...) instead", expr.at) | ||
| 419 | + } | ||
| 420 | + | ||
| 421 | + // --- LINT018: narrowing the size argument of memcpy / memcmp --- | ||
| 422 | + | ||
| 423 | + def check_lint018_mem_size_narrowing(expr : ExprCall?) : void { | ||
| 424 | + if (noLint || genericDepth > 0 | ||
| 425 | + || (expr.name != "memcpy" && expr.name != "memcmp") | ||
| 426 | + || length(expr.arguments) != 3) return | ||
| 427 | + let size = expr.arguments[2] | ||
| 428 | + if (!(size is ExprCall)) return | ||
| 429 | + let scall = size as ExprCall | ||
| 430 | + if (scall.name != "int" || length(scall.arguments) != 1) return | ||
| 431 | + let src = scall.arguments[0]._type | ||
| 432 | + if (src == null) return | ||
| 433 | + let bt = src.baseType | ||
| 434 | + if (bt != Type.tInt64 && bt != Type.tUInt64 && bt != Type.tUInt) return | ||
| 435 | + lint_error("LINT018: int(...) truncates the {expr.name} size above 2^31; {expr.name} has uint/int64/uint64 overloads, so drop the cast", expr.at) | ||
| 436 | + } | ||
| 437 | + | ||
| 391 | 438 | def override preVisitExprCall(expr : ExprCall?) : void { | |
| 392 | 439 | check_string_push_clone(expr) | |
| 440 | + check_lint017_long_cast(expr) | ||
| 441 | + check_lint018_mem_size_narrowing(expr) | ||
| 393 | 442 | } | |
| 394 | 443 | ||
| 395 | 444 | // `assume name = <block>` keeps a template block at the alias-definition site; every use of | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,55 @@ | |||
| 1 | + options gen2 | ||
| 2 | + options auto_inline_functions = false // lint fixtures assert SOURCE shapes; splices rewrite them | ||
| 3 | + // LINT017: int64/uint64 cast of a call that has a long_ counterpart | ||
| 4 | + // | ||
| 5 | + // Problem: int64(length(x)) computes the 32-bit length FIRST and only then widens | ||
| 6 | + // it, so it still wraps past 2^31 — the cast buys nothing. long_length(x) is | ||
| 7 | + // 64-bit the whole way through. | ||
| 8 | + // | ||
| 9 | + // Bad pattern: | ||
| 10 | + // let n = int64(length(arr)) | ||
| 11 | + // | ||
| 12 | + // Good pattern: | ||
| 13 | + // let n = long_length(arr) | ||
| 14 | + | ||
| 15 | + require daslib/linq | ||
| 16 | + | ||
| 17 | + // bad: int64 of array length (1 warning) | ||
| 18 | + def bad_int64_length(arr : array<int>) : int64 { | ||
| 19 | + return int64(length(arr)) | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + // bad: uint64 of array capacity (1 warning) | ||
| 23 | + def bad_uint64_capacity(arr : array<int>) : uint64 { | ||
| 24 | + return uint64(capacity(arr)) | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + // bad: int64 of string length (1 warning) | ||
| 28 | + def bad_int64_string_length(s : string) : int64 { | ||
| 29 | + return int64(length(s)) | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + // bad: int64 of table length (1 warning) | ||
| 33 | + def bad_int64_table_length(t : table<string; int>) : int64 { | ||
| 34 | + return int64(length(t)) | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + // bad: int64 of linq count (1 warning) | ||
| 38 | + def bad_int64_count(arr : array<int>) : int64 { | ||
| 39 | + return int64(count(arr)) | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + // good: 64-bit all the way through | ||
| 43 | + def good_long_length(arr : array<int>) : int64 { | ||
| 44 | + return long_length(arr) | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + // good: a fixed array has no long_length twin, so the cast is the only spelling | ||
| 48 | + def good_fixed_array(fa : int[4]) : int64 { | ||
| 49 | + return int64(length(fa)) | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + // good: widening an ordinary int is not a truncation | ||
| 53 | + def good_plain_widen(n : int) : int64 { | ||
| 54 | + return int64(n) | ||
| 55 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,38 @@ | |||
| 1 | + options gen2 | ||
| 2 | + options auto_inline_functions = false // lint fixtures assert SOURCE shapes; splices rewrite them | ||
| 3 | + // LINT018: narrowing the size argument of memcpy / memcmp | ||
| 4 | + // | ||
| 5 | + // Problem: int(n) truncates above 2^31, so the copy or compare silently covers | ||
| 6 | + // the wrong number of bytes. memcpy and memcmp both carry uint / int64 / uint64 | ||
| 7 | + // size overloads, so the size can be passed straight through. | ||
| 8 | + // | ||
| 9 | + // Bad pattern: | ||
| 10 | + // memcpy(dst, src, int(nbytes)) // nbytes : int64 | ||
| 11 | + // | ||
| 12 | + // Good pattern: | ||
| 13 | + // memcpy(dst, src, nbytes) | ||
| 14 | + | ||
| 15 | + // bad: int() narrowing of an int64 size (1 warning) | ||
| 16 | + def bad_memcpy_int64(dst, src : void?; nbytes : int64) { | ||
| 17 | + unsafe(memcpy(dst, src, int(nbytes))) | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + // bad: int() narrowing of a uint64 size (1 warning) | ||
| 21 | + def bad_memcpy_uint64(dst, src : void?; nbytes : uint64) { | ||
| 22 | + unsafe(memcpy(dst, src, int(nbytes))) | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + // bad: int() narrowing on memcmp (1 warning) | ||
| 26 | + def bad_memcmp_int64(dst, src : void?; nbytes : int64) : int { | ||
| 27 | + return unsafe(memcmp(dst, src, int(nbytes))) | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + // good: the 64-bit size goes straight to the 64-bit overload | ||
| 31 | + def good_memcpy_int64(dst, src : void?; nbytes : int64) { | ||
| 32 | + unsafe(memcpy(dst, src, nbytes)) | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + // good: an int-typed size needs no cast at all | ||
| 36 | + def good_memcpy_int(dst, src : void?; nbytes : int) { | ||
| 37 | + unsafe(memcpy(dst, src, nbytes)) | ||
| 38 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments