[refurb] Guard hashlib imports and mark hashlib-digest-hex fix as safe (FURB181) (#14694)

## Summary

- Check if `hashlib` and `crypt` imports have been seen for `FURB181`
and `S324`
- Mark the fix for `FURB181` as safe: I think it was accidentally marked
as unsafe in the first place. The rule does not support user-defined
classes as the "fix safety" section suggests.
- Removed `hashlib._Hash`, as it's not part of the `hashlib` module.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

Updated the test snapshots
This commit is contained in:
Simon Brugman 2024-12-02 02:24:49 +01:00 committed by GitHub
parent 289a938ae8
commit 48ec3a8add
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 33 additions and 23 deletions

View file

@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::is_const_false;
use ruff_python_ast::{self as ast, Arguments};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@ -64,6 +65,13 @@ impl Violation for HashlibInsecureHashFunction {
/// S324
pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast::ExprCall) {
if !checker
.semantic()
.seen_module(Modules::HASHLIB | Modules::CRYPT)
{
return;
}
if let Some(weak_hash_call) = checker
.semantic()
.resolve_qualified_name(&call.func)

View file

@ -1,6 +1,7 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{Expr, ExprAttribute, ExprCall};
use ruff_python_semantic::Modules;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
@ -27,11 +28,6 @@ use crate::checkers::ast::Checker;
/// hashed = sha512(b"some data").hexdigest()
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as the target of the `.digest()` call
/// could be a user-defined class that implements a `.hex()` method, rather
/// than a hashlib hash object.
///
/// ## References
/// - [Python documentation: `hashlib`](https://docs.python.org/3/library/hashlib.html)
#[derive(ViolationMetadata)]
@ -52,6 +48,10 @@ impl Violation for HashlibDigestHex {
/// FURB181
pub(crate) fn hashlib_digest_hex(checker: &mut Checker, call: &ExprCall) {
if !checker.semantic().seen_module(Modules::HASHLIB) {
return;
}
if !call.arguments.is_empty() {
return;
}
@ -105,14 +105,13 @@ pub(crate) fn hashlib_digest_hex(checker: &mut Checker, call: &ExprCall) {
| "sha3_512"
| "shake_128"
| "shake_256"
| "_Hash"
]
)
})
{
let mut diagnostic = Diagnostic::new(HashlibDigestHex, call.range());
if arguments.is_empty() {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
".hexdigest".to_string(),
TextRange::new(value.end(), call.func.end()),
)));

View file

@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/refurb/mod.rs
snapshot_kind: text
---
FURB181.py:19:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
@ -13,7 +12,7 @@ FURB181.py:19:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
16 16 |
17 17 | # these will match
18 18 |
@ -33,7 +32,7 @@ FURB181.py:20:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
17 17 | # these will match
18 18 |
19 19 | blake2b().digest().hex()
@ -54,7 +53,7 @@ FURB181.py:21:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
18 18 |
19 19 | blake2b().digest().hex()
20 20 | blake2s().digest().hex()
@ -75,7 +74,7 @@ FURB181.py:22:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
19 19 | blake2b().digest().hex()
20 20 | blake2s().digest().hex()
21 21 | md5().digest().hex()
@ -96,7 +95,7 @@ FURB181.py:23:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
20 20 | blake2s().digest().hex()
21 21 | md5().digest().hex()
22 22 | sha1().digest().hex()
@ -117,7 +116,7 @@ FURB181.py:24:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
21 21 | md5().digest().hex()
22 22 | sha1().digest().hex()
23 23 | sha224().digest().hex()
@ -138,7 +137,7 @@ FURB181.py:25:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
22 22 | sha1().digest().hex()
23 23 | sha224().digest().hex()
24 24 | sha256().digest().hex()
@ -159,7 +158,7 @@ FURB181.py:26:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
23 23 | sha224().digest().hex()
24 24 | sha256().digest().hex()
25 25 | sha384().digest().hex()
@ -180,7 +179,7 @@ FURB181.py:27:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
24 24 | sha256().digest().hex()
25 25 | sha384().digest().hex()
26 26 | sha3_224().digest().hex()
@ -201,7 +200,7 @@ FURB181.py:28:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
25 25 | sha384().digest().hex()
26 26 | sha3_224().digest().hex()
27 27 | sha3_256().digest().hex()
@ -222,7 +221,7 @@ FURB181.py:29:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
26 26 | sha3_224().digest().hex()
27 27 | sha3_256().digest().hex()
28 28 | sha3_384().digest().hex()
@ -243,7 +242,7 @@ FURB181.py:30:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
27 27 | sha3_256().digest().hex()
28 28 | sha3_384().digest().hex()
29 29 | sha3_512().digest().hex()
@ -285,7 +284,7 @@ FURB181.py:34:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
31 31 | shake_128().digest(10).hex()
32 32 | shake_256().digest(10).hex()
33 33 |
@ -306,7 +305,7 @@ FURB181.py:36:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
33 33 |
34 34 | hashlib.sha256().digest().hex()
35 35 |
@ -327,7 +326,7 @@ FURB181.py:38:1: FURB181 [*] Use of hashlib's `.digest().hex()`
|
= help: Replace with `.hexdigest()`
Unsafe fix
Safe fix
35 35 |
36 36 | sha256(b"text").digest().hex()
37 37 |

View file

@ -1411,6 +1411,8 @@ impl<'a> SemanticModel<'a> {
"typing_extensions" => self.seen.insert(Modules::TYPING_EXTENSIONS),
"attr" | "attrs" => self.seen.insert(Modules::ATTRS),
"airflow" => self.seen.insert(Modules::AIRFLOW),
"hashlib" => self.seen.insert(Modules::HASHLIB),
"crypt" => self.seen.insert(Modules::CRYPT),
_ => {}
}
}
@ -2039,6 +2041,8 @@ bitflags! {
const ATTRS = 1 << 25;
const REGEX = 1 << 26;
const AIRFLOW = 1 << 27;
const HASHLIB = 1 << 28;
const CRYPT = 1 << 29;
}
}