Не отключена ли проверка типа указателя в 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...
Это означает следующее:
Сначала проверяется, является ли аргумент CDatum (прокси для некоторых необработанных данных в формате C и связанного с ним CType).
Если это так, он проверяет, совпадает ли тип с формальным определением в прототипе внешнего метода (self).
Если нет, то это может быть аргумент void *, и в этом случае принимается любой указатель (проверено, является ли он указателем в коде, который я перерезал), за исключением случаев, когда это указатель на const.
Существует первое несоответствие: оно должно проверить, является ли формальное определениеconst void *
и принять любой указатель на const в этом случае... Но это не имеет большого значения, у нас редко есть фактический аргумент, объявленный const.Если нет, он проверяет, является ли массив (например, 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 * был способом сделать это в любом случае)... Сейчас у меня нет хорошего решения для этого из-за цикла событий,