Comments 50
Внезапно читая в тексте упоминание dtrace – подсознательно ожидал увидеть упоминание darkproger'а. Так и вышло.
А вообще – отличное расследование, но я так и не уловил конец. Кто таки вызывал -resizeToContents неправильно?
А вообще – отличное расследование, но я так и не уловил конец. Кто таки вызывал -resizeToContents неправильно?
Вызывал кто-то глубоко в коде, не используя результат вызова (прямо как в примере). А причина, почему вызывалось через performSelector — скорее всего из-за использования приватного метода. Проще, наверное было сделать так, чем поправить библиотеку, вынести метод в заголовок, сапдейтиться и использовать правильный метод. Кто знает ;)
Я еще поищу, но там было что-то вроде
Я еще поищу, но там было что-то вроде
if ([label respondsToSelector:@selector(resizeToContents)]) {
[label resizeToContents];
}
Хорошая статья!
Эпичный подход к поиску багов)
>Мораль: Читайте документацию внимательно.
Мораль на самом деле: в некоторых языках можно выстрелить себе в ногу из любого положения и даже с закрытыми глазами. Более того, в ряде из них вы обязательно это сделаете, даже если не будете трогать пистолет.
Мораль на самом деле: в некоторых языках можно выстрелить себе в ногу из любого положения и даже с закрытыми глазами. Более того, в ряде из них вы обязательно это сделаете, даже если не будете трогать пистолет.
Мда, если кто-то в вашем присутствии подумает вслух «А вдруг я программист приложений для IOS?», просто дайте ему ссылку на этот топик
1) Т.е.
2) Использовать
В таком случае было бы логично, если б реализация
Странно, что по-умолчанию так не сделано — вместо этого имеем грабли которые так лежат, что на них просто нельзя не наступить.
resizeToContents
вызывался через performSelector:
? Из статьи это не очевидно.2) Использовать
performSelector
для методов, возвращающих что-то кроме id
нельзя.В таком случае было бы логично, если б реализация
performSelector
проверяла NSMethodSignature
селектора и использовала NSInvocation
когда нужно.Странно, что по-умолчанию так не сделано — вместо этого имеем грабли которые так лежат, что на них просто нельзя не наступить.
Первый вопрос снимается. Ответ нашел.
Ну как бы там id написан, потому и не проверяет. Хотя что мне в Obj-C не нравится — там много неопределённого поведения в тех местах, где нужно было бы кинуть исключение.
Дешевле (в плане быстродействия) возложить эти проверки на разработчика, также как к примеру со всякими memcpy, strcpy и т.п.
А разве XCode не давал warning на performSeletor? Возвращает-то он id, а id присваивается float'у. Неявное преобразование типов.
Насколько я понял -performSeletor: вызывался для метода, который возвращает CGFloat без прививания переменной. В этом случае XCode не выдает даже warning'а. Что то вроде:
[self performSelector:@selector(selectorReturnCGFloat)];
Там возвращаемое значение ничему не присваивалось, см. habrahabr.ru/post/161921/#comment_5560051. Потому и не было warning'а. Было бы неплохо, если б хотя бы статический анализатор предупреждал о такой ошибке.
Интересно, выдавал ли предупреждение анализатор?
Шикарная статья! Великолепный подход к поиску багов!
Получается вся проблема лечится использованием NSInvocation вместо селектора? :)
А можно всегда в подобных конструкциях:
использовать NSInvocation? Просто, чтобы перестраховываться.
Получается вся проблема лечится использованием NSInvocation вместо селектора? :)
А можно всегда в подобных конструкциях:
if ([label respondsToSelector:@selector(resizeToContents)]) {
[label resizeToContents];
}
использовать NSInvocation? Просто, чтобы перестраховываться.
Конкретно в этом случае — не надо.
Надо в случае такого кода:
А в общем случае, нужно просто смотреть, что возвращает метод
Надо в случае такого кода:
if ([label respondsToSelector:@selector(resizeToContents)]) {
[label performSelector:@selector(resizeToContents)];
}
А в общем случае, нужно просто смотреть, что возвращает метод
Прекрасное детективное расследование ;)
P.S. Сам как-то 2 дня убил на бодание с багом в большом приложении, которое в итоге свелось к багу в gcc, из-за которого он неудачно оптимизировал деление на константу на PPC архитектуре (через несколько умножений / сложений / других операций). С -O0 все работало, с -O2 оптимизатор преставлял команды так, что деление происходило уже не верно, а приводило это все к тому, только в одном единственном месте, size от std::vector-а начинал возвращать бред, и дальше по цепочке. Это было, на OS X 10.3 еще кажется, на PowerPC G4 маках. Более свежие версии gcc потом этот баг уже исправляли, но было весело все равно.
P.S. Сам как-то 2 дня убил на бодание с багом в большом приложении, которое в итоге свелось к багу в gcc, из-за которого он неудачно оптимизировал деление на константу на PPC архитектуре (через несколько умножений / сложений / других операций). С -O0 все работало, с -O2 оптимизатор преставлял команды так, что деление происходило уже не верно, а приводило это все к тому, только в одном единственном месте, size от std::vector-а начинал возвращать бред, и дальше по цепочке. Это было, на OS X 10.3 еще кажется, на PowerPC G4 маках. Более свежие версии gcc потом этот баг уже исправляли, но было весело все равно.
Занятно, есть ряд неточностей, но всё равно неплохо.
Правда имхо стоит еще написать почему не работает на симуляторе, а работает на устройстве.
Это связано с тем что в arm совершенно другая модель работы с float'ами.
Там используется набор регистров (S0..16, D0..16 или D0..32, если есть расширенный SIMD), и нет никакого fpu-стека, поэтому неиспользование значения после вызова функции никак не влияет на состояние VFP. В любом случае, в регистре остаётся результат, даже если прочитали возвращаемое значение.
Правда имхо стоит еще написать почему не работает на симуляторе, а работает на устройстве.
Это связано с тем что в arm совершенно другая модель работы с float'ами.
Там используется набор регистров (S0..16, D0..16 или D0..32, если есть расширенный SIMD), и нет никакого fpu-стека, поэтому неиспользование значения после вызова функции никак не влияет на состояние VFP. В любом случае, в регистре остаётся результат, даже если прочитали возвращаемое значение.
interface UIView (SFAdditions)
/**
Handy getters and setters
*/
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat height;
@property (nonatomic, assign) CGFloat left;
@property (nonatomic, assign) CGFloat right;
@property (nonatomic, assign) CGFloat bottom;
@property (nonatomic, assign) CGFloat top;
@property (nonatomic, assign) CGSize size;
end
Безотносительно бага (который, кстати, сказать, не был бы возможен, если с самого начала читать доки), за вышеприведнный код я бы руки отрывал. Нельзя делать категории к системным объектам со столь общими именами. Добавляйте префикс (постфикс, что угодно) к именам методов. Иначе на хабре будут и дальше детективные раследования.

Занятный материал. Самое интересное, как такое обходить и не только в objective-c.
Очень «просто» обходить — включать мозг и читать документацию.
Если бы этого было достаточно, то мы бы жили в идеальном мире.
О! Поди и без багов можно также писать? Научите, а?
Вера в непокобелимость людей и восприятие их как роботов, которые не подвержены челофактору — есть первый шаг на пути в п… ц.
Вера в непокобелимость людей и восприятие их как роботов, которые не подвержены челофактору — есть первый шаг на пути в п… ц.
Очень «просто» исправлять.
Обходить это не помогает, т.к. постоянная концентрация на 100% человеку не под силу.
Если бы все возможные факторы риска были записаны в таблицу в памяти, по которой в каждой точке можно бы было сверять… Но объем оперативных данных в мозгу слишком мал для такой задачи :-)
Обходить это не помогает, т.к. постоянная концентрация на 100% человеку не под силу.
Если бы все возможные факторы риска были записаны в таблицу в памяти, по которой в каждой точке можно бы было сверять… Но объем оперативных данных в мозгу слишком мал для такой задачи :-)
как хорошо, что я не программирую на Objective C
Замечательная статья!
Богатый рантайм ObjC даёт питательную почву для всевозможных «магических» багов.
Кстати, фундаментальный подход к их обнаружению — редкость в наши дни. Даже в lldb/gdb многие не пользуются функциями типа «mem r -c 512», не говоря уже о просмотре регистров.
Богатый рантайм ObjC даёт питательную почву для всевозможных «магических» багов.
Кстати, фундаментальный подход к их обнаружению — редкость в наши дни. Даже в lldb/gdb многие не пользуются функциями типа «mem r -c 512», не говоря уже о просмотре регистров.
отличное расследование, учтём.
Классическая ошибка. Не первый раз читаю расследование на подобную тему, но эта была занимательная! Хороший детективчик :)
О, у меня как раз сейчас тоже в коде, доставшемся от индусов, периодически во фрэйме ячейки NAN случается. Правда, NSLog не спасает. Поищу возможные performSelector-ы и поменяю на прямой вызов методов. Спасибо!
Вот такой подход к решению проблем и отличает быдлокодеров от настоящих Разработчиков.
А никого не смущает тот факт, что метод
Но расследование интересное, да.
resizeToContents
вообще возвращает значение? Судя по названию метода я бы сказал, что данный метод вообще не должен возвращать значений.Но расследование интересное, да.
Спасибо. Радует, что есть еще настоящие программисты, а не «работает и по...»)
Напомнило как для PS2 игры искал баг с «иногда разворачивающимся рандомно мотоциклом»:
после недели логгирования (уменьшающего вероятность бага) оказалось, что компилятор в коде прерывания стримящегося звука использовал float регистры (не восстанавливаются после прерывания) тупо для более быстрого копирования мелких целочисленных структур =)
Напомнило как для PS2 игры искал баг с «иногда разворачивающимся рандомно мотоциклом»:
после недели логгирования (уменьшающего вероятность бага) оказалось, что компилятор в коде прерывания стримящегося звука использовал float регистры (не восстанавливаются после прерывания) тупо для более быстрого копирования мелких целочисленных структур =)
а еще есть одна штука с CGRect интересная:
When accessing the x, y, width, or height of a CGRect, always use the CGGeometry functions instead of direct struct member access. From Apple's CGGeometry reference:
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
For example:
Not:
github.com/NYTimes/objective-c-style-guide советую почитать
When accessing the x, y, width, or height of a CGRect, always use the CGGeometry functions instead of direct struct member access. From Apple's CGGeometry reference:
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
For example:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
Not:
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
github.com/NYTimes/objective-c-style-guide советую почитать
Спасибо огромное за статью. Очень похоже что я нарвался на этот же баг, но performSelector-а ни одного во всем проекте нет. Пока что добавлю NSLog. Даже не знаю как быть — в проекте полно сторонних либ. Сам падение получить не могу, анализирую логи с крашлитикса.
Sign up to leave a comment.
История одного Crash-а, и NSLog'а его лечившего