Не отключена ли проверка типа указателя в DLL/C-Connect, и это нормально?

После этого как-то связанного вопроса Почему я не могу передать UninterpretedBytes в void* через DLL/C-Connect? там, где мы увидели, что я не могу передать массив битов Smalltalk параметру void *, я дополнительно проанализировал метод, отвечающий за проверку совместимости описания формального указателя с эффективным объектом, переданным в качестве аргумента, и думаю, что обнаружил еще одну сомнительную часть:

CPointerType>>coerceForArgument: anObject
    ...snip...
    (anObject isKindOf: self defaultDatumClass)
        ifTrue: [
            (referentType = anObject type referentType
                or: [(referentType isVoid
                    and: [anObject type referentType isConstant not])
                or: [anObject type isArray not
                    or: [anObject type baseArrayType = referentType]]])
            ifTrue: [^anObject asPointer]].
    ...snip...

Это означает следующее:

  1. Сначала проверяется, является ли аргумент CDatum (прокси для некоторых необработанных данных в формате C и связанного с ним CType).

  2. Если это так, он проверяет, совпадает ли тип с формальным определением в прототипе внешнего метода (self).

  3. Если нет, то это может быть аргумент void *, и в этом случае принимается любой указатель (проверено, является ли он указателем в коде, который я перерезал), за исключением случаев, когда это указатель на const.
    Существует первое несоответствие: оно должно проверить, является ли формальное определение const void * и принять любой указатель на const в этом случае... Но это не имеет большого значения, у нас редко есть фактический аргумент, объявленный const.

  4. Если нет, он проверяет, является ли массив (например, int foo[2]) или массивом, тип которого совпадает (тот же базовый тип и размерность).

Итак, если формальное определение, например, struct {int a; char *b} *fooи что я передаю double * barтип не совпадает, несоответствие квалификатора const отсутствует, а параметр не является массивом, вывод: мы можем безопасно передать его без дальнейшей проверки!

Это своего рода псевдоним указателя. У нас нет оптимизирующего компилятора, делающего какие-либо предположения об отсутствии такого псевдонима в Smalltalk, так что это не будет источником неопределенного поведения. Вполне возможно, что мы намеренно хотим вызвать такого рода грязный reinterpret_cast по неясным причинам (поскольку мы можем явно приводить CDatum, я бы предпочел явный способ).

НО, возможно, мы полностью испортили и передали неправильный объект с неправильным типом, неправильным измерением и что адрес foo->b в моем примере выше будет содержать какой-то переосмысленный мусор, если указатель выровнен на 32 бита, или быть полностью неопределенным на 64-битной машине (потому что за пределами размера double).

Компилятор A C наверняка предупредит меня о псевдонимах и предотвратит создание артефакта с помощью -Wall -Werror.
Меня беспокоит то, что я даже не получил предупреждение...

Это звучит правильно?

1 ответ

Краткий ответ: исправлять это поведение нельзя, потому что от этого зависят некоторые вещи пользовательского интерфейса низкого уровня (цикл обработки событий). Мы даже не можем ввести предупреждение или что-то еще.

Более длинная история: я попытался переписать весь метод с двойной диспетчеризацией (спросите у anObject, совместим ли он с формальным CPointerType, вместо того, чтобы тестировать каждый возможный класс Object с повторным isKindOf:).

Но, опуская позорное допуск на наложение указателя, он неизменно привинчивал мое изображение Macosx 8.3 с тоннами открывающихся пустых окон и блокировал бесперебойный интерфейс...

После инструментирования кажется, что цикл событий полагается на него и передает aString asNSString (который преобразуется в utf16, но сохраняется в ByteArray и, таким образом, объявляется как unsigned char *), в метод Objective C, ожидающий unsigned short *.

Это случай, когда псевдоним указателя является доброкачественным, пока мы пропускаем хорошие байты.

Если я попытаюсь исправить asNSString с правильным приведением к unsigned short *, то пользовательский интерфейс блокируется (я не знаю почему, но это потребовало бы отладки на уровне VM).

Вывод: это правда, что некоторые различия, такие как (unsigned char *) vs (char *), могут быть уместны и их лучше не запрещать полностью (независимо от того, подписан char или нет, зависит от платформы, и не все библиотеки имеют четко определенные API). То же самое касается платформозависимого широкого символа, у нас есть методы преобразования, создающие хорошие байты, но не хорошие типы. В конечном итоге мы могли бы сделать исключение для char *, как мы сделали для void * (до того, как была введена void *, char * был способом сделать это в любом случае)... Сейчас у меня нет хорошего решения для этого из-за цикла событий,

Другие вопросы по тегам