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: 5 additions & 2 deletions docs/environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ 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``, ``011`` is 9, not 11.
* 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.
* Comments are allowed.
Expand Down
6 changes: 5 additions & 1 deletion shared-bindings/supervisor/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,15 @@ 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``.
//|
//| Leading zeros are not allowed in integers or in the integer part of floats:
//| ``017`` and ``01.5`` are errors.
//|
//| Example::
//|
//| # settings.toml:
Expand Down
101 changes: 86 additions & 15 deletions supervisor/shared/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,49 @@ 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 if ((dig | 0x20) >= 'a') {
// Lowercase; anything past 'z' ends up >= base.
dig = (dig | 0x20) - 'a' + 10;
} else {
break;
}
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];
mp_uint_t c = 0;
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]) {
return SETTINGS_ERR_BAD_VALUE;
uint8_t hex = get_next_byte(file_handle);
if (!unichar_isxdigit(hex)) {
return SETTINGS_ERR_BAD_VALUE;
}
c = c * 16 + unichar_xdigit_value(hex);
}
if (c >= 0x110000) {
return SETTINGS_ERR_UNICODE;
Expand Down Expand Up @@ -403,15 +436,44 @@ static settings_err_t get_int(const char *key, mp_int_t *value) {
if (quoted) {
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;
} else if (unichar_isdigit(str[1])) {
// TOML does not allow leading zeros on decimal integers.
return SETTINGS_ERR_BAD_VALUE;
}
}
if (end == buf || *end) { // If the whole buffer was not consumed it's an error
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;
}
*value = (mp_int_t)num;
while (unichar_isspace(*str)) {
str++;
}
if (*str) { // If the whole buffer was not consumed it's an error
return SETTINGS_ERR_BAD_VALUE;
}
*value = (mp_int_t)(neg ? 0 - magnitude : magnitude);
return SETTINGS_OK;
}

Expand Down Expand Up @@ -452,14 +514,23 @@ settings_err_t settings_get_bool(const char *key, bool *value) {
}

#if MICROPY_PY_BUILTINS_FLOAT
// TOML does not allow leading zeros on decimal integers or on the integer part of floats.
// https://toml.io/en/v1.0.0#integer and https://toml.io/en/v1.0.0#float
static bool has_leading_zero(const char *str) {
if (*str == '+' || *str == '-') {
str++;
}
return str[0] == '0' && unichar_isdigit(str[1]);
}

static settings_err_t get_float(const char *key, mp_float_t *value) {
char buf[48];
bool quoted;
settings_err_t result = settings_get_buf_terminated(key, buf, sizeof(buf), &quoted);
if (result != SETTINGS_OK) {
return result;
}
if (quoted) {
if (quoted || has_leading_zero(buf)) {
return SETTINGS_ERR_BAD_VALUE;
}

Expand Down
16 changes: 16 additions & 0 deletions tests/circuitpython-manual/settings/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@ def check(key, expected):
check("int_val", 42)
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)
check("bool_false", False)
check("float_val", 3.14)
check("neg_float_val", -1.5)
check("frac_float_val", 0.5)
check("sci_float_val", 6.626e-34)
# math.nan and math.inf are not always available
check("pos_inf", float("inf"))
Expand All @@ -50,6 +58,14 @@ def check_bad(key):
check_bad("bad_float_junk") # float with trailing garbage
check_bad("bad_inf_junk") # inf with trailing characters
check_bad("bad_lone_sign") # bare + with nothing after it
check_bad("bad_leading_zero") # TOML does not allow leading zeros
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
check_bad("bad_digit_at") # '@' is not a digit
check_bad("bad_unicode_underscore") # no underscores in \u escapes

# Missing key returns default
got = supervisor.get_setting("no_such_key", "default")
Expand Down
16 changes: 16 additions & 0 deletions tests/circuitpython-manual/settings/settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,29 @@ bad_word = hello
bad_float_junk = 1.2abc
bad_inf_junk = infoo
bad_lone_sign = +
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
bad_digit_at = 12@
bad_unicode_underscore = "\u0_41"
int_val = 42
neg_int_val = -7
hex_int_val = 0x1F
Comment thread
dhalbert marked this conversation as resolved.
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
bool_false = false
float_val = 3.14
neg_float_val = -1.5
frac_float_val = 0.5
sci_float_val = 6.626e-34
pos_inf = inf
neg_inf = -inf
Expand Down
Loading