Русский
Русский
English
Статистика
Реклама

Топ-10

Топ 10 ошибок в C проектах за 2020 год

18.12.2020 10:08:54 | Автор: admin
image1.png

За окном зима, год стремится к завершению, а значит, пришло время рассмотреть самые интересные ошибки, обнаруженные анализатором PVS-Studio в 2020 году.

Стоит отметить, что прошедший год ознаменовался большим количеством новых диагностических правил, срабатывания которых позволили им попасть в данный топ. Также мы продолжаем улучшать ядро анализатора и добавлять новые сценарии его использования, обо всём этом можно почитать в нашем блоге. Если вам интересны другие поддерживаемые нашим анализатором языки (C, C# и Java), обратите внимание на статьи моих коллег. Теперь же перейдём непосредственно к самым запомнившимся мне багам, найденным PVS-Studio за прошедший год.

Десятое место: Деление по модулю на единицу


V1063 The modulo by 1 operation is meaningless. The result will always be zero. llvm-stress.cpp 631

void Act() override {  ....  // If the value type is a vector, and we allow vector select,  // then in 50% of the cases generate a vector select.  if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 1)) {    unsigned NumElem =        cast<FixedVectorType>(Val0->getType())->getNumElements();    CondTy = FixedVectorType::get(CondTy, NumElem);  }  ....}

Разработчик хотел получить случайное значение в диапазоне от 0 до 1, использовав деление по модулю. Однако операция вида X%1 всегда вернёт 0. В данном случае правильно было бы переписать условие следующим образом:

if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 2))

Эта ошибка вошла в топ из статьи: "Проверка Clang 11 с помощью PVS-Studio".

Девятое место: Четыре проверки


На следующий участок кода PVS-Studio выдал четыре предупреждения:

  • V560 A part of conditional expression is always true: x >= 0. editor.cpp 1137
  • V560 A part of conditional expression is always true: y >= 0. editor.cpp 1137
  • V560 A part of conditional expression is always true: x < 40. editor.cpp 1137
  • V560 A part of conditional expression is always true: y < 30. editor.cpp 1137

int editorclass::at( int x, int y ){  if(x<0) return at(0,y);  if(y<0) return at(x,0);  if(x>=40) return at(39,y);  if(y>=30) return at(x,29);  if(x>=0 && y>=0 && x<40 && y<30)  {      return contents[x+(levx*40)+vmult[y+(levy*30)]];  }  return 0;}

Все предупреждения относятся к последнему if-выражению. Проблема в том, что все четыре проверки, которые в нём выполняются, всегда будут возвращать true. Не сказал бы, что это серьезная ошибка, но получилось довольно забавно. В общем, данные проверки являются избыточными и их можно убрать.

Эта ошибка вошла в топ из статьи: "VVVVVV??? VVVVVV!!!".

Восьмое место: delete вместо delete[]


V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] poke_data;'. CCDDE.CPP 410

BOOL Send_Data_To_DDE_Server (char *data, int length, int packet_type){  ....  char *poke_data = new char [length + 2*sizeof(int)]; // <=  ....  if(DDE_Class->Poke_Server( .... ) == FALSE) {    CCDebugString("C&C95 - POKE failed!\n");    DDE_Class->Close_Poke_Connection();    delete poke_data;                                  // <=    return (FALSE);  }  DDE_Class->Close_Poke_Connection();  delete poke_data;                                    // <=  return (TRUE);}

Анализатор обнаружил ошибку, связанную с тем, что память выделена и освобождена несовместимыми между собой способами. Для освобождения памяти, выделенной под массив, следует использовать оператор delete[], а не delete.

Эта ошибка вошла в топ из статьи: "Код игры Command & Conquer: баги из 90-х. Том второй".

Седьмое место: Выход за границу буфера


Рассмотрим функцию net_hostname_get, которая будет использоваться дальше.

#if defined(CONFIG_NET_HOSTNAME_ENABLE)const char *net_hostname_get(void);#elsestatic inline const char *net_hostname_get(void){  return "zephyr";}#endif

В данном случае при препроцессировании выбирался вариант, относящийся к ветке #else. То есть, в препроцессированном файле функция реализуется так:

static inline const char *net_hostname_get(void){  return "zephyr";}

Функция возвращает указатель на массив из 7 байт (учитываем терминальный ноль в конце строки).

Теперь рассмотрим код, приводящий к выходу за границу массива.

static int do_net_init(void){  ....  (void)memcpy(hostname, net_hostname_get(), MAX_HOSTNAME_LEN);  ....}

Предупреждение PVS-Studio: V512 [CWE-119] A call of the 'memcpy' function will lead to the 'net_hostname_get()' buffer becoming out of range. log_backend_net.c 114

После препроцессирования MAX_HOSTNAME_LEN раскрывается следующим образом:

(void)memcpy(hostname, net_hostname_get(),    sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx"));

Соответственно, при копировании данных возникает выход за границу строкового литерала. Как это скажется на выполнении программы предсказать сложно, так как это приводит к неопределённому поведению.

Эта ошибка вошла в топ из статьи: "Исследуем качество кода операционной системы Zephyr".

Шестое место: Что-то очень странное


static char *mntpt_prepare(char *mntpt){  char *cpy_mntpt;  cpy_mntpt = k_malloc(strlen(mntpt) + 1);  if (cpy_mntpt) {    ((u8_t *)mntpt)[strlen(mntpt)] = '\0';    memcpy(cpy_mntpt, mntpt, strlen(mntpt));  }  return cpy_mntpt;}

Предупреждение PVS-Studio: V575 [CWE-628] The 'memcpy' function doesn't copy the whole string. Use 'strcpy / strcpy_s' function to preserve terminal null. shell.c 427

Кто-то пытался сделать аналог функции strdup, но у него это не получилось.

Начнём с предупреждения анализатора. Он сообщает, что функция memcpy копирует строчку, но не скопирует терминальный ноль, и это очень подозрительно.

Кажется, что этот терминальный 0 копируется здесь:

((u8_t *)mntpt)[strlen(mntpt)] = '\0';

Но нет! Здесь опечатка, из-за которой терминальный ноль копируется сам в себя! Обратите внимание, что запись происходит в массив mntpt, а не в cpy_mntpt. В итоге функция mntpt_prepare возвращает строку, незавершенную терминальным нулём.

На самом деле программист хотел написать так:

((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';

Однако всё равно не понятно, зачем сделано так сложно! Этот код можно упростить до следующего варианта:

static char *mntpt_prepare(char *mntpt){  char *cpy_mntpt;  cpy_mntpt = k_malloc(strlen(mntpt) + 1);  if (cpy_mntpt) {    strcpy(cpy_mntpt, mntpt);  }  return cpy_mntpt;}

Эта ошибка вошла в топ из вышеупомянутой статьи: "Исследуем качество кода операционной системы Zephyr".

Пятое место: Неправильная защита от переполнения


V547 [CWE-570] Expression 'rel_wait < 0' is always false. Unsigned type value is never < 0. os_thread_windows.c 359

static DWORDget_rel_wait(const struct timespec *abstime){  struct __timeb64 t;  _ftime64_s(&t);  time_t now_ms = t.time * 1000 + t.millitm;  time_t ms = (time_t)(abstime->tv_sec * 1000 +    abstime->tv_nsec / 1000000);  DWORD rel_wait = (DWORD)(ms - now_ms);  return rel_wait < 0 ? 0 : rel_wait;}

В данном случае переменная rel_wait имеет беззнаковый тип DWORD. А значит, сравнение rel_wait < 0 не имеет смысла, так как результатом всегда является истина.

Сама по себе ошибка не очень интересная. Зато интересно вышло с тем, как её попробовали исправить. Получилось, что изменения не исправили, а лишь упростили код. Подробнее про эту историю можно прочитать в статье моего коллеги: "Почему PVS-Studio не предлагает автоматические правки кода".

Ошибка же вошла в топ из статьи: "Статический анализ кода коллекции библиотек PMDK от Intel и ошибки, которые не ошибки".

Четвёртое место: Не пиши в std, брат


V1061 Extending the 'std' namespace may result in undefined behavior. sized_iterator.hh 210

// Dirty hack because g++ 4.6 at least wants// to do a bunch of copy operations.namespace std {inline void iter_swap(util::SizedIterator first,                      util::SizedIterator second){  util::swap(*first, *second);}} // namespace std

В статье, из которой взято срабатывание: "Анализ кода проекта DeepSpeech или почему не стоит писать в namespace std" подробно описано, почему не стоит поступать подобным образом.

Третье место: Скроллбар, который не смог


V501. There are identical sub-expressions to the left and to the right of the '-' operator: bufferHeight bufferHeight TermControl.cpp 592

bool TermControl::_InitializeTerminal(){  ....  auto bottom = _terminal->GetViewport().BottomExclusive();  auto bufferHeight = bottom;  ScrollBar().Maximum(bufferHeight - bufferHeight);  ScrollBar().Minimum(0);  ScrollBar().Value(0);  ScrollBar().ViewportSize(bufferHeight);  ....}

Это, что называется, "срабатывание с историей". В данном случае из-за ошибки не работал скроллбар в Windows Terminal. По мотивам данного бага написана целая статья, в которой мой коллега провёл исследование и разобрался почему так случилось. Заинтересовались? Вот она: "Скроллбар, который не смог".

Второе место: перепутали радиус и высоту


И опять речь пойдёт о нескольких предупреждениях анализатора:

  • V764 Possible incorrect order of arguments passed to 'CreateWheel' function: 'height' and 'radius'. StandardJoints.cpp 791
  • V764 Possible incorrect order of arguments passed to 'CreateWheel' function: 'height' and 'radius'. StandardJoints.cpp 833
  • V764 Possible incorrect order of arguments passed to 'CreateWheel' function: 'height' and 'radius'. StandardJoints.cpp 884

Привожу вызовы функции:

NewtonBody* const wheel = CreateWheel (scene, origin, height, radius);

А так выглядит её объявление:

static NewtonBody* CreateWheel (DemoEntityManager* const scene,  const dVector& location, dFloat radius, dFloat height)

При вызовах функций аргументы были перепутаны местами.

Эта ошибка вошла в топ из статьи: "Повторная проверка Newton Game Dynamics статическим анализатором PVS-Studio".

Первое место: Затирание результата


V519 The 'color_name' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 621, 627. string.cpp 627

static bool parseNamedColorString(const std::string &value,                                  video::SColor &color){  std::string color_name;  std::string alpha_string;  size_t alpha_pos = value.find('#');  if (alpha_pos != std::string::npos) {    color_name = value.substr(0, alpha_pos);    alpha_string = value.substr(alpha_pos + 1);  } else {    color_name = value;  }  color_name = lowercase(value); // <=  std::map<const std::string, unsigned>::const_iterator it;  it = named_colors.colors.find(color_name);  if (it == named_colors.colors.end())    return false;  ....}

Данная функция должна производить разбор названия цвета с параметром прозрачности и вернуть его шестнадцатеричный код. В зависимости от результата проверки условия в переменную color_name передается либо результат разбиения строки, либо копия аргумента функции.

Однако затем в функции lowercase() в нижний регистр переводится не сама полученная строка, а исходный аргумент функции. В результате мы просто потеряем цвет, который должна была вернуть parseNamedColorString().

color_name = lowercase(color_name);

Эта ошибка вошла в топ из статьи: "PVS-Studio: Анализ pull request-ов в Azure DevOps при помощи self-hosted агентов".

Заключение


За прошедший год мы нашли много ошибок в open source проектах. Это были привычные ошибки copy-paste, ошибки в константах, утечки памяти и множество других проблем. Наш анализатор не стоит на месте и в топе присутствует несколько срабатываний новых диагностик, написанных в этом году.

Надеюсь, вам понравились собранные ошибки. Лично мне они показались достаточно интересными. Но, конечно, ваше видение может отличаться от моего, поэтому вы можете составить свой "Tоп 10...", почитав статьи из нашего блога или посмотрев список ошибок, найденных PVS-Studio в open source проектах.

Также предлагаю вашему вниманию статьи с топ 10 C++ ошибок прошлых лет: 2016, 2017, 2018, 2019.


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Vladislav Stolyarov. Top 10 Bugs Found in C++ Projects in 2020.
Подробнее..

Топ 10 ошибок в проектах Java за 2020 год

28.12.2020 18:17:25 | Автор: admin
image1.png

Новый год неумолимо приближается а, значит, настало время подводить итоги. Продолжая традицию, мы прошлись по нашим статьям о проверках Java-проектов из мира open-source за этот год и составили рейтинг 10 самых интересных ошибок.

За уходящий год мы (Java-команда PVS-Studio) разобрали в наших статьях ошибки из пяти open-source проектов и совсем немного рассказали про нашу внутреннюю кухню:


Предлагаем читателю сначала ознакомиться с этими статьями и составить свой личный рейтинг, чтобы потом сравнить его с нашим и сказать, что мы неправы :).

Десятое место: "Обманчивое равенство"


Источник: Big/Bug Data: анализируем исходный код Apache Flink

V6001 There are identical sub-expressions 'processedData' to the left and to the right of the '==' operator. CheckpointStatistics.java(229)

@Overridepublic boolean equals(Object o) {  ....  CheckpointStatistics that = (CheckpointStatistics) o;  return id == that.id &&    savepoint == that.savepoint &&    triggerTimestamp == that.triggerTimestamp &&    latestAckTimestamp == that.latestAckTimestamp &&    stateSize == that.stateSize &&    duration == that.duration &&    alignmentBuffered == that.alignmentBuffered &&    processedData == processedData &&                // <=    persistedData == that.persistedData &&    numSubtasks == that.numSubtasks &&    numAckSubtasks == that.numAckSubtasks &&    status == that.status &&    Objects.equals(checkpointType, that.checkpointType) &&    Objects.equals(      checkpointStatisticsPerTask,       that.checkpointStatisticsPerTask);}

Простая и очень обидная ошибка из-за невнимательности: поле processedData сравнивается с самим собой. Из-за этой ошибки сравнение объектов типа CheckpointStatistics иногда будет выдавать ложноположительный результат. Но основная опасность этой опечатки состоит в том, что equals крайне активно используется в коллекциях, и некорректная реализация этого метода может привести к очень странному поведению, на отладку которого уйдёт огромное количество времени.

Хочу заметить, что ошибаться в функциях сравнения для разработчиков привычное дело. Мой коллега даже написал большую статью "Зло живет в функциях сравнения" с множеством примеров и объяснений.

Девятое место: "Недостижимый код"


Источник: Единороги на страже вашей безопасности: исследуем код Bouncy Castle.

V6019 Unreachable code detected. It is possible that an error is present. XMSSTest.java(170)

public void testSignSHA256CompleteEvenHeight2() {    ....    int height = 10;    ....    for (int i = 0; i < (1 << height); i++) {        byte[] signature = xmss.sign(new byte[1024]);        switch (i) {            case 0x005b:                assertEquals(signatures[0], Hex.toHexString(signature));                break;            case 0x0822:                assertEquals(signatures[1], Hex.toHexString(signature));                break;            ....        }    }}

Ветвь switch для значения i == 0x0822(2082) оказалась недостижимой. Как же так получилось?

Если обратить внимание на условие цикла 1 << height, где height всегда равен 10, то всё сразу встанет на свои места. Согласно условию цикла, счётчик i в цикле for не может быть больше, чем 1024 (1 << 10). Естественно, выполнение рассматриваемой ветви switch никогда не произойдет.

Восьмое место: "Проаннотированный метод"


Источник: Под капотом PVS-Studio для Java: разработка диагностик.

V6009 Collection is empty. The call of the 'clear' function is senseless. MetricRepositoryRule.java(90)

protected void after(){  this.metricsById.clear();  this.metricsById.clear();}

Часть наших диагностик сильно полагается на механизм аннотирования методов. Аннотации предоставляют дополнительную информацию анализатору об используемых методах, например:

  • Чистый ли это метод,
  • Какие накладываются ограничения на аргументы,
  • Возвращаемый результат,
  • и всякое прочее.

Некоторые аннотации анализатор выводит сам из исходного кода, некоторые мы проставляем вручную (например, для методов стандартной библиотеки). История этой ошибки началась с того, что мы не в полной мере проаннотировали метод Map#clear. После того, как мы это заметили и исправили, на наших тестовых проектах повылезали новые срабатывания, среди которых был и наш интересный случай.

На первый взгляд, повторная очистка словаря не ошибка. И мы бы даже решили, что это случайно продублированная строка, если бы не обратили внимание на поля класса:

private final Map<String, Metric> metricsByKey = new HashMap<>();private final Map<Long, Metric> metricsById = new HashMap<>();

У класса есть два поля с похожими именами metricsById и metricsByKey. Это и наталкивает на мысль, что автор кода хотел очистить оба словаря, но этого не произошло. Таким образом, два словаря, которые хранят связанные данные, будут рассинхронизированы после вызова after.

Седьмое место: "Ожидание / реальность"


Источник: Проверка WildFly сервера JavaEE приложений.

V6058 The 'equals' function compares objects of incompatible types: String, ModelNode. JaxrsIntegrationProcessor.java(563)

// Send value to RESTEasy only if it's not null, empty string, or the // default value.private boolean isTransmittable(AttributeDefinition attribute,                                ModelNode modelNode) {  if (modelNode == null || ModelType      .UNDEFINED.equals(modelNode.getType())) {    return false;  }  String value = modelNode.asString();  if ("".equals(value.trim())) {    return false;  }  return !value.equals(attribute.getDefaultValue());        // <=}

Обратив внимание на предшествующий методу комментарий, можно ожидать, что метод вернет true, если:

  • modelNode не null,
  • строковое представление modelNode не пустое,
  • modelNode не значение по умолчанию.

Несмотря на авторский комментарий и корректную, на первый взгляд, логику, поведение метода будет отличаться. Причиной этому служит проверка modelNode на равенство со значением по умолчанию в последней строчке метода.

Строковое представление modelNode сравнивается с объектом типа ModelNode, и, как можно догадаться, такое сравнение всегда будет возвращать отрицательный результат из-за несовместимости типов.

Последствия ошибки: непредвиденное разрешение к отправке значения modelNode, когда оно равно значению по умолчанию (attribute.getDefaultValue()).

Шестое место: "Копипаст-ориентированное программирование"


Источник: Проверка кода XMage и почему недоступны специальные редкие карточки для коллекции Dragon's Maze.

V6072 Two similar code fragments were found. Perhaps, this is a typo and 'playerB' variable should be used instead of 'playerA'. SubTypeChangingEffectsTest.java(162), SubTypeChangingEffectsTest.java(158), SubTypeChangingEffectsTest.java(156), SubTypeChangingEffectsTest.java(160)

@Testpublic void testArcaneAdaptationGiveType() {    addCard(Zone.HAND, playerA, "Arcane Adaptation", 1);    addCard(Zone.BATTLEFIELD, playerA, "Island", 3);    addCard(Zone.HAND, playerA, "Silvercoat Lion");    addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");    addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion");   // <=    addCard(Zone.HAND, playerB, "Silvercoat Lion");    addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");    addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion");   // <=    ....    for (Card card : playerB.getGraveyard().getCards(currentGame)) {        if (card.isCreature()) {            Assert.assertEquals(card.getName() + " should not have ORC type",                    false, card.getSubtype(currentGame).contains(SubType.ORC));            Assert.assertEquals(card.getName() + " should have CAT type",                    true, card.getSubtype(currentGame).contains(SubType.CAT));        }    }}

В этом году, как и в прошлом (Топ 10 ошибок за 2019), классная copy-paste ошибка от диагностического правила V6072 заслуживает место в десятке.

Характер ошибки заключается в том, что когда разработчику нужно сделать схожие действия для различных переменных, то он копирует добросовестно написанный ранее код и изменяет название переменной. Но делает это уже не совсем добросовестно и забывает доизменить переменные.

В данном фрагменте кода так и произошло. Автор теста имитировал игру между игроками, раскидывая между ними одинаковые карты по игровым зонам, но из-за copy-paste игроку playerA дважды досталась одна и та же карта. Из-за этого игровая зона Zone.GRAVEYARD игрока playerB осталась без тестирования. Подробное описание ошибки можно почитать в самой статье.

Пятое место: "Ненормальное распределение"


Источник: Big/Bug Data: анализируем исходный код Apache Flink

V6048 This expression can be simplified. Operand 'index' in the operation equals 0. CollectionUtil.java(76)

public static <T> Collection<List<T>> partition(Collection<T> elements, int numBuckets) {  Map<Integer, List<T>> buckets = new HashMap<>(numBuckets);    int initialCapacity = elements.size() / numBuckets;  int index = 0;  for (T element : elements)   {    int bucket = index % numBuckets;                                 // <=    buckets.computeIfAbsent(bucket,                             key -> new ArrayList<>(initialCapacity))           .add(element);   }  return buckets.values();}

Ошибка была обнаружена в утилитном методе partition, который разбивает переданную коллекцию elements на numBuckets коллекций. Суть ошибки в том, что индекс коллекции bucket, в которую хотят поместить каждый рассматриваемый элемент, имеет константное значение (0). Причиной этому служит то, что разработчик забыл инкрементировать переменную index на каждой итерации цикла.

Вследствие чего метод partition будет всегда возвращать коллекцию elements, обернутую в другую коллекцию. А это вряд ли задуманное поведение.

Четвертое место: "Бомба замедленного действия"


Источник: АНБ, Ghidra и единороги.

V6008 Null dereference of 'selectedNode' in function 'setViewPanel'. OptionsPanel.java(266)

private void processSelection(OptionsTreeNode selectedNode) {  if (selectedNode == null) {    setViewPanel(defaultPanel, selectedNode); // <=    return;  }  ....}private void setViewPanel(JComponent component, OptionsTreeNode selectedNode) {  ....  setHelpLocation(component, selectedNode);  ....}private void setHelpLocation(JComponent component, OptionsTreeNode node) {  Options options = node.getOptions();  ....}

В приведенном фрагменте кода явно напортачили. Если вы проследите за selectedNode из processSelection(), когда selectedNode == null, то сразу же обнаружите, что при таком исходе нас ждет неминуемый NullPointerException. О чем и предупреждает нас анализатор.

Но, изучив немного код, автор статьи пришел к выводу, что выполнение программы никогда не встретится с NullPointerException, так как processSelection() вызывается всего в двух местах, перед вызовом которых selectedNode явно проверяется на null.

Несмотря на это, такой код бомба замедленного действия, поскольку другой разработчик может увидеть, что метод явно обрабатывает случай selectedNode == null, и решить, что это валидное значение, что потом выльется в падение приложения.

Третье место: "Всегда false"


Источник: Проверка кода XMage и почему недоступны специальные редкие карточки для коллекции Dragon's Maze.

V6007 Expression 'filter.getMessage().toLowerCase(Locale.ENGLISH).startsWith("Each ")' is always false. SetPowerToughnessAllEffect.java(107)

@Overridepublic String getText(Mode mode) {  StringBuilder sb = new StringBuilder();  ....  if (filter.getMessage().toLowerCase(Locale.ENGLISH).startsWith("Each ")) {    sb.append(" has base power and toughness ");  } else {    sb.append(" have base power and toughness ");  }  ....  return sb.toString();}

Кто же сравнивает строку, приведенную в нижний регистр, со строкой, которая начинается с заглавной буквы? Отсюда и всегда ложный результат проверки сообщения.

Результат дефекта не критический, но тоже неприятный: где-то будет фигурировать неграмотно составленный текст.

Второе место: "2-в-1"


Источник: АНБ, Ghidra и единороги.

V6007 Expression 'index >= 0' is always true. ExternalNamesTableModel.java(105)

V6019 Unreachable code detected. It is possible that an error is present. ExternalNamesTableModel.java(109)

public void setValueAt(Object aValue, int row, int column) {  ....  int index = indexOf(newName);  if (index >= 0) {                  // <=    Window window = tool.getActiveWindow();    Msg.showInfo(getClass(), window, "Duplicate Name",                 "Name already exists: " + newName);    return;  }  ExternalPath path = paths.get(row); // <=  ....}private int indexOf(String name) {  for (int i = 0; i < paths.size(); i++) {    ExternalPath path = paths.get(i);    if (path.getName().equals(name)) {      return i;    }  }  return 0;}

Метод indexOf всегда возвращает неотрицательное число. А всё из-за того, что автор метода в случае отсутствия искомого newName по ошибке возвращает 0, а не -1. Такая ошибка приводит к тому, что поток выполнения программы всегда будет заходить в then-ветку условного оператора if (index >= 0), в котором будет выдавать сообщение о существующем newName и успешно выходить из метода, даже тогда, когда в реальности newName не был найден.

Но и это ещё не всё. Так как then-ветка условного оператора прекращает выполнение метода, то до кода после условного оператора дело так и не дойдет.

Об этом и предупреждает нас анализатор.

Первое место: "А то ли мы проверили?"


Источник: Под капотом PVS-Studio для Java: разработка диагностик.

V6080 Consider checking for misprints. It's possible that an assigned variable should be checked in the next condition. Menu.java(40)

public class Menu{  private Map<String, List<String>> menus = new HashMap<String, List<String>>();  public void putMenuItem(String menu, String item)  {    List<String> items = menus.get(menu);    if (item == null)                      // <=    {      items = new ArrayList<String>();      menus.put(menu, items);    }    items.add(item);  }  ....}

По задумке автора предполагалось создать коллекцию по ключу menu, если таковой ещё не было. Но проверка не той переменной разрушила всю задумку, прорубив лазеечку для NullPointerException. Метод выбросит исключение, когда в словаре ключ menu будет отсутствовать, и значение item, которое хотели добавить, не будет null.

Заключение


Проверки open-source проектов с помощью PVS-Studio из года в год доказывают, что такой рубеж защиты, как статический анализ кода, должен обязательно присутствовать в разработке. Каким бы вы мастером своего дела ни были, ошибки обязательно найдут лазеечку в ваш проект, и причин этому множество: устали, завал на работе или вовсе отвлеклись на котиков. А если вы работаете в команде, то количество возможностей попасть ошибкам в код вырастает пропорционально количеству коллег.

Если вам понравился наш обзор, то не ждите следующего конца года. Статьи о проверках начнутся сразу же с первого месяца 2021, а если вам не терпится смелее скачивайте анализатор и самостоятельно проверяйте open-source проекты.


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Maxim Stefanov. Top-10 Bugs in Java Projects in 2020.
Подробнее..

Recovery mode Для тех, кто покупает ссылки

23.06.2020 22:21:05 | Автор: admin

Статья не про "работают или не работаю ссылки", а для тех, кто занимается их покупкой или хочет самостоятельно начать это делать. В ней я дам некоторые рекомендации, опираясь на свой опыт по ссылочным биржам. На рассмотрение возьмем трех представителей.


Общее правило если вы не хотите бесплатно ссылку на этом сайте не покупайте.


  1. trastik.com

Хочу более подробно написать о данной площадке, поскольку узнал о ней не так давно (ранее просто не было нужны в доп. биржах). Распишу без воды, основные плюсы, минусы и личные рекомендации.


Плюсы:


  1. Хорошая цена на ссылки. Если на GGL, Miralinks и PR-SAPE ценник на "нормальную" ссылку начинается от 500 р., здесь можно получить аналогичного донора за 150-200 р. Лично я приобрел за 1000 р. ссылку на домен с трафиком 1кк уникумов в месяц. Очень достойная цена. Проверьте стоимость на других сервисах очень удивитесь :).


  2. Качество сайтов. В целом оно "окей". Это не "девственные" порталы с 0 исходящих ссылок.Общее соотношение в пределах нормы. Это значит, что в 80% случаев, входящих больше чем исходящих. А соотношение (входящие разделить на исходящие) в 60% случаев от 0 до 0,6. Простыми словами сайты не сильно заспамлены автобиржами и дешевыми ссылками ради быстрого продвижения. Это значит меньше вероятности получить бэклинк с сайта под фильтром в будущем.



Пояснение по отношению входящие/исходящие ссылки: если на сайте больше исходящих чем входящих, это может означать следующее:


а) Сайт сейчас не в самой лучшей форме (падает трафик, стагнация, вирусы, ошибки и т.д.) и вебмастер пытается максимизировать прибыль, продавая ссылки по заниженной цене.


б) Сайт изначально создавался для продажи ссылок. Это значит, что вебмастер получил ряд "пузомерок" (икс, трафик, PR, количество страниц и т.д.) и далее планирует заняться только его монетизацией, изредка поддерживая ресурс "на плаву".


в) Сайт планируют продавать. Аналогично перед продажей, владелец пытается выжать максимум прибыли. Здесь стоит отметить, что так поступают редко и только недобросовестные и, если честно, глупые вебмастера. Скачок ссылочной массы перед продажей может аннулировать сделку.


г) Сайт состоит в автоматизированной ссылочной бирже типа SAPE, SeoPult и т.д. А значит большинство ссылок с него мусор.


  1. Вменяемая обратная связь. Лично у меня никаких претензий к обратной связи нет. Ответы получал за несколько минут. Некоторые коллеги были недовольны долгими ответами. Но ничего такого я не заметил.


  2. Идеально для "серых" и "черных" тематик. Большинство сайтов на площадке взломаны. То есть вебмастер не знает, что площадка продаёт на сайте ссылку. Моральный аспект этого обсуждать не будем. Получить много вечных ссылок за небольшие деньги отличная стратегия для гемблинг, адалт, фарм и других подобных тематик. Кроме того, можно сменить домен или страницу ссылки бесплатно. Забанили сайт быстро меняем всё ссылочное на новый домен и ждём индексации. По сути, у вас динамическая ссылочная масса, которую вы можете применять к разным сайтам.


  3. Ещё несколько особенностей:



а) Статьи и ссылки стоят одинаково. Если не загрузить свою статью, то будет использована случайная дешевая статья с биржи etxt, они берутся только ради уникальности. Если нужна качественная статья, то заказываете сами и используете.


б) Есть иностранные площадки может быть для тех же "серых" и "черных" тем подойдёт.


в) Гарантия на размещение год.


Минусы:


  1. Не показан адрес домена. Я считаю это ключевой недостаток биржи. Оно и понятно, кто будет показывать ломаный сайт? Я всегда рекомендую перед покупкой ссылки смотреть сайт глазами. Никакие "пузомерки" не покажут всей картины, если не полазить по сайту самому. Тех параметров, которые предоставляет нам система конечно не хватает для нормального анализа. Эта проблема решается только когда сумма пополнений на аккаунте достигнет 100 000 рублей, тогда домены не скрыты.


  2. Мало площадок. С большой вероятностью, вы не найдёте точного попадания в вашу тематику. Рассчитывайте на околотематические сайты. Плохо для крупных сайтов, где ссылочное играет весомую роль. Хорошо миксовать с другими биржами, снимая "сливки" по хорошему ценнику. Но в целом на идеальное попадание надеяться не стоит.


  3. Интерфейс дело сугубо личное. На мой взгляд здесь он хуже, чем на GGL, но лучше, чем на miralinks.


  4. Наличие хорошей статьи обязательно. Иначе получите рандомную статью только ради уникальности.



Возможно есть ещё минусы, например, связанные с анализом и размещение ссылок в стандартном интерфейсе, но я обычно пользуюсь минимальными настройками внутри сервиса.


Общий вывод по бирже: инструмент безусловно заслуживает внимания. В первую очередь из-за приятных ценников.


Для белой тематики: при закупке ссылок обязательно заглядывайте на trastik.com. Проверьте точное или близкое попадание в нишу и купите парочку грошовых ссылок с неплохих доноров (если они будут). Ведите закпку по принципу:


  1. Точное попадание в тематику;


  2. Околотематические сайты;


  3. Новостные сайты.



Снимите самые "сливки", а дальше проверяйте вручную через другие биржи.


Для "чёрной тематики": я бы закупал ссылку только тут. Опять-таки из-за цены и возможности удобно поменять домен. Можно рассматривать как альтернативу PBN.


  1. Gogetlinks.net

Одна из самых известных бирж. Все, кто слышал о ссылках, слышал и о GGL.


Плюсы:


  1. Максимально полная информация о донорах для ручной выборки. Но самое главное виден домен, можно выгрузить и залить в чек-траст и проверить там.


  2. В 90% отзывчивые веб-мастера. После размещения ссылок я просил поправить статью примерно в 70% случаях. Кривая вёрстка, другой шрифт, отсутствие изображений это всё стандартные проблемы при закупке. Как правило всё исправляли. Если человек не отвечал открывался спор и обычно всё решалось в мою пользу. После этого сайт сразу отправлялся в black-лист. С неадекватными вебмастерами нет смысла работать.


  3. Удобный интерфейс (это субъективно). Если вы первый раз на сайте может немного шокировать, но потом очень удобно. Просмотр статьи с ссылкой прямо на сайте, поиск по множеству параметров и т.д.


  4. Много площадок. Здесь можно найти почти любую тематику (за исключением запрещенных: порно, казино и т.д.).



"В Gogetlinks не участвуют сайты, нарушающие авторские права, противоречащие законам России, а также нормам общественной морали (порно, взломы сайтов и т.д.), сайты 'для взрослых' и сайты знакомств. (с)".


Минусы:


  1. Часто размещают ужасные статьи. Бывает совершенно не то, что я бы хотел видеть, даже бесплатно. Решается, как я писал выше в 90% случаев.


  2. Цена на ссылку от 500р. Есть и дешевле, но обычно это мусор. Если закупаемся на GGL, это будет не бюджет, но и не так дорого, как на Miralinks.


  3. Много завышенных цен на ссылки. Очень важно проверять каждого донора вручную. Часто вебмастера мимикрируют под нормальный сайт, продавая за 1000р. полный треш. На самом деле, это проблема всех ссылочных бирж.


  4. На мой взгляд, есть лишняя и вводящая в заблуждение информация. Например, выбор ПС продвижения. Мне лично не понятен этот пункт. По какому принципу фильтруются ссылки с этой функцией? Какая-то ссылка полезнее гуглу, а какая-то яндексу? Ну, спорно. Не рекомендую использовать. Также, как и "Траст". По сути, любой "Траст", будь то GGL, CheckTrust, miralinks и т.д. это какой то, самостоятельно выведенный показатель. Он формируется из других абстрактных показателей, проще говоря, это средняя температура по больнице. Можно брать во внимание, если траст от 0 до 4. Это значит, что совсем мусор: трафик никакой, автобиржи ссылок, заспамлен и т.д. Но дальше при выборе ориентируйтесь на конкретные значения: трафик, ссылочность, наличие фильтра, автобиржи, внешний вид и т.д.


  5. Пополнение по безналу. От 15000р. с комиссией 9% это подойдёт не для всех. Часто клиенты (особенно в регионах) были шокированы такими ценами.



Общий вывод по бирже: удобная, в меру мусора, оптимальная для среднего бюджета. Подходит для продвижения сайтов услуг, интернет магазинов, инфо-порталов в белой тематике. Не подходит для черной и серой тематики.


|вставка: Почти на каждой ссылочной бирже существует режим "Для новичка". Это когда система автоматически подбирает для вас доноров, анкоры, иногда и количество ссылок. Никогда не выбирайте этот режим. Даже если вы новичок. Лучше разберитесь с системой, чем доверить всё машине, которая вероятно просто "сольёт" ваши деньги. Конец вставки|


  1. Miralinks

Не менее популярная система. Дорого, качественно.


Плюсы:


  1. Качественное отличие от конкурентов огромное количество качественных доноров, очень строгий отбор при размещении сайта на бирже. Можно разместить несколько ссылок. При закупке я обычно пишу статью 5000 знаков и внутри ставлю 2 или 3 ссылки. Например, 2 анкорные: "купить ладу ларгус" и "цены на ларгус", а другую в самом конце статьи, "по материалам сайта sitename.ru".


  2. По аналогии с GGL (это одна компания), здесь в 90% адекватные вебмастера и поддержка.


  3. Самые естественные ссылки. Максимально приближено к естественной ссылке, а значит наиболее эффективно.


  4. Как и на GGL, приемлемые фильтры (если разобраться в интерфейсе)


  5. Есть возможность усиления ссылок. Усиление за счет живых переходов. Лично я эффекта не увидел. Лучше делать это самостоятельно.



Минусы:


  1. Цена. Очень дорого. Статья + ссылка ~2000р. Только для высоких бюджетов. Если бюджет средний или низкий лучше GGL.


  2. Ужасный интерфейс (это субъективно). Не специалист без мануала точно ничего не поймёт. Если долго не заходил на площадку, приходится разбираться заново. В отличии от той же GGL.


  3. Те же странные "показатели качества" что и у GGL.


  4. Пополнение по безналу. От 12000р., комиссия 12% банковская и 12% комиссия системы. Короче минимальная сумма пополнения 15500. Ну, если пополните на 500 000р., получите дополнительно VIP-статус, и скидку 25000р. Итого, придётся оплатить 610000р. :).


  5. Будет сложна для новичков. Здесь слишком большая совокупность различных показателей: качество статьи, донора, количество ссылок в статье, цена и т.д. Нужно не только выбрать донора, но и подготовить правильную статью опираясь на семантическое ядро. Чтобы получить максимальный эффект всё должно работать в комплексе, а для этого нужен определенный опыт.



Общий вывод: только для крупных бюджетов. При должном понимании принципа закупки ссылок, можно построить максимально качественное ссылочное благодаря экспертным статьям. Не рекомендуется новичкам.


В завершение напишу пошаговый вариант закупки, используя связку из этих трёх бирж (белая тематика):


Бюджет 20000р. и меньше. Я рассматриваю такой бюджет, потому что кампании/вебмастера с бюджетом 50000+ т. р сами понимают, где и какие ссылки им закупить.


  1. Идём на trastik.com, снимаем все "сливки" в виде дешевых и качественных доноров. Найдёте 2-5 штук;


  2. Идём на GGL, изучаем доноров, закупаем по всем правилам на максимально тематические площадки. Если их много закупаем там по максимуму. Если точных попаданий в тематику мало идём на Miralinks;


  3. Заходим на Miralinks, смотрим есть ли "вкусные" доноры по приемлемой цене. Самостоятельно пишем хорошую экспертную статью. Размещаем на тематической площадке.


  4. Усиливаем (покупаем ссылки на заказную статью)на остатки бюджета тематическую статью с miralinks дешевыми ссылками с трастика. Только не переусердствуйте с этим.



Если вы продвигаете серую или чёрную тематику однозначно пользоваться trastik.com. Динамическая ссылочная масса сэкономит вам деньги при блокировании сайтов. Не нужно тратиться на GGL и уж тем более на Miralinks.


На этому, пожалуй, остановлюсь. Если интересно, напишу ещё про несколько ссылочных бирж со своими фишками.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru