Skip to content

Commit 630107a

Browse files
committed
Removed implicit conversion in comparison operators (issue #998)
1 parent 4eb8074 commit 630107a

14 files changed

+383
-133
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ HEAD
99
* Made `deserializeJson()` more picky about trailing characters (issue #980)
1010
* Added `ARDUINOJSON_ENABLE_NAN` (default=0) to enable NaN in JSON (issue #973)
1111
* Added `ARDUINOJSON_ENABLE_INFINITY` (default=0) to enable Infinity in JSON
12+
* Removed implicit conversion in comparison operators (issue #998)
13+
* Added lexicographical comparison for `JsonVariant`
1214

1315
> ### BREAKING CHANGES
1416
>

src/ArduinoJson/Operators/VariantComparisons.hpp

+154-92
Original file line numberDiff line numberDiff line change
@@ -7,154 +7,216 @@
77
#include "../Variant/VariantRef.hpp"
88

99
namespace ARDUINOJSON_NAMESPACE {
10+
11+
template <typename T, typename Enable = void>
12+
struct Comparer;
13+
14+
template <typename T>
15+
struct Comparer<T, typename enable_if<IsString<T>::value>::type> {
16+
T rhs;
17+
int result;
18+
19+
explicit Comparer(T value) : rhs(value), result(1) {}
20+
21+
void visitArray(const CollectionData &) {}
22+
void visitObject(const CollectionData &) {}
23+
void visitFloat(Float) {}
24+
void visitString(const char *lhs) {
25+
result = -adaptString(rhs).compare(lhs);
26+
}
27+
void visitRawJson(const char *, size_t) {}
28+
void visitNegativeInteger(UInt) {}
29+
void visitPositiveInteger(UInt) {}
30+
void visitBoolean(bool) {}
31+
void visitNull() {
32+
result = adaptString(rhs).compare(NULL);
33+
}
34+
};
35+
36+
template <typename T>
37+
typename enable_if<is_signed<T>::value, int>::type sign(const T &value) {
38+
return value < 0 ? -1 : value > 0 ? 1 : 0;
39+
}
40+
1041
template <typename T>
11-
struct is_simple_value {
12-
static const bool value = is_integral<T>::value ||
13-
is_floating_point<T>::value ||
14-
is_same<T, bool>::value;
42+
typename enable_if<is_unsigned<T>::value, int>::type sign(const T &value) {
43+
return value > 0 ? 1 : 0;
44+
}
45+
46+
template <typename T>
47+
struct Comparer<T, typename enable_if<is_integral<T>::value ||
48+
is_floating_point<T>::value>::type> {
49+
T rhs;
50+
int result;
51+
52+
explicit Comparer(T value) : rhs(value), result(1) {}
53+
54+
void visitArray(const CollectionData &) {}
55+
void visitObject(const CollectionData &) {}
56+
void visitFloat(Float lhs) {
57+
result = sign(lhs - static_cast<Float>(rhs));
58+
}
59+
void visitString(const char *) {}
60+
void visitRawJson(const char *, size_t) {}
61+
void visitNegativeInteger(UInt lhs) {
62+
result = -sign(static_cast<T>(lhs) + rhs);
63+
}
64+
void visitPositiveInteger(UInt lhs) {
65+
result = static_cast<T>(lhs) < rhs ? -1 : static_cast<T>(lhs) > rhs ? 1 : 0;
66+
}
67+
void visitBoolean(bool) {}
68+
void visitNull() {}
69+
};
70+
71+
template <>
72+
struct Comparer<bool, void> {
73+
bool rhs;
74+
int result;
75+
76+
explicit Comparer(bool value) : rhs(value), result(1) {}
77+
78+
void visitArray(const CollectionData &) {}
79+
void visitObject(const CollectionData &) {}
80+
void visitFloat(Float) {}
81+
void visitString(const char *) {}
82+
void visitRawJson(const char *, size_t) {}
83+
void visitNegativeInteger(UInt) {}
84+
void visitPositiveInteger(UInt) {}
85+
void visitBoolean(bool lhs) {
86+
result = static_cast<int>(lhs - rhs);
87+
}
88+
void visitNull() {}
1589
};
1690

1791
template <typename TVariant>
1892
class VariantComparisons {
19-
public:
20-
// const char* == TVariant
93+
private:
2194
template <typename T>
22-
friend typename enable_if<IsString<T *>::value, bool>::type operator==(
23-
T *lhs, TVariant rhs) {
24-
return adaptString(lhs).equals(rhs.template as<const char *>());
95+
static int compare(TVariant lhs, const T &rhs) {
96+
Comparer<T> comparer(rhs);
97+
lhs.accept(comparer);
98+
return comparer.result;
2599
}
26100

27-
// std::string == TVariant
101+
public:
102+
// value == TVariant
28103
template <typename T>
29-
friend typename enable_if<IsString<T>::value, bool>::type operator==(
30-
const T &lhs, TVariant rhs) {
31-
return adaptString(lhs).equals(rhs.template as<const char *>());
104+
friend bool operator==(T *lhs, TVariant rhs) {
105+
return compare(rhs, lhs) == 0;
32106
}
33-
34-
// TVariant == const char*
35107
template <typename T>
36-
friend typename enable_if<IsString<T *>::value, bool>::type operator==(
37-
TVariant lhs, T *rhs) {
38-
return adaptString(rhs).equals(lhs.template as<const char *>());
108+
friend bool operator==(const T &lhs, TVariant rhs) {
109+
return compare(rhs, lhs) == 0;
39110
}
40111

41-
// TVariant == std::string
112+
// TVariant == value
42113
template <typename T>
43-
friend typename enable_if<IsString<T>::value, bool>::type operator==(
44-
TVariant lhs, const T &rhs) {
45-
return adaptString(rhs).equals(lhs.template as<const char *>());
114+
friend bool operator==(TVariant lhs, T *rhs) {
115+
return compare(lhs, rhs) == 0;
46116
}
47-
48-
// bool/int/float == TVariant
49117
template <typename T>
50-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator==(
51-
const T &lhs, TVariant rhs) {
52-
return lhs == rhs.template as<T>();
118+
friend bool operator==(TVariant lhs, const T &rhs) {
119+
return compare(lhs, rhs) == 0;
53120
}
54121

55-
// TVariant == bool/int/float
122+
// value != TVariant
56123
template <typename T>
57-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator==(
58-
TVariant lhs, const T &rhs) {
59-
return lhs.template as<T>() == rhs;
124+
friend bool operator!=(T *lhs, TVariant rhs) {
125+
return compare(rhs, lhs) != 0;
60126
}
61-
62-
// const char* != TVariant
63127
template <typename T>
64-
friend typename enable_if<IsString<T *>::value, bool>::type operator!=(
65-
T *lhs, TVariant rhs) {
66-
return !adaptString(lhs).equals(rhs.template as<const char *>());
128+
friend bool operator!=(const T &lhs, TVariant rhs) {
129+
return compare(rhs, lhs) != 0;
67130
}
68131

69-
// std::string != TVariant
132+
// TVariant != value
70133
template <typename T>
71-
friend typename enable_if<IsString<T>::value, bool>::type operator!=(
72-
const T &lhs, TVariant rhs) {
73-
return !adaptString(lhs).equals(rhs.template as<const char *>());
134+
friend bool operator!=(TVariant lhs, T *rhs) {
135+
return compare(lhs, rhs) != 0;
74136
}
75-
76-
// TVariant != const char*
77137
template <typename T>
78-
friend typename enable_if<IsString<T *>::value, bool>::type operator!=(
79-
TVariant lhs, T *rhs) {
80-
return !adaptString(rhs).equals(lhs.template as<const char *>());
138+
friend bool operator!=(TVariant lhs, const T &rhs) {
139+
return compare(lhs, rhs) != 0;
81140
}
82141

83-
// TVariant != std::string
142+
// value < TVariant
84143
template <typename T>
85-
friend typename enable_if<IsString<T>::value, bool>::type operator!=(
86-
TVariant lhs, const T &rhs) {
87-
return !adaptString(rhs).equals(lhs.template as<const char *>());
144+
friend bool operator<(T *lhs, TVariant rhs) {
145+
return compare(rhs, lhs) > 0;
88146
}
89-
90-
// bool/int/float != TVariant
91147
template <typename T>
92-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator!=(
93-
const T &lhs, TVariant rhs) {
94-
return lhs != rhs.template as<T>();
148+
friend bool operator<(const T &lhs, TVariant rhs) {
149+
return compare(rhs, lhs) > 0;
95150
}
96151

97-
// TVariant != bool/int/float
152+
// TVariant < value
98153
template <typename T>
99-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator!=(
100-
TVariant lhs, const T &rhs) {
101-
return lhs.template as<T>() != rhs;
154+
friend bool operator<(TVariant lhs, T *rhs) {
155+
return compare(lhs, rhs) < 0;
102156
}
103-
104-
// bool/int/float < TVariant
105157
template <typename T>
106-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator<(
107-
const T &lhs, TVariant rhs) {
108-
return lhs < rhs.template as<T>();
158+
friend bool operator<(TVariant lhs, const T &rhs) {
159+
return compare(lhs, rhs) < 0;
109160
}
110161

111-
// TVariant < bool/int/float
162+
// value <= TVariant
112163
template <typename T>
113-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator<(
114-
TVariant lhs, const T &rhs) {
115-
return lhs.template as<T>() < rhs;
164+
friend bool operator<=(T *lhs, TVariant rhs) {
165+
return compare(rhs, lhs) >= 0;
116166
}
117-
118-
// bool/int/float <= TVariant
119167
template <typename T>
120-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator<=(
121-
const T &lhs, TVariant rhs) {
122-
return lhs <= rhs.template as<T>();
168+
friend bool operator<=(const T &lhs, TVariant rhs) {
169+
return compare(rhs, lhs) >= 0;
123170
}
124171

125-
// TVariant <= bool/int/float
172+
// TVariant <= value
126173
template <typename T>
127-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator<=(
128-
TVariant lhs, const T &rhs) {
129-
return lhs.template as<T>() <= rhs;
174+
friend bool operator<=(TVariant lhs, T *rhs) {
175+
return compare(lhs, rhs) <= 0;
176+
}
177+
template <typename T>
178+
friend bool operator<=(TVariant lhs, const T &rhs) {
179+
return compare(lhs, rhs) <= 0;
130180
}
131181

132-
// bool/int/float > TVariant
182+
// value > TVariant
133183
template <typename T>
134-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator>(
135-
const T &lhs, TVariant rhs) {
136-
return lhs > rhs.template as<T>();
184+
friend bool operator>(T *lhs, TVariant rhs) {
185+
return compare(rhs, lhs) < 0;
186+
}
187+
template <typename T>
188+
friend bool operator>(const T &lhs, TVariant rhs) {
189+
return compare(rhs, lhs) < 0;
137190
}
138191

139-
// TVariant > bool/int/float
192+
// TVariant > value
193+
template <typename T>
194+
friend bool operator>(TVariant lhs, T *rhs) {
195+
return compare(lhs, rhs) > 0;
196+
}
140197
template <typename T>
141-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator>(
142-
TVariant lhs, const T &rhs) {
143-
return lhs.template as<T>() > rhs;
198+
friend bool operator>(TVariant lhs, const T &rhs) {
199+
return compare(lhs, rhs) > 0;
144200
}
145201

146-
// bool/int/float >= TVariant
202+
// value >= TVariant
203+
template <typename T>
204+
friend bool operator>=(T *lhs, TVariant rhs) {
205+
return compare(rhs, lhs) <= 0;
206+
}
147207
template <typename T>
148-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator>=(
149-
const T &lhs, TVariant rhs) {
150-
return lhs >= rhs.template as<T>();
208+
friend bool operator>=(const T &lhs, TVariant rhs) {
209+
return compare(rhs, lhs) <= 0;
151210
}
152211

153-
// TVariant >= bool/int/float
212+
// TVariant >= value
213+
template <typename T>
214+
friend bool operator>=(TVariant lhs, T *rhs) {
215+
return compare(lhs, rhs) >= 0;
216+
}
154217
template <typename T>
155-
friend typename enable_if<is_simple_value<T>::value, bool>::type operator>=(
156-
TVariant lhs, const T &rhs) {
157-
return lhs.template as<T>() >= rhs;
218+
friend bool operator>=(TVariant lhs, const T &rhs) {
219+
return compare(lhs, rhs) >= 0;
158220
}
159221
};
160222

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// ArduinoJson - arduinojson.org
2+
// Copyright Benoit Blanchon 2014-2019
3+
// MIT License
4+
5+
#pragma once
6+
7+
namespace ARDUINOJSON_NAMESPACE {
8+
9+
inline int8_t safe_strcmp(const char* a, const char* b) {
10+
if (a == b) return 0;
11+
if (!a) return -1;
12+
if (!b) return 1;
13+
return static_cast<int8_t>(strcmp(a, b));
14+
}
15+
16+
inline int8_t safe_strncmp(const char* a, const char* b, size_t n) {
17+
if (a == b) return 0;
18+
if (!a) return -1;
19+
if (!b) return 1;
20+
return static_cast<int8_t>(strncmp(a, b, n));
21+
}
22+
} // namespace ARDUINOJSON_NAMESPACE

src/ArduinoJson/Strings/ArduinoStringAdapter.hpp

+8-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#pragma once
66

77
#include <WString.h>
8+
#include "../Polyfills/safe_strcmp.hpp"
89

910
namespace ARDUINOJSON_NAMESPACE {
1011

@@ -25,11 +26,14 @@ class ArduinoStringAdapter {
2526
return !_str->c_str();
2627
}
2728

28-
bool equals(const char* expected) const {
29+
int8_t compare(const char* other) const {
2930
// Arduino's String::c_str() can return NULL
30-
const char* actual = _str->c_str();
31-
if (!actual || !expected) return actual == expected;
32-
return 0 == strcmp(actual, expected);
31+
const char* me = _str->c_str();
32+
return safe_strcmp(me, other);
33+
}
34+
35+
bool equals(const char* expected) const {
36+
return compare(expected) == 0;
3337
}
3438

3539
const char* data() const {

src/ArduinoJson/Strings/ConstRamStringAdapter.hpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@
66

77
#include <stddef.h> // size_t
88
#include <string.h> // strcmp
9+
#include "../Polyfills/safe_strcmp.hpp"
910

1011
namespace ARDUINOJSON_NAMESPACE {
1112

1213
class ConstRamStringAdapter {
1314
public:
1415
ConstRamStringAdapter(const char* str = 0) : _str(str) {}
1516

17+
int8_t compare(const char* other) const {
18+
return safe_strcmp(_str, other);
19+
}
20+
1621
bool equals(const char* expected) const {
17-
const char* actual = _str;
18-
if (!actual || !expected) return actual == expected;
19-
return strcmp(actual, expected) == 0;
22+
return compare(expected) == 0;
2023
}
2124

2225
bool isNull() const {

0 commit comments

Comments
 (0)