Skip to content

Commit 75ce925

Browse files
committed
objstr: Implement "%(key)s" % {} formatting for strings and dicts.
Also, make sure that args to "*" format specifiers are bounds-checked properly and don't lead for segfaults in case of mismatch.
1 parent 1e82ef3 commit 75ce925

File tree

5 files changed

+76
-17
lines changed

5 files changed

+76
-17
lines changed

py/builtinimport.c

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,6 @@ void do_load(mp_obj_t module_obj, vstr_t *file) {
163163
mp_globals_set(old_globals);
164164
}
165165

166-
// TODO: Move to objdict?
167-
STATIC inline mp_obj_t mp_obj_dict_get(mp_obj_t dict_in, mp_obj_t key) {
168-
mp_obj_dict_t *dict = dict_in;
169-
mp_map_elem_t *elem = mp_map_lookup(&dict->map, key, MP_MAP_LOOKUP);
170-
if (elem == NULL) {
171-
return elem;
172-
}
173-
return elem->value;
174-
}
175-
176166
mp_obj_t mp_builtin___import__(uint n_args, mp_obj_t *args) {
177167
#if DEBUG_PRINT
178168
printf("__import__:\n");

py/obj.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ typedef struct _mp_obj_dict_t {
505505
} mp_obj_dict_t;
506506
void mp_obj_dict_init(mp_obj_dict_t *dict, int n_args);
507507
uint mp_obj_dict_len(mp_obj_t self_in);
508+
mp_obj_t mp_obj_dict_get(mp_obj_t self_in, mp_obj_t index);
508509
mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value);
509510
mp_obj_t mp_obj_dict_delete(mp_obj_t self_in, mp_obj_t key);
510511
mp_map_t *mp_obj_dict_get_map(mp_obj_t self_in);

py/objdict.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ STATIC mp_obj_t dict_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
117117
}
118118
}
119119

120+
// TODO: Make sure this is inlined in dict_subscr() below.
121+
mp_obj_t mp_obj_dict_get(mp_obj_t self_in, mp_obj_t index) {
122+
mp_obj_dict_t *self = self_in;
123+
mp_map_elem_t *elem = mp_map_lookup(&self->map, index, MP_MAP_LOOKUP);
124+
if (elem == NULL) {
125+
nlr_raise(mp_obj_new_exception_msg(&mp_type_KeyError, "<value>"));
126+
} else {
127+
return elem->value;
128+
}
129+
}
130+
120131
STATIC mp_obj_t dict_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
121132
if (value == MP_OBJ_NULL) {
122133
// delete

py/objstr.c

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
#include "objstr.h"
4141
#include "objlist.h"
4242

43-
STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t *args);
43+
STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t *args, mp_obj_t dict);
4444
const mp_obj_t mp_const_empty_bytes;
4545

4646
// use this macro to extract the string hash
@@ -307,14 +307,19 @@ STATIC mp_obj_t str_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
307307
case MP_BINARY_OP_MODULO: {
308308
mp_obj_t *args;
309309
uint n_args;
310+
mp_obj_t dict = MP_OBJ_NULL;
310311
if (MP_OBJ_IS_TYPE(rhs_in, &mp_type_tuple)) {
311312
// TODO: Support tuple subclasses?
312313
mp_obj_tuple_get(rhs_in, &n_args, &args);
314+
} else if (MP_OBJ_IS_TYPE(rhs_in, &mp_type_dict)) {
315+
args = NULL;
316+
n_args = 0;
317+
dict = rhs_in;
313318
} else {
314319
args = &rhs_in;
315320
n_args = 1;
316321
}
317-
return str_modulo_format(lhs_in, n_args, args);
322+
return str_modulo_format(lhs_in, n_args, args, dict);
318323
}
319324

320325
//case MP_BINARY_OP_NOT_EQUAL: // This is never passed here
@@ -1125,7 +1130,7 @@ mp_obj_t mp_obj_str_format(uint n_args, const mp_obj_t *args) {
11251130
return s;
11261131
}
11271132

1128-
STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t *args) {
1133+
STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t *args, mp_obj_t dict) {
11291134
assert(MP_OBJ_IS_STR(pattern));
11301135

11311136
GET_STR_DATA_LEN(pattern, str, len);
@@ -1137,6 +1142,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t
11371142
pfenv_vstr.print_strn = pfenv_vstr_add_strn;
11381143

11391144
for (const byte *top = str + len; str < top; str++) {
1145+
mp_obj_t arg = MP_OBJ_NULL;
11401146
if (*str != '%') {
11411147
vstr_add_char(vstr, *str);
11421148
continue;
@@ -1148,9 +1154,21 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t
11481154
vstr_add_char(vstr, '%');
11491155
continue;
11501156
}
1151-
if (arg_i >= n_args) {
1152-
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "not enough arguments for format string"));
1157+
1158+
// Dictionary value lookup
1159+
if (*str == '(') {
1160+
const byte *key = ++str;
1161+
while (*str != ')') {
1162+
if (str >= top) {
1163+
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "incomplete format key"));
1164+
}
1165+
++str;
1166+
}
1167+
mp_obj_t k_obj = mp_obj_new_str((const char*)key, str - key, true);
1168+
arg = mp_obj_dict_get(dict, k_obj);
1169+
str++;
11531170
}
1171+
11541172
int flags = 0;
11551173
char fill = ' ';
11561174
bool alt = false;
@@ -1169,6 +1187,9 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t
11691187
int width = 0;
11701188
if (str < top) {
11711189
if (*str == '*') {
1190+
if (arg_i >= n_args) {
1191+
goto not_enough_args;
1192+
}
11721193
width = mp_obj_get_int(args[arg_i++]);
11731194
str++;
11741195
} else {
@@ -1181,6 +1202,9 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t
11811202
if (str < top && *str == '.') {
11821203
if (++str < top) {
11831204
if (*str == '*') {
1205+
if (arg_i >= n_args) {
1206+
goto not_enough_args;
1207+
}
11841208
prec = mp_obj_get_int(args[arg_i++]);
11851209
str++;
11861210
} else {
@@ -1195,7 +1219,15 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t
11951219
if (str >= top) {
11961220
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "incomplete format"));
11971221
}
1198-
mp_obj_t arg = args[arg_i];
1222+
1223+
// Tuple value lookup
1224+
if (arg == MP_OBJ_NULL) {
1225+
if (arg_i >= n_args) {
1226+
not_enough_args:
1227+
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "not enough arguments for format string"));
1228+
}
1229+
arg = args[arg_i++];
1230+
}
11991231
switch (*str) {
12001232
case 'c':
12011233
if (MP_OBJ_IS_STR(arg)) {
@@ -1284,7 +1316,6 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t
12841316
"unsupported format character '%c' (0x%x) at index %d",
12851317
*str, *str, str - start_str));
12861318
}
1287-
arg_i++;
12881319
}
12891320

12901321
if (arg_i != n_args) {

tests/basics/string-format-modulo.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,29 @@
4848
print("%#6o" % 18)
4949
print("%#6x" % 18)
5050
print("%#06x" % 18)
51+
52+
print("%*d" % (5, 10))
53+
print("%*.*d" % (2, 2, 20))
54+
# TODO: Formatted incorrectly
55+
#print("%*.*d" % (5, 8, 20))
56+
57+
# Cases when "*" used and there's not enough values total
58+
try:
59+
print("%*s" % 5)
60+
except TypeError:
61+
print("TypeError")
62+
try:
63+
print("%*.*s" % (1, 15))
64+
except TypeError:
65+
print("TypeError")
66+
67+
print("%(foo)s" % {"foo": "bar", "baz": False})
68+
try:
69+
print("%(foo)s" % {})
70+
except KeyError:
71+
print("KeyError")
72+
# Using in "*" with dict got to fail
73+
try:
74+
print("%(foo)*s" % {"foo": "bar"})
75+
except TypeError:
76+
print("TypeError")

0 commit comments

Comments
 (0)