From 652603a3ce93fc21f7cb5ea461dc16bbae490aa8 Mon Sep 17 00:00:00 2001 From: Steve C Date: Tue, 16 Jan 2024 02:27:28 -0500 Subject: [PATCH 1/5] [`pylint`] - add `potential-index-error` rule (`PLE0643`) --- .../fixtures/pylint/potential_index_error.py | 11 +++ .../src/checkers/ast/analyze/expression.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/pylint/mod.rs | 1 + .../ruff_linter/src/rules/pylint/rules/mod.rs | 2 + .../pylint/rules/potential_index_error.rs | 77 +++++++++++++++++++ ...sts__PLE0643_potential_index_error.py.snap | 40 ++++++++++ ruff.schema.json | 2 + 8 files changed, 137 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py create mode 100644 crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py b/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py new file mode 100644 index 0000000000000..c4a6b193092a3 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py @@ -0,0 +1,11 @@ +print([1, 2, 3][3]) # PLE0643 +print([1, 2, 3][-4]) # PLE0643 +print([1, 2, 3][2147483647]) # PLE0643 +print([1, 2, 3][-2147483647]) # PLE0643 + +print([1, 2, 3][2]) # OK +print([1, 2, 3][0]) # OK +print([1, 2, 3][-3]) # OK +print([1, 2, 3][3:]) # OK +print([1, 2, 3][2147483648]) # OK (i32 overflow, ignored) +print([1, 2, 3][-2147483648]) # OK (i32 overflow, ignored) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index f8214c6c8924d..64e3634982114 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -125,6 +125,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::SliceCopy) { refurb::rules::slice_copy(checker, subscript); } + if checker.enabled(Rule::PotentialIndexError) { + pylint::rules::potential_index_error(checker, value, slice); + } pandas_vet::rules::subscript(checker, value, expr); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 4975d6992aab6..f8a30866ee2b3 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -229,6 +229,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "E0307") => (RuleGroup::Stable, rules::pylint::rules::InvalidStrReturnType), (Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject), (Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat), + (Pylint, "E0643") => (RuleGroup::Preview, rules::pylint::rules::PotentialIndexError), (Pylint, "E0704") => (RuleGroup::Preview, rules::pylint::rules::MisplacedBareRaise), (Pylint, "E1132") => (RuleGroup::Preview, rules::pylint::rules::RepeatedKeywordArgument), (Pylint, "E1142") => (RuleGroup::Stable, rules::pylint::rules::AwaitOutsideAsync), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 9182956716f34..6616ac26b2ed2 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -167,6 +167,7 @@ mod tests { #[test_case(Rule::NoClassmethodDecorator, Path::new("no_method_decorator.py"))] #[test_case(Rule::UnnecessaryDunderCall, Path::new("unnecessary_dunder_call.py"))] #[test_case(Rule::NoStaticmethodDecorator, Path::new("no_method_decorator.py"))] + #[test_case(Rule::PotentialIndexError, Path::new("potential_index_error.py"))] #[test_case(Rule::SuperWithoutBrackets, Path::new("super_without_brackets.py"))] #[test_case( Rule::UnnecessaryDictIndexLookup, diff --git a/crates/ruff_linter/src/rules/pylint/rules/mod.rs b/crates/ruff_linter/src/rules/pylint/rules/mod.rs index ba5f2916dca13..22be5657bd22d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/mod.rs @@ -42,6 +42,7 @@ pub(crate) use no_self_use::*; pub(crate) use non_ascii_module_import::*; pub(crate) use non_ascii_name::*; pub(crate) use nonlocal_without_binding::*; +pub(crate) use potential_index_error::*; pub(crate) use property_with_parameters::*; pub(crate) use redefined_argument_from_local::*; pub(crate) use redefined_loop_name::*; @@ -124,6 +125,7 @@ mod no_self_use; mod non_ascii_module_import; mod non_ascii_name; mod nonlocal_without_binding; +mod potential_index_error; mod property_with_parameters; mod redefined_argument_from_local; mod redefined_loop_name; diff --git a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs new file mode 100644 index 0000000000000..7b20c498e6722 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs @@ -0,0 +1,77 @@ +use ruff_python_ast::{self as ast, Expr}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_text_size::TextRange; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for potential hard-coded IndexErrors, which occurs when accessing +/// a list or tuple with an index that is known to be out of bounds. +/// +/// ## Why is this bad? +/// This will cause a runtime error. +/// +/// ## Example +/// ```python +/// print([1, 2, 3][123]) +/// ``` +/// +#[violation] +pub struct PotentialIndexError; + +impl Violation for PotentialIndexError { + #[derive_message_formats] + fn message(&self) -> String { + format!("Potential IndexError") + } +} + +/// PLE0643 +pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: &Expr) { + let length = match value { + Expr::Tuple(ast::ExprTuple { elts, .. }) | Expr::List(ast::ExprList { elts, .. }) => { + i32::try_from(elts.len()) + } + _ => { + return; + } + }; + + let Ok(length) = length else { + return; + }; + + let (number_value, range) = match slice { + Expr::NumberLiteral(ast::ExprNumberLiteral { + value: ast::Number::Int(number_value), + range, + }) => (number_value.as_i32(), *range), + Expr::UnaryOp(ast::ExprUnaryOp { + op: ast::UnaryOp::USub, + operand, + range, + }) => match operand.as_ref() { + Expr::NumberLiteral(ast::ExprNumberLiteral { + value: ast::Number::Int(number_value), + .. + }) => match number_value.as_i32() { + Some(value) => (Some(-value), *range), + None => (None, TextRange::default()), + }, + _ => (None, TextRange::default()), + }, + _ => (None, TextRange::default()), + }; + + let Some(number_value) = number_value else { + return; + }; + + if number_value >= length || number_value < -length { + checker + .diagnostics + .push(Diagnostic::new(PotentialIndexError, range)); + } +} diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap new file mode 100644 index 0000000000000..8c0564d15a5ea --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap @@ -0,0 +1,40 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- +potential_index_error.py:1:17: PLE0643 Potential IndexError + | +1 | print([1, 2, 3][3]) # PLE0643 + | ^ PLE0643 +2 | print([1, 2, 3][-4]) # PLE0643 +3 | print([1, 2, 3][2147483647]) # PLE0643 + | + +potential_index_error.py:2:17: PLE0643 Potential IndexError + | +1 | print([1, 2, 3][3]) # PLE0643 +2 | print([1, 2, 3][-4]) # PLE0643 + | ^^ PLE0643 +3 | print([1, 2, 3][2147483647]) # PLE0643 +4 | print([1, 2, 3][-2147483647]) # PLE0643 + | + +potential_index_error.py:3:17: PLE0643 Potential IndexError + | +1 | print([1, 2, 3][3]) # PLE0643 +2 | print([1, 2, 3][-4]) # PLE0643 +3 | print([1, 2, 3][2147483647]) # PLE0643 + | ^^^^^^^^^^ PLE0643 +4 | print([1, 2, 3][-2147483647]) # PLE0643 + | + +potential_index_error.py:4:17: PLE0643 Potential IndexError + | +2 | print([1, 2, 3][-4]) # PLE0643 +3 | print([1, 2, 3][2147483647]) # PLE0643 +4 | print([1, 2, 3][-2147483647]) # PLE0643 + | ^^^^^^^^^^^ PLE0643 +5 | +6 | print([1, 2, 3][2]) # OK + | + + diff --git a/ruff.schema.json b/ruff.schema.json index 9289747f76f4c..78e801d2790ee 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3129,6 +3129,8 @@ "PLE060", "PLE0604", "PLE0605", + "PLE064", + "PLE0643", "PLE07", "PLE070", "PLE0704", From 6ba0c8536866225c3d19c78df3bd239c1c7a30be Mon Sep 17 00:00:00 2001 From: Steve C Date: Thu, 18 Jan 2024 23:53:27 -0500 Subject: [PATCH 2/5] tweak and add i64 capability --- .../fixtures/pylint/potential_index_error.py | 8 ++--- .../pylint/rules/potential_index_error.rs | 29 +++++++++---------- ...sts__PLE0643_potential_index_error.py.snap | 24 +++++---------- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py b/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py index c4a6b193092a3..a80dc7a6be878 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py @@ -1,11 +1,11 @@ print([1, 2, 3][3]) # PLE0643 print([1, 2, 3][-4]) # PLE0643 -print([1, 2, 3][2147483647]) # PLE0643 -print([1, 2, 3][-2147483647]) # PLE0643 +print([1, 2, 3][9223372036854775807]) # PLE0643 +print([1, 2, 3][-9223372036854775807]) # PLE0643 print([1, 2, 3][2]) # OK print([1, 2, 3][0]) # OK print([1, 2, 3][-3]) # OK print([1, 2, 3][3:]) # OK -print([1, 2, 3][2147483648]) # OK (i32 overflow, ignored) -print([1, 2, 3][-2147483648]) # OK (i32 overflow, ignored) +print([1, 2, 3][-9223372036854775808]) # OK (i64 overflow, ignored) +print([1, 2, 3][9223372036854775808]) # OK (i64 overflow, ignored) diff --git a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs index 7b20c498e6722..769e63dacaac7 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs @@ -2,7 +2,6 @@ use ruff_python_ast::{self as ast, Expr}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_text_size::TextRange; use crate::checkers::ast::Checker; @@ -32,22 +31,24 @@ impl Violation for PotentialIndexError { pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: &Expr) { let length = match value { Expr::Tuple(ast::ExprTuple { elts, .. }) | Expr::List(ast::ExprList { elts, .. }) => { - i32::try_from(elts.len()) + match i64::try_from(elts.len()) { + Ok(length) => length, + Err(_) => return, + } } _ => { return; } }; - let Ok(length) = length else { - return; - }; - let (number_value, range) = match slice { Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(number_value), range, - }) => (number_value.as_i32(), *range), + }) => match number_value.as_i64() { + Some(value) => (-value, *range), + None => return, + }, Expr::UnaryOp(ast::ExprUnaryOp { op: ast::UnaryOp::USub, operand, @@ -56,17 +57,13 @@ pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(number_value), .. - }) => match number_value.as_i32() { - Some(value) => (Some(-value), *range), - None => (None, TextRange::default()), + }) => match number_value.as_i64() { + Some(value) => (-value, *range), + None => return, }, - _ => (None, TextRange::default()), + _ => return, }, - _ => (None, TextRange::default()), - }; - - let Some(number_value) = number_value else { - return; + _ => return, }; if number_value >= length || number_value < -length { diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap index 8c0564d15a5ea..81229a2cd1a24 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap @@ -1,38 +1,30 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -potential_index_error.py:1:17: PLE0643 Potential IndexError - | -1 | print([1, 2, 3][3]) # PLE0643 - | ^ PLE0643 -2 | print([1, 2, 3][-4]) # PLE0643 -3 | print([1, 2, 3][2147483647]) # PLE0643 - | - potential_index_error.py:2:17: PLE0643 Potential IndexError | 1 | print([1, 2, 3][3]) # PLE0643 2 | print([1, 2, 3][-4]) # PLE0643 | ^^ PLE0643 -3 | print([1, 2, 3][2147483647]) # PLE0643 -4 | print([1, 2, 3][-2147483647]) # PLE0643 +3 | print([1, 2, 3][9223372036854775807]) # PLE0643 +4 | print([1, 2, 3][-9223372036854775807]) # PLE0643 | potential_index_error.py:3:17: PLE0643 Potential IndexError | 1 | print([1, 2, 3][3]) # PLE0643 2 | print([1, 2, 3][-4]) # PLE0643 -3 | print([1, 2, 3][2147483647]) # PLE0643 - | ^^^^^^^^^^ PLE0643 -4 | print([1, 2, 3][-2147483647]) # PLE0643 +3 | print([1, 2, 3][9223372036854775807]) # PLE0643 + | ^^^^^^^^^^^^^^^^^^^ PLE0643 +4 | print([1, 2, 3][-9223372036854775807]) # PLE0643 | potential_index_error.py:4:17: PLE0643 Potential IndexError | 2 | print([1, 2, 3][-4]) # PLE0643 -3 | print([1, 2, 3][2147483647]) # PLE0643 -4 | print([1, 2, 3][-2147483647]) # PLE0643 - | ^^^^^^^^^^^ PLE0643 +3 | print([1, 2, 3][9223372036854775807]) # PLE0643 +4 | print([1, 2, 3][-9223372036854775807]) # PLE0643 + | ^^^^^^^^^^^^^^^^^^^^ PLE0643 5 | 6 | print([1, 2, 3][2]) # OK | From e23bb9d0cee7fefe2d45b9cf91bf4e3a730045e3 Mon Sep 17 00:00:00 2001 From: Steve C Date: Sat, 20 Jan 2024 19:36:24 -0500 Subject: [PATCH 3/5] fix positive integer --- .../src/rules/pylint/rules/potential_index_error.rs | 2 +- ...__pylint__tests__PLE0643_potential_index_error.py.snap | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs index 769e63dacaac7..c4b71ab044a28 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs @@ -46,7 +46,7 @@ pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: value: ast::Number::Int(number_value), range, }) => match number_value.as_i64() { - Some(value) => (-value, *range), + Some(value) => (value, *range), None => return, }, Expr::UnaryOp(ast::ExprUnaryOp { diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap index 81229a2cd1a24..ce3b518f4e4a4 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap @@ -1,6 +1,14 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- +potential_index_error.py:1:17: PLE0643 Potential IndexError + | +1 | print([1, 2, 3][3]) # PLE0643 + | ^ PLE0643 +2 | print([1, 2, 3][-4]) # PLE0643 +3 | print([1, 2, 3][9223372036854775807]) # PLE0643 + | + potential_index_error.py:2:17: PLE0643 Potential IndexError | 1 | print([1, 2, 3][3]) # PLE0643 From 926364497ef373d179a2f71e781c51a8d6a0c568 Mon Sep 17 00:00:00 2001 From: Steve C Date: Sat, 20 Jan 2024 20:06:51 -0500 Subject: [PATCH 4/5] tweak logic --- .../fixtures/pylint/potential_index_error.py | 6 ++--- .../pylint/rules/potential_index_error.rs | 24 ++++++++++++------- ...sts__PLE0643_potential_index_error.py.snap | 18 +++++++------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py b/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py index a80dc7a6be878..7f1a0b47264d5 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py @@ -1,11 +1,9 @@ print([1, 2, 3][3]) # PLE0643 print([1, 2, 3][-4]) # PLE0643 -print([1, 2, 3][9223372036854775807]) # PLE0643 -print([1, 2, 3][-9223372036854775807]) # PLE0643 +print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643 +print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643 print([1, 2, 3][2]) # OK print([1, 2, 3][0]) # OK print([1, 2, 3][-3]) # OK print([1, 2, 3][3:]) # OK -print([1, 2, 3][-9223372036854775808]) # OK (i64 overflow, ignored) -print([1, 2, 3][9223372036854775808]) # OK (i64 overflow, ignored) diff --git a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs index c4b71ab044a28..0c8e52c338f8b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use ruff_python_ast::{self as ast, Expr}; use ruff_diagnostics::{Diagnostic, Violation}; @@ -45,10 +47,7 @@ pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(number_value), range, - }) => match number_value.as_i64() { - Some(value) => (value, *range), - None => return, - }, + }) => (number_value.to_owned(), *range), Expr::UnaryOp(ast::ExprUnaryOp { op: ast::UnaryOp::USub, operand, @@ -57,16 +56,23 @@ pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(number_value), .. - }) => match number_value.as_i64() { - Some(value) => (-value, *range), - None => return, - }, + }) => ( + ast::Int::from_str(&format!("-{number_value}")).unwrap(), + *range, + ), _ => return, }, _ => return, }; - if number_value >= length || number_value < -length { + let emit = if let Some(number) = number_value.as_i64() { + number >= length || number < -length + } else { + // this should be impossible + true + }; + + if emit { checker .diagnostics .push(Diagnostic::new(PotentialIndexError, range)); diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap index ce3b518f4e4a4..2cfa565dc5eb8 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE0643_potential_index_error.py.snap @@ -6,7 +6,7 @@ potential_index_error.py:1:17: PLE0643 Potential IndexError 1 | print([1, 2, 3][3]) # PLE0643 | ^ PLE0643 2 | print([1, 2, 3][-4]) # PLE0643 -3 | print([1, 2, 3][9223372036854775807]) # PLE0643 +3 | print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643 | potential_index_error.py:2:17: PLE0643 Potential IndexError @@ -14,25 +14,25 @@ potential_index_error.py:2:17: PLE0643 Potential IndexError 1 | print([1, 2, 3][3]) # PLE0643 2 | print([1, 2, 3][-4]) # PLE0643 | ^^ PLE0643 -3 | print([1, 2, 3][9223372036854775807]) # PLE0643 -4 | print([1, 2, 3][-9223372036854775807]) # PLE0643 +3 | print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643 +4 | print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643 | potential_index_error.py:3:17: PLE0643 Potential IndexError | 1 | print([1, 2, 3][3]) # PLE0643 2 | print([1, 2, 3][-4]) # PLE0643 -3 | print([1, 2, 3][9223372036854775807]) # PLE0643 - | ^^^^^^^^^^^^^^^^^^^ PLE0643 -4 | print([1, 2, 3][-9223372036854775807]) # PLE0643 +3 | print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLE0643 +4 | print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643 | potential_index_error.py:4:17: PLE0643 Potential IndexError | 2 | print([1, 2, 3][-4]) # PLE0643 -3 | print([1, 2, 3][9223372036854775807]) # PLE0643 -4 | print([1, 2, 3][-9223372036854775807]) # PLE0643 - | ^^^^^^^^^^^^^^^^^^^^ PLE0643 +3 | print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643 +4 | print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLE0643 5 | 6 | print([1, 2, 3][2]) # OK | From 2fedb6df87d7f28940b27801bc3d1f2321766983 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 20 Jan 2024 22:54:48 -0500 Subject: [PATCH 5/5] Small tweaks --- .../pylint/rules/potential_index_error.rs | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs index 0c8e52c338f8b..0e4048c20e3fb 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs @@ -1,24 +1,23 @@ -use std::str::FromStr; - -use ruff_python_ast::{self as ast, Expr}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr}; +use ruff_text_size::Ranged; use crate::checkers::ast::Checker; /// ## What it does -/// Checks for potential hard-coded IndexErrors, which occurs when accessing -/// a list or tuple with an index that is known to be out of bounds. +/// Checks for hard-coded sequence accesses that are known to be out of bounds. /// /// ## Why is this bad? -/// This will cause a runtime error. +/// Attempting to access a sequence with an out-of-bounds index will cause an +/// `IndexError` to be raised at runtime. When the sequence and index are +/// defined statically (e.g., subscripts on `list` and `tuple` literals, with +/// integer indexes), such errors can be detected ahead of time. /// /// ## Example /// ```python -/// print([1, 2, 3][123]) +/// print([0, 1, 2][3]) /// ``` -/// #[violation] pub struct PotentialIndexError; @@ -31,6 +30,7 @@ impl Violation for PotentialIndexError { /// PLE0643 pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: &Expr) { + // Determine the length of the sequence. let length = match value { Expr::Tuple(ast::ExprTuple { elts, .. }) | Expr::List(ast::ExprList { elts, .. }) => { match i64::try_from(elts.len()) { @@ -43,38 +43,31 @@ pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: } }; - let (number_value, range) = match slice { + // Determine the index value. + let index = match slice { Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(number_value), - range, - }) => (number_value.to_owned(), *range), + .. + }) => number_value.as_i64(), Expr::UnaryOp(ast::ExprUnaryOp { op: ast::UnaryOp::USub, operand, - range, + .. }) => match operand.as_ref() { Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(number_value), .. - }) => ( - ast::Int::from_str(&format!("-{number_value}")).unwrap(), - *range, - ), + }) => number_value.as_i64().map(|number| -number), _ => return, }, _ => return, }; - let emit = if let Some(number) = number_value.as_i64() { - number >= length || number < -length - } else { - // this should be impossible - true - }; - - if emit { + // Emit a diagnostic if the index is out of bounds. If the index can't be represented as an + // `i64`, but the length _can_, then the index is definitely out of bounds. + if index.map_or(true, |index| index >= length || index < -length) { checker .diagnostics - .push(Diagnostic::new(PotentialIndexError, range)); + .push(Diagnostic::new(PotentialIndexError, slice.range())); } }