Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ Details of the TOML language subset
* The supported data types are strings, integers, floats, and booleans.
* Whitespace is allowed.
* Only basic strings are supported, not triple-quoted strings.
* Only integers supported by ``strtol()`` can be parsed:
no ``0o``, no ``0b``, no underscores ``1_000``.
TOML allows all of these, but this parser does not yet.
* Integers may be decimal, hexadecimal ``0x1F``, octal ``0o17``, or binary ``0b101``,
and may contain underscores ``1_000``.
Unlike TOML, a sign is also allowed before ``0x``, ``0o``, and ``0b``,
the prefix may be uppercase, and underscores need not be between digits.
* Leading zeros are not allowed: ``011`` is an error, not 9 or 11.
* Only bare keys are supported.
* Duplicate keys are not diagnosed.
Expand Down
3 changes: 2 additions & 1 deletion shared-bindings/supervisor/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,8 @@ MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_usb_identification_obj, 0, supervisor_
//| - ``str``: Double-quoted string.
//| The string may include Unicode characters, and ``\\u`` Unicode escapes. Backslash-escaped characters
//| ``\\b``, ``\\r``, ``\\n``, ``\\t``, ``\\v``, ``\\v`` are also allowed.
//| - ``int``: signed or unsigned integer.
//| - ``int``: signed or unsigned integer: decimal, or hexadecimal ``0x1F``, octal ``0o17``,
//| or binary ``0b101``. Underscores are allowed: ``1_000``.
//| - ``bool``: lower-case ``true`` or ``false``.
//| The values are returned as Python ``True`` or ``False`` values.
//| - ``float``: signed or unsigned decimal number with a ``.`` or exponent, or ``inf``, ``inf``, ``nan``.
Expand Down
76 changes: 67 additions & 9 deletions supervisor/shared/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,47 @@ static bool key_matches(file_arg *file_handle, const char *key) {
return true;
}

// Parse digits in base 2 to 16, skipping any '_' after the first digit.
// Used instead of strtol() and strtoul(), which would pull in about 0.8 kB of libc.
//
// Returns a pointer past the last digit, or NULL if there are no digits or the value
// overflows mp_uint_t. *value is not touched on error.
static const char *parse_digits(const char *str, mp_uint_t base, mp_uint_t *value) {
const char *start = str;
mp_uint_t val = 0;
for (;; str++) {
mp_uint_t dig = (unsigned char)*str;
if (dig == '_' && str != start) {
continue;
}
if ('0' <= dig && dig <= '9') {
dig -= '0';
} else {
// Lowercase; anything that is not a letter ends up >= base.
dig = (dig | 0x20) - 'a' + 10;
}
if (dig >= base) {
break;
}
if (__builtin_mul_overflow(val, base, &val) || __builtin_add_overflow(val, dig, &val)) {
return NULL;
}
}
if (str == start) {
return NULL;
}
*value = val;
return str;
}

static settings_err_t read_unicode_escape(file_arg *file_handle, int sz, vstr_t *vstr) {
char hex_buf[sz + 1];
for (int i = 0; i < sz; i++) {
hex_buf[i] = get_next_byte(file_handle);
}
hex_buf[sz] = 0;
char *end;
unsigned long c = strtoul(hex_buf, &end, 16);
if (end != &hex_buf[sz]) {
mp_uint_t c;
if (parse_digits(hex_buf, 16, &c) != &hex_buf[sz]) {
return SETTINGS_ERR_BAD_VALUE;
}
if (c >= 0x110000) {
Expand Down Expand Up @@ -412,15 +444,41 @@ static settings_err_t get_int(const char *key, mp_int_t *value) {
if (quoted || has_leading_zero(buf)) {
return SETTINGS_ERR_BAD_VALUE;
}
char *end;
long num = strtol(buf, &end, 0);
while (unichar_isspace(*end)) {
end++;
// Accept a superset of TOML: a sign is also allowed before 0x, 0o and 0b, the prefix
// may be uppercase, and underscores need not be between digits.
const char *str = buf;
bool neg = false;
if (*str == '+' || *str == '-') {
neg = *str == '-';
str++;
}
mp_uint_t base = 10;
if (str[0] == '0') {
char prefix = str[1] | 0x20;
if (prefix == 'x') {
base = 16;
} else if (prefix == 'o') {
base = 8;
} else if (prefix == 'b') {
base = 2;
}
if (base != 10) {
str += 2;
}
}
mp_uint_t magnitude;
str = parse_digits(str, base, &magnitude);
// A negative value can be one larger than MP_INT_MAX.
if (str == NULL || magnitude > (mp_uint_t)MP_INT_MAX + neg) {
return SETTINGS_ERR_BAD_VALUE;
}
while (unichar_isspace(*str)) {
str++;
}
if (end == buf || *end) { // If the whole buffer was not consumed it's an error
if (*str) { // If the whole buffer was not consumed it's an error
return SETTINGS_ERR_BAD_VALUE;
}
*value = (mp_int_t)num;
*value = (mp_int_t)(neg ? 0 - magnitude : magnitude);
return SETTINGS_OK;
}

Expand Down
6 changes: 6 additions & 0 deletions tests/circuitpython-manual/settings/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ def check(key, expected):
check("neg_int_val", -7)
check("hex_int_val", 31)
check("hex_leading_zero_val", 15) # TOML allows leading zeros after 0x
check("neg_hex_int_val", -17) # not TOML, but accepted
check("oct_int_val", 15)
check("bin_int_val", 17)
check("underscore_int_val", 1000) # used to fall back to float 1000.0
check("zero_int_val", 0)
check("signed_zero_val", 0)
check("bool_true", True)
Expand Down Expand Up @@ -58,6 +62,8 @@ def check_bad(key):
check_bad("bad_leading_zero_neg") # also after a sign
check_bad("bad_leading_zero_08") # not octal, so it used to fall back to float
check_bad("bad_leading_zero_float") # nor on the integer part of a float
check_bad("bad_hex_no_digits") # prefix with no digits
check_bad("bad_hex_overflow") # does not fit in a 32-bit int

# Missing key returns default
got = supervisor.get_setting("no_such_key", "default")
Expand Down
6 changes: 6 additions & 0 deletions tests/circuitpython-manual/settings/settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ bad_leading_zero = 017
bad_leading_zero_neg = -017
bad_leading_zero_08 = 08
bad_leading_zero_float = 01.5
bad_hex_no_digits = 0x
bad_hex_overflow = 0x1_0000_0000
int_val = 42
neg_int_val = -7
hex_int_val = 0x1F
hex_leading_zero_val = 0x000F
neg_hex_int_val = -0x11
oct_int_val = 0o17
bin_int_val = 0b10001
underscore_int_val = 1_000
zero_int_val = 0
signed_zero_val = -0
bool_true = true
Expand Down
Loading