compare_double mishandles infinities: equal infinities compare unequal, opposite infinities may compare equal
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 78/100
Hướng nghiên cứu
Bắt đầu với các triển khai của compare_double() trong cJSON.c và cJSON_Utils.c, sau đó chạy bản tái hiện C được cung cấp bằng cJSON_Compare(). Cập nhật hành vi so sánh các giá trị không hữu hạn ở cả hai vị trí và xác minh rằng các vô cực bằng nhau được so sánh là bằng nhau, các vô cực đối nhau được so sánh là không bằng nhau, còn các phép so sánh xấp xỉ hữu hạn vẫn không thay đổi.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
compare_double() does not handle non-finite values correctly.
In particular:
- two separately allocated numbers containing
+INFINITYcompare unequal; - two separately allocated numbers containing
-INFINITYcompare unequal; +INFINITYand-INFINITYcompare equal incJSON_Compare().
The issue comes from applying the relative-error comparison to infinities without handling non-finite values first.
Affected code
In cJSON.c, compare_double() is currently equivalent to:
static cJSON_bool compare_double(double a, double b)
{
double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b);
return (fabs(a - b) <= maxVal * DBL_EPSILON) ? true : false;
}
For infinities this gives unexpected results.
For equal infinities:
a = +Inf
b = +Inf
a - b = NaN
fabs(a - b) = NaN
maxVal * epsilon = Inf
NaN <= Inf = false
so two distinct +Inf values compare unequal. The same happens for -Inf.
For opposite infinities:
a = +Inf
b = -Inf
a - b = +Inf
fabs(a - b) = +Inf
maxVal * epsilon = +Inf
Inf <= Inf = true
so +Inf and -Inf compare equal.
cJSON_Compare() uses this function for cJSON_Number, so these results are observable through the public API.
A similar compare_double() implementation also exists in cJSON_Utils.c.
Reproduction
#include <stdio.h>
#include <math.h>
#include "cJSON.h"
int main(void)
{
cJSON *p1 = cJSON_CreateNumber(INFINITY);
cJSON *p2 = cJSON_CreateNumber(INFINITY);
cJSON *n1 = cJSON_CreateNumber(-INFINITY);
cJSON *n2 = cJSON_CreateNumber(-INFINITY);
if (!p1 || !p2 || !n1 || !n2)
{
return 1;
}
printf("+Inf vs +Inf: %d\n", cJSON_Compare(p1, p2, 1));
printf("-Inf vs -Inf: %d\n", cJSON_Compare(n1, n2, 1));
printf("+Inf vs -Inf: %d\n", cJSON_Compare(p1, n1, 1));
printf("-Inf vs +Inf: %d\n", cJSON_Compare(n1, p1, 1));
cJSON_Delete(p1);
cJSON_Delete(p2);
cJSON_Delete(n1);
cJSON_Delete(n2);
return 0;
}
Observed result:
+Inf vs +Inf: 0
-Inf vs -Inf: 0
+Inf vs -Inf: 1
-Inf vs +Inf: 1
Expected behavior would be:
+Inf vs +Inf: 1
-Inf vs -Inf: 1
+Inf vs -Inf: 0
-Inf vs +Inf: 0
Why this is reachable
Although JSON itself has no NaN or Infinity literals, non-finite values are reachable through the cJSON C API:
cJSON_CreateNumber(INFINITY);
cJSON_SetNumberValue(item, INFINITY);
Infinity can also arise while parsing a syntactically valid JSON number whose magnitude exceeds the finite range of double, depending on the strtod() implementation, for example:
cJSON_Parse("1e999");
So the comparison code should not assume that every stored double is finite.
Suggested fix
Handle non-finite values before applying the relative-error calculation.
For example, exact equality can be checked first:
static cJSON_bool compare_double(double a, double b)
{
double maxVal;
if (a == b)
{
return true;
}
if (!isfinite(a) || !isfinite(b))
{
return false;
}
maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b);
return (fabs(a - b) <= maxVal * DBL_EPSILON) ? true : false;
}
This gives the expected infinity behavior while preserving the existing approximate comparison for finite values.
The equivalent implementation in cJSON_Utils.c should be updated as well.
Related NaN-to-integer issue
While reviewing the same non-finite-number paths, cJSON_CreateNumber() / cJSON_SetNumberHelper() also reach:
valueint = (int)number;
for NaN, because both comparisons against INT_MAX and INT_MIN are false for NaN. Converting NaN to an integer type this way is undefined behavior when the value cannot be represented.
However, that issue is already tracked by #999 and addressed by PR #1000, so I am not proposing to duplicate it here.
This issue is specifically about the incorrect infinity comparison behavior in compare_double().
- Ngôn ngữ chính
- C
- Star
- 13k
- Fork
- 3.5k
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của DaveGamble/cJSON
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
DaveGamble/cJSON#1094 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
DaveGamble/cJSON#1093 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
DaveGamble/cJSON#1082 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
DaveGamble/cJSON#1081 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
DaveGamble/cJSON#1074 ·
Tất cả issue của DaveGamble/cJSON
Issue tương tự
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Status: Opened
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Issue-Bug Needs-Triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
trezor/trezor-firmware#7997 ·
Maintainer thường phản hồi trong vòng 2 ngày