Python 3 не интернирует bytes
объекты так, как он str
.Вместо этого он хранит статический массив из них так же, как и для int
.
.С другой стороны, это означает, что нет таблицы (с API) для манипулирования.С другой стороны, это означает, что если вы можете найти статический массив, вы можете исправить его так же, как и для целочисленных, потому что индекс массива и символьное значение строки должны быть идентичны.
Если вы посмотрите на bytesobject.c
, массив будет объявлен сверху:
static PyBytesObject *characters[UCHAR_MAX + 1];
… а затем, например, в PyBytes_FromStringAndSize
:
if (size == 1 && str != NULL &&
(op = characters[*str & UCHAR_MAX]) != NULL)
{
#ifdef COUNT_ALLOCS
one_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
Обратите внимание, что массив равен static
, поэтому он недоступен извне этого файла и что он все еще пересчитывает объекты, поэтому вызывающие (даже внутренние компоненты в интерпретаторе, а тем более расширение C API) не могутскажи, что происходит что-то особенное.
Итак, нет "правильного" способа убрать это.
Но если вы хотите стать хакером ...
Если у вас естьссылка на любой из байтов с одним символом, и вы знаете, каким символом он должен был быть, вы можете добраться до начала массива и затем очистить все это.
Если вы не испортилидаже больше, чем вы думаете, вы можете простовозьмите один символ bytes
и вычтите символ, которым он должен был быть .PyBytes_FromStringAndSize("a", 1)
вернет объект, который должен быть 'a'
, даже если это произойдет с на самом деле hold 'b'
.Откуда мы это знаем?Потому что это именно та проблема, которую вы пытаетесь решить.
На самом деле, возможно, есть способы, которыми вы могли бы сломать вещи еще хуже ... которые кажутся маловероятными, но для безопасности давайте использовать персонажа, которого выменее вероятно, что он сломался, чем a
, как \x80
:
PyBytesObject *byte80 = (PyBytesObject *)PyBytes_FromStringAndSize("\x80", 1);
PyBytesObject *characters = byte80 - 0x80;
Единственное другое предостережение: если вы попытаетесь сделать это из Python с ctypes
вместо кода C, это будеттребуется дополнительная осторожность, 1 , но так как вы не используете ctypes
, давайте не будем об этом беспокоиться.
Итак, теперь у нас есть указатель на characters
, мы можем идтиЭто.Мы не можем просто удалить объекты, чтобы «удалить их», потому что это будет мешать любому, кто имеет ссылку на какой-либо из них, и, вероятно, приведет к segfault.Но мы не должны.Любой объект, который находится в таблице, мы знаем, каким он должен быть - characters[i]
должен быть с одним символом bytes
, чей единственный символ - i
.Так что просто установите его обратно, с циклом примерно так:
for (size_t char i=0; i!=UCHAR_MAX; i++) {
if (characters[i]) {
// do the same hacky stuff you did to break the string in the first place
}
}
Это все, что нужно.
Ну, за исключением компиляции. 2
К счастью, в интерактивном интерпретаторе каждый полный оператор верхнего уровня является собственным модулем компиляции, так что ... вы должны быть в порядке с любой новой строкой, которую вы вводите после запуска исправления.
Но модуль, который вы импортировали, должен был быть скомпилирован, пока у вас были разбитые строки?Вы, вероятно, испортили его константы.И я не могу придумать хороший способ убрать это, кроме как принудительно перекомпилировать и повторно импортировать каждый модуль.
1.Компилятор может превратить ваш b'\x80'
аргумент в неправильную вещь еще до того, как он доберется до вызова C.И вы будете удивлены во всех местах, где, по вашему мнению, вы проходите вокруг c_char_p
, и он действительно волшебным образом превращается в bytes
и обратно.Вероятно, лучше использовать POINTER(c_uint8)
.
2.Если вы скомпилировали некоторый код с b'a'
, массив consts должен иметь ссылку на b'a'
, которая будет исправлена.Но, поскольку bytes
известны как неизменяемые для компилятора, если он знает, что b'a' == b'b'
, он может вместо этого хранить указатель на синглтон b'b'
, по той же причине, что 123456 is 123456
верно, и в этом случае исправлениеb'a'
может на самом деле не решить проблему.