Skip to content

Commit 924ef2f

Browse files
authored
Match hashlib constructor name type diagnostics (#9066)
Use native type names, None spelling, and UTF-8 byte precision for invalid name arguments without changing accepted strings or hashing behavior. Assisted-by: OpenAI Codex:model identifier unavailable
1 parent 4638b6a commit 924ef2f

2 files changed

Lines changed: 43 additions & 3 deletions

File tree

‎crates/stdlib/src/hashlib.rs‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub(crate) use _hashlib::module_def;
88
#[pymodule]
99
pub(crate) mod _hashlib {
1010
use crate::vm::{
11-
Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
11+
Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
1212
builtins::{
1313
PyBaseExceptionRef, PyBytes, PyFrozenSet, PyStr, PyType, PyTypeRef, PyUtf8StrRef,
1414
PyValueError,
@@ -67,11 +67,35 @@ pub(crate) mod _hashlib {
6767
dict.into()
6868
}
6969

70+
#[derive(Debug)]
71+
struct HashName(PyUtf8StrRef);
72+
73+
impl TryFromObject for HashName {
74+
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
75+
if !obj.downcastable::<PyStr>() {
76+
let name = if vm.is_none(&obj) {
77+
"None".into()
78+
} else {
79+
obj.class().slot_name()
80+
};
81+
let mut end = name.len().min(50);
82+
while !name.is_char_boundary(end) {
83+
end -= 1;
84+
}
85+
return Err(vm.new_type_error(format!(
86+
"new() argument 'name' must be str, not {}",
87+
&name[..end]
88+
)));
89+
}
90+
PyUtf8StrRef::try_from_object(vm, obj).map(Self)
91+
}
92+
}
93+
7094
#[derive(FromArgs, Debug)]
7195
#[allow(unused)]
7296
struct NewHashArgs {
7397
#[pyarg(any)]
74-
name: PyUtf8StrRef,
98+
name: HashName,
7599
// Missing still allows the string keyword; b'' does not.
76100
#[pyarg(any, optional, py_default = "b''")]
77101
data: OptionalArg<ArgBytesLike>,
@@ -785,7 +809,7 @@ pub(crate) mod _hashlib {
785809
#[pyfunction(name = "new")]
786810
fn hashlib_new(args: NewHashArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
787811
let data = resolve_data(args.data, args.string, vm)?;
788-
match args.name.as_str().to_lowercase().as_str() {
812+
match args.name.0.as_str().to_lowercase().as_str() {
789813
"md5" => Ok(new_fixed_hasher("md5", data).into_pyobject(vm)),
790814
"sha1" => Ok(new_fixed_hasher("sha1", data).into_pyobject(vm)),
791815
"sha224" => Ok(new_fixed_hasher("sha224", data).into_pyobject(vm)),

‎extra_tests/snippets/stdlib_hashlib.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import _hashlib
12
import _md5
23
import _sha1
4+
import array
35
import hashlib
46

57
from testutils import assert_raises
@@ -64,3 +66,17 @@
6466
# narrower than the length asked for, converting the argument fails first.
6567
with assert_raises(OverflowError):
6668
hashlib.pbkdf2_hmac("sha256", b"password", b"salt", 1, 2**62)
69+
70+
71+
# Invalid names use the native constructor's bounded type diagnostic.
72+
for value, name in (
73+
(None, "None"),
74+
(42, "int"),
75+
(b"sha256", "bytes"),
76+
(array.array("b"), "array.array"),
77+
(type("x" * 60, (), {})(), "x" * 50),
78+
(type("€" * 17, (), {})(), "€" * 16),
79+
):
80+
with assert_raises(TypeError) as caught:
81+
_hashlib.new(value)
82+
assert str(caught.exception) == f"new() argument 'name' must be str, not {name}"

0 commit comments

Comments
 (0)