На сайте, который поддерживал программист, пользователи могли оставлять комментарии. В какой-то момент было решено помечать комментарии, содержащие упоминания брендов, как возможный спам.
Программист реализовал простую проверку “текст содержит слово из списка запрещенных”, изменения в коде получились совсем небольшими:
--- SomeModule.pm (revision 12345)
+++ SomeModule.pm (working copy)
@@ -6,6 +6,43 @@
+{
+my $brand_name_regexp = qr/
+\b(?:
+ autocad |
+ benetton |
+ cisco |
+ dodge |
+ ericsson |
+ forbes |
+ gorenje |
+ hasselblad |
+ ikea |
+ jaguar |
+ kyocera |
+ lego |
+ manfrotto |
+ nokian |
+ oracle |
+ rolex |
+ salamander |
+ swarovski |
+ toshiba |
+ ubiquam |
+ volkswagen |
+ wallmart |
+ yamamoto |
+ zippo
+)\b
+/xi;
+
+sub contains_brand_name
+{
+ return $_[0] =~ $brand_name_regexp;
+}
+}
@@ -53,8 +90,10 @@
push @posts, {
text => $_,
+ contains_brand_name => contains_brand_name($_),
word_count => scalar split(/\W+/),
};
}
И что же, вы думаете, случилось дальше?
Во-первых, был весьма высок процент ошибок типа false negative (текст был хороший, но помечался как содержащий слова из списка).
Во-вторых, если присмореться, в массиве @posts
встречались
вот такие нелепые хеши:
{
'4' => undef,
'text' => "отличный штатив, всем рекомендую",
'contains_brand_name' => 'word_count'
}
Почему 'word_count'
в contains_brand_name
?
Откуда ключ 4
?
Источник проблем – функция contains_brand_name
,
а точнее – ее поведение в списковом контексте.
В скалярном контексте сопоставление с шаблоном возвращает истину или ложь – признак успешности сопоставления. В списковом же контексте возвращается список строк, которые были захвачены скобками, или пустой список в случае неудачного сопоставления.
Конструирование же хеша предоставляет как раз списковый контекст, так что хеш
{
text => $_,
contains_brand_name => contains_brand_name($_),
word_count => scalar split(/\W+/),
}
превращается в
{
text => $_,
contains_brand_name => word_count => scalar split(/\W+/),
}
, а так как =>
– это всего лишь запятая,
дополнительно заключающая свой левый операнд в кавычки,
хеш получается таким:
{
text => $_,
contains_brand_name => 'word_count',
scalar split(/\W+/) => undef,
}
Конечно, это не то, на что рассчитвал программист.
Кстати, если программист использует use warnings
,
то при выполнении кода увидит по крайней мере предупреждение
Odd number of elements in anonymous hash
.
Для исправление программы стоило бы поменять код функции contains_brand_name
,
и сопоставление с регулярным выражением выполнять всегда
в скалярном контексте:
return scalar $_[0] =~ $brand_name_regexp;