Skip to content

Commit 3f43723

Browse files
authored
RUST-2127 lint for sign loss when casting (#1481)
1 parent 50a1368 commit 3f43723

File tree

14 files changed

+111
-37
lines changed

14 files changed

+111
-37
lines changed

src/action/gridfs/download.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ impl GridFsBucket {
6666
} else {
6767
(-1, -revision - 1)
6868
};
69+
// unwrap safety: `skip` is always >= 0
70+
let skip: u64 = skip.try_into().unwrap();
6971

7072
match self
7173
.files()
7274
.find_one(doc! { "filename": filename })
7375
.sort(doc! { "uploadDate": sort })
74-
.skip(skip as u64)
76+
.skip(skip)
7577
.await?
7678
{
7779
Some(fcd) => Ok(fcd),

src/bson_util.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,39 @@ pub(crate) fn get_int_raw(val: RawBsonRef<'_>) -> Option<i64> {
6262
}
6363
}
6464

65+
#[allow(private_bounds)]
66+
pub(crate) fn round_clamp<T: RoundClampTarget>(input: f64) -> T {
67+
T::round_clamp(input)
68+
}
69+
70+
trait RoundClampTarget {
71+
fn round_clamp(input: f64) -> Self;
72+
}
73+
74+
impl RoundClampTarget for u64 {
75+
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
76+
fn round_clamp(input: f64) -> Self {
77+
input as u64
78+
}
79+
}
80+
81+
impl RoundClampTarget for u32 {
82+
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
83+
fn round_clamp(input: f64) -> Self {
84+
input as u32
85+
}
86+
}
87+
6588
/// Coerce numeric types into an `u64` if it would be lossless to do so. If this Bson is not numeric
6689
/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`.
6790
#[allow(clippy::cast_possible_truncation)]
6891
pub(crate) fn get_u64(val: &Bson) -> Option<u64> {
6992
match *val {
7093
Bson::Int32(i) => u64::try_from(i).ok(),
7194
Bson::Int64(i) => u64::try_from(i).ok(),
72-
Bson::Double(f) if (f - (f as u64 as f64)).abs() <= f64::EPSILON => Some(f as u64),
95+
Bson::Double(f) if (f - (round_clamp::<u64>(f) as f64)).abs() <= f64::EPSILON => {
96+
Some(round_clamp(f))
97+
}
7398
_ => None,
7499
}
75100
}
@@ -291,6 +316,31 @@ impl RawDocumentCollection for RawArrayBuf {
291316
}
292317
}
293318

319+
pub(crate) mod option_u64_as_i64 {
320+
use serde::{Deserialize, Serialize};
321+
322+
pub(crate) fn serialize<S: serde::Serializer>(
323+
value: &Option<u64>,
324+
s: S,
325+
) -> std::result::Result<S::Ok, S::Error> {
326+
let conv: Option<i64> = value
327+
.as_ref()
328+
.map(|&u| u.try_into())
329+
.transpose()
330+
.map_err(serde::ser::Error::custom)?;
331+
conv.serialize(s)
332+
}
333+
334+
pub(crate) fn deserialize<'de, D: serde::Deserializer<'de>>(
335+
d: D,
336+
) -> std::result::Result<Option<u64>, D::Error> {
337+
let conv = Option::<i64>::deserialize(d)?;
338+
conv.map(|i| i.try_into())
339+
.transpose()
340+
.map_err(serde::de::Error::custom)
341+
}
342+
}
343+
294344
#[cfg(test)]
295345
mod test {
296346
use crate::bson_util::num_decimal_digits;

src/client/options.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1952,7 +1952,10 @@ impl ConnectionString {
19521952
// -1 maxStaleness means no maxStaleness, which is the default
19531953
return Ok(());
19541954
}
1955-
Ordering::Greater => Duration::from_secs(max_staleness_seconds as u64),
1955+
Ordering::Greater => {
1956+
// unwrap safety: `max_staleness_seconds` will always be >= 0
1957+
Duration::from_secs(max_staleness_seconds.try_into().unwrap())
1958+
}
19561959
};
19571960

19581961
parts.max_staleness = Some(max_staleness);

src/cmap/conn/stream_description.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl StreamDescription {
5757
logical_session_timeout: reply
5858
.command_response
5959
.logical_session_timeout_minutes
60-
.map(|mins| Duration::from_secs(mins as u64 * 60)),
60+
.map(|mins| Duration::from_secs(mins * 60)),
6161
max_bson_object_size: reply.command_response.max_bson_object_size,
6262
// The defaulting to 100,000 is here because mongocryptd doesn't include this field in
6363
// hello replies; this should never happen when talking to a real server.

src/cmap/options.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ impl<'de> Deserialize<'de> for BackgroundThreadInterval {
116116
Ordering::Less => BackgroundThreadInterval::Never,
117117
Ordering::Equal => return Err(D::Error::custom("zero is not allowed")),
118118
Ordering::Greater => {
119-
BackgroundThreadInterval::Every(Duration::from_millis(millis as u64))
119+
// unwrap safety: millis is validated to be in the u64 range
120+
BackgroundThreadInterval::Every(Duration::from_millis(millis.try_into().unwrap()))
120121
}
121122
})
122123
}

src/hello.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ pub(crate) struct HelloCommandResponse {
157157
pub is_replica_set: Option<bool>,
158158

159159
/// The time in minutes that a session remains active after its most recent use.
160-
pub logical_session_timeout_minutes: Option<i64>,
160+
#[serde(default, with = "crate::bson_util::option_u64_as_i64")]
161+
pub logical_session_timeout_minutes: Option<u64>,
161162

162163
/// Optime and date information for the server's most recent write operation.
163164
pub last_write: Option<LastWrite>,

src/index/options.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ impl<'de> Deserialize<'de> for IndexVersion {
168168
0 => Ok(IndexVersion::V0),
169169
1 => Ok(IndexVersion::V1),
170170
2 => Ok(IndexVersion::V2),
171-
i => Ok(IndexVersion::Custom(i as u32)),
171+
i => Ok(IndexVersion::Custom(
172+
i.try_into().map_err(serde::de::Error::custom)?,
173+
)),
172174
}
173175
}
174176
}
@@ -213,7 +215,9 @@ impl<'de> Deserialize<'de> for TextIndexVersion {
213215
1 => Ok(TextIndexVersion::V1),
214216
2 => Ok(TextIndexVersion::V2),
215217
3 => Ok(TextIndexVersion::V3),
216-
i => Ok(TextIndexVersion::Custom(i as u32)),
218+
i => Ok(TextIndexVersion::Custom(
219+
i.try_into().map_err(serde::de::Error::custom)?,
220+
)),
217221
}
218222
}
219223
}
@@ -253,7 +257,9 @@ impl<'de> Deserialize<'de> for Sphere2DIndexVersion {
253257
match i32::deserialize(deserializer)? {
254258
2 => Ok(Sphere2DIndexVersion::V2),
255259
3 => Ok(Sphere2DIndexVersion::V3),
256-
i => Ok(Sphere2DIndexVersion::Custom(i as u32)),
260+
i => Ok(Sphere2DIndexVersion::Custom(
261+
i.try_into().map_err(serde::de::Error::custom)?,
262+
)),
257263
}
258264
}
259265
}

src/lib.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#![doc = include_str!("../README.md")]
2-
#![warn(missing_docs)]
3-
#![warn(rustdoc::missing_crate_level_docs)]
4-
#![warn(clippy::cast_possible_truncation)]
5-
#![warn(clippy::cast_possible_wrap)]
2+
#![warn(
3+
missing_docs,
4+
rustdoc::missing_crate_level_docs,
5+
clippy::cast_possible_truncation,
6+
clippy::cast_possible_wrap,
7+
clippy::cast_sign_loss
8+
)]
69
#![allow(
710
clippy::unreadable_literal,
811
clippy::cognitive_complexity,

src/sdam/description/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ impl ServerDescription {
409409
Ok(Some(ref reply)) => Ok(reply
410410
.command_response
411411
.logical_session_timeout_minutes
412-
.map(|timeout| Duration::from_secs(timeout as u64 * 60))),
412+
.map(|timeout| Duration::from_secs(timeout * 60))),
413413
Err(ref e) => Err(e.clone()),
414414
}
415415
}

src/sdam/description/topology/server_selection/test/in_window.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::{collections::HashMap, sync::Arc, time::Duration};
22

3-
use crate::bson::{doc, Document};
3+
use crate::{
4+
bson::{doc, Document},
5+
bson_util::round_clamp,
6+
};
47
use approx::abs_diff_eq;
58
use serde::Deserialize;
69

@@ -188,14 +191,14 @@ async fn load_balancing_test() {
188191
assert!(
189192
share_of_selections <= max_share,
190193
"expected no more than {}% of selections, instead got {}%",
191-
(max_share * 100.0) as u32,
192-
(share_of_selections * 100.0) as u32
194+
round_clamp::<u32>(max_share * 100.0),
195+
round_clamp::<u32>(share_of_selections * 100.0)
193196
);
194197
assert!(
195198
share_of_selections >= min_share,
196199
"expected at least {}% of selections, instead got {}%",
197-
(min_share * 100.0) as u32,
198-
(share_of_selections * 100.0) as u32
200+
round_clamp::<u32>(min_share * 100.0),
201+
round_clamp::<u32>(share_of_selections * 100.0)
199202
);
200203
}
201204
}

0 commit comments

Comments
 (0)