* Fix steps being mistaken for seconds

* Cap steps at `u32::max` seconds

* Fix overflow of steps in Rust

* Prevent overflow of `IntervalKind`

* Prevent overflow in `revlod/mod.rs`

Also replace some `as` with `from` and `try_from` as is recommended to
highlight potential issues.

* Ensure v2 doesn't store overflowing revlog ivls

* Lower steps cap in deck options

Whereas large card intervals are converted to days, revlog intervals use
i32s to store large numbers of seconds.

* Format
This commit is contained in:
RumovZ 2021-12-15 09:46:26 +01:00 committed by GitHub
parent 0d51b4db1f
commit 80ed94ed08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 30 additions and 18 deletions

View File

@ -755,8 +755,8 @@ limit ?"""
card.id,
self.col.usn(),
ease,
ivl,
lastIvl,
saturated_i32(ivl),
saturated_i32(lastIvl),
card.factor,
card.time_taken(),
type,
@ -888,8 +888,8 @@ limit ?"""
card.id,
self.col.usn(),
ease,
-delay or card.ivl,
card.lastIvl,
saturated_i32(-delay or card.ivl),
saturated_i32(card.lastIvl),
card.factor,
card.time_taken(),
type,
@ -1152,3 +1152,10 @@ and (queue={QUEUE_TYPE_NEW} or (queue={QUEUE_TYPE_REV} and due<=?))""",
def _is_finished(self) -> bool:
"Don't use this, it is a stop-gap until this code is refactored."
return not any((self.newCount, self.revCount, self._immediate_learn_count))
def saturated_i32(number: int) -> int:
"""Avoid problems on the backend by ensuring reasonably sized values."""
I32_MIN = -(2 ** 31)
I32_MAX = 2 ** 31 - 1
return min(max(number, I32_MIN), I32_MAX)

View File

@ -78,11 +78,12 @@ impl Default for RevlogReviewKind {
impl RevlogEntry {
pub(crate) fn interval_secs(&self) -> u32 {
(if self.interval > 0 {
self.interval * 86_400
u32::try_from(if self.interval > 0 {
self.interval.saturating_mul(86_400)
} else {
-self.interval
}) as u32
self.interval.saturating_mul(-1)
})
.unwrap()
}
}
@ -98,9 +99,9 @@ impl Collection {
cid: card.id,
usn,
button_chosen: 0,
interval: card.interval as i32,
last_interval: original.interval as i32,
ease_factor: card.ease_factor as u32,
interval: i32::try_from(card.interval).unwrap_or(i32::MAX),
last_interval: i32::try_from(original.interval).unwrap_or(i32::MAX),
ease_factor: u32::from(card.ease_factor),
taken_millis: 0,
review_kind: RevlogReviewKind::Manual,
};

View File

@ -25,14 +25,14 @@ impl IntervalKind {
pub(crate) fn as_seconds(self) -> u32 {
match self {
IntervalKind::InSecs(secs) => secs,
IntervalKind::InDays(days) => days * 86_400,
IntervalKind::InDays(days) => days.saturating_mul(86_400),
}
}
pub(crate) fn as_revlog_interval(self) -> i32 {
match self {
IntervalKind::InDays(days) => days as i32,
IntervalKind::InSecs(secs) => -(secs as i32),
IntervalKind::InSecs(secs) => -i32::try_from(secs).unwrap_or(i32::MAX),
}
}
}

View File

@ -109,7 +109,7 @@ impl<'a> StateContext<'a> {
pub(crate) fn defaults_for_testing() -> Self {
Self {
fuzz_factor: None,
steps: LearningSteps::new(&[60.0, 600.0]),
steps: LearningSteps::new(&[1.0, 10.0]),
graduating_interval_good: 1,
graduating_interval_easy: 4,
initial_ease_factor: 2.5,
@ -118,7 +118,7 @@ impl<'a> StateContext<'a> {
interval_multiplier: 1.0,
maximum_review_interval: 36500,
leech_threshold: 8,
relearn_steps: LearningSteps::new(&[600.0]),
relearn_steps: LearningSteps::new(&[10.0]),
lapse_multiplier: 0.0,
minimum_lapse_interval: 1,
in_filtered_deck: false,

View File

@ -5,6 +5,7 @@ const DEFAULT_SECS_IF_MISSING: u32 = 60;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct LearningSteps<'a> {
/// The steps in minutes.
steps: &'a [f32],
}
@ -13,6 +14,7 @@ fn to_secs(v: f32) -> u32 {
}
impl<'a> LearningSteps<'a> {
/// Takes `steps` as minutes.
pub(crate) fn new(steps: &[f32]) -> LearningSteps<'_> {
LearningSteps { steps }
}
@ -51,11 +53,11 @@ impl<'a> LearningSteps<'a> {
let next = if self.steps.len() > 1 {
self.secs_at_index(idx + 1).unwrap_or(60)
} else {
current * 2
current.saturating_mul(2)
}
.max(current);
Some((current + next) / 2)
Some(current.saturating_add(next) / 2)
} else {
None
}

View File

@ -49,7 +49,9 @@ function stringToMinutes(text: string): number {
const [_, num, suffix] = match;
const unit = suffixToUnit(suffix);
const seconds = unitSeconds(unit) * parseInt(num, 10);
return seconds / 60;
// should be representable as negative i32 seconds in a revlog
const capped_seconds = Math.min(seconds, 2 ** 31);
return capped_seconds / 60;
} else {
return 0;
}