﻿# Баги носят данные\. Проверка Apache NiFi

Сбор, обработка и перемещение данных — ключевые процессы в IT\. Но что, если они нарушатся из\-за коварных багов в коде? Рассказываем об ошибках, найденных статическим анализатором в проекте Apache NiFi\.

![1285_nifi_ru/image1.png](https://import.viva64.com/docx/blog/1285_nifi_ru/image1.png)

[Apache NiFi](https://github.com/apache/nifi) — это бесплатный инструмент с открытым кодом для сбора, обработки и перемещения данных\. Сначала он разрабатывался в американском агентстве безопасности, а в 2014 году стал открытым и вошёл в экосистему Apache\. Сегодня NiFi часто используют в проектах с большими данными, особенно вместе с Hadoop\.


> \*\*Примечание\\\.\*\* Ранее мы уже проверяли и проект Apache Hadoop\\\. Прочитать эту статью можно в нашем \[блоге\]\(https://pvs\-studio\.ru/ru/blog/posts/java/0697/\)\\\.

Главное преимущество NiFi — простой визуальный интерфейс, где данные можно перемещать, как на схеме: перетаскиваешь блоки \(процессоры\) и соединяешь их стрелками\. Это позволяет не только администраторам, но и аналитикам с разработчиками настраивать потоки данных без глубоких знаний в системе\. NiFi умеет работать с разными источниками: файлы по SFTP, логи по syslog, базы данных \(через JDBC\), Kafka, HDFS, Elasticsearch и многие другие\.

Сегодня мы посмотрим на то, какие интересные баги смог найти статический анализатор PVS\-Studio в этом проекте\. Проверялся проект на коммите [d924680](https://github.com/apache/nifi/tree/d9246807ea115f65dc3871b94a2011e1b6001c7d)\.

Кстати, в проекте уже используется инструмент контроля качества кода — [PMD](https://pmd.github.io/)\. Посмотрим, сможет ли PVS\-Studio найти ошибки в коде, который уже проверяется :\)

## Как это делалось?

Проект использует сборочную систему Maven, поэтому для его анализа использовался плагин PVS\-Studio для этой сборочной системы\.

Чтобы проанализировать исходный код, нужно было добавить несколько строк в файл `pom.xml`\. Для начала необходимо подключить репозиторий плагинов PVS\-Studio:

```cpp
....
<pluginRepositories>
  <pluginRepository>
    <id>pvsstudio-maven-repo</id>
    <url>https://wcdn.pvs-studio.com/java/pvsstudio-maven-repository/</url>
  </pluginRepository>
</pluginRepositories>
....
```

А затем применить плагин и выставить нужные настройки для анализа:

```cpp
....
<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.pvsstudio</groupId>
      <artifactId>pvsstudio-maven-plugin</artifactId>
      <version>7.38.96564</version>
      <configuration>
        <analyzer>
          <outputType>json</outputType>
          <outputFile>PVS-Studio.json</outputFile>
          <analysisMode>GA,OWASP</analysisMode>
        </analyzer>
      </configuration>
    </plugin>
    ....
  </plugins>
</pluginManagement>
....
```

Здесь в конфигурации анализатора используются следующие настройки:

* `<outputType>` — формат, в котором анализатор сохранит результаты анализа \(в нашем случае — формат отчёта json\);
* `<outputFile>` — путь, по которому будет расположен отчёт анализатора \(в нашем случае — корневая директория проекта\);
* `<analysisMode>` — включённые группы диагностик для анализа проекта \(в нашем случае — диагностические правила из групп General Analysis и OWASP\)\.

После произведённых изменений в `pom.xml` и сборки проекта осталось лишь запустить команду:

```cpp
mvn pvsstudio:pvsAnalyze
```


> \*\*Примечание\\\.\*\* Подробнее о плагине PVS\\\-Studio для сборочной системы Maven можно прочитать в \[нашей документации\]\(https://pvs\-studio\.ru/ru/docs/manual/6705/\)\\\.

## Таковы наши условия

Срабатывания анализатора на логически неверные выражения — частые гости статей с проверками open source проектов\. Так почему эта статья должна быть исключением? :\)

**Фрагмент 1**

```cpp
public void communicate() throws IOException {
  final String line = reader.readLine();
  final String[] splits = line.split(" "); 
  if (splits.length < 0) {
    throw new IOException(....);
  ....
}
```

Предупреждение PVS\-Studio: [V6007](https://pvs-studio.ru/ru/docs/warnings/v6007/) Expression 'splits\.length < 0' is always false\. [BootstrapCodec\.java 45](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-registry/nifi-registry-core/nifi-registry-bootstrap/src/main/java/org/apache/nifi/registry/bootstrap/BootstrapCodec.java#L45)

Всем приготовиться, мы переходим границы времени и пространства\! В этом фрагменте кода мы бросаем исключение, если длина полученного массива\.\.\. меньше нуля\.

Вероятно, автор хотел проверить, равна ли длина массива нулю, но эта ситуация тоже никогда не произойдёт\.\.\. Почему?

Изначально мы получаем строку от пользователя через `reader`, а после разбиваем с помощью `line.split()` по пробелам\. Дело в том, что даже если в строке нет ни одного пробела, то `String.split()` вернёт нам массив, в котором единственным элементом будет исходная строка\.

**Фрагмент 2**

```cpp
protected Map<String, String> getAttributes(
  final TarArchiveInputStream stream
) throws IOException {
  ....
  for (final Entry<Object, Object> entry : props.entrySet()) {
    final Object keyObject = entry.getKey();
    final Object valueObject = entry.getValue();
    if (!(keyObject instanceof String)) {            // <=
      throw new IOException(
        "Flow file attributes object contains key of type "
        + keyObject.getClass().getCanonicalName()
        + " but expected java.lang.String");
    } else if (!(keyObject instanceof String)) {     // <=
      throw new IOException(
        "Flow file attributes object contains value of type "
        + keyObject.getClass().getCanonicalName()
        + " but expected java.lang.String");
  }
  final String key = (String) keyObject;
  final String value = (String) valueObject;
  result.put(key, value);
  }
}
```

Предупреждение PVS\-Studio: [V6003](https://pvs-studio.ru/ru/docs/warnings/v6003/) The use of 'if \(A\) \{\.\.\.\} else if \(A\) \{\.\.\.\}' pattern was detected\. There is a probability of logical error presence\. [FlowFileUnpackagerV1\.java 80](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/FlowFileUnpackagerV1.java#L80)

Вы когда\-нибудь испытывали ощущение безысходности?

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

Что же произошло? Автор написал первый блок этого ветвления, потом скопировал его и вставил в `else if`, забыв поменять `keyObject` на `valueObject`\. Причём забыл он его поменять везде, в том числе и в сообщении выдаваемого `IOException`\.

**Фрагмент 3**

```cpp
public void setOutputPorts(
  final Set<RemoteProcessGroupPortDescriptor> ports, 
  final boolean pruneUnusedPorts
) {
....
final Iterator<StandardRemoteGroupPort> itr = outputPorts.values().iterator();
int prunedCount = 0;
while (itr.hasNext()) {
  final StandardRemoteGroupPort port = itr.next();
  if (....) {
    port.setTargetExists(false);
    port.setTargetRunning(false);
      // If port has connections, 
      // it will be cleaned up 
      // when connections are removed
      if (port.getConnections().isEmpty()) {
        itr.remove();
        logger.info(
          "Pruning unused Output Port {} from {}", port, this
        );
       }
     }
   }
    if (prunedCount == 0) {  // <=
      logger.debug(
        "There were no Output Ports to prune from {}", 
        this
      );
    } else {....}
  }
  ....
}
```

Предупреждение PVS\-Studio: [V6007](https://pvs-studio.ru/ru/docs/warnings/v6007/) Expression 'prunedCount \=\= 0' is always true\. [StandardRemoteProcessGroup\.java 640](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java#L640)

Понять, почему анализатор ругается на этот участок кода, помогают комментарии разработчика\.

Здесь идёт очистка портов, у которых нет активных соединений\. Также есть счётчик `prunedCount`, который должен считать, сколько портов было удалено\.

Анализатор замечает, что условие `prunedCount == 0` всегда истинно\. И он прав, потому что счётчик нигде не увеличивается: он просто не меняется на протяжении всего кода\.

Но, посмотрев на логи и комментарии, видно, что после строки `itr.remove()` как раз происходит удаление порта\. Об этом даже пишется в лог\. Значит, именно в этом месте и должно быть увеличение счётчика `prunedCount`\. Скорее всего, его просто забыли добавить\.

## Исключительные баги

Вы когда\-нибудь ловили исключение при обработке исключения? 

**Фрагмент 4**

```cpp
public void revertReceivedTo(Relationship r, Throwable t) {
  ....
  String errorMessage = Throwables.getMessage(t, null, 950);
  String stackTrace = Throwables.stringStackTrace(t);
  for (FlowFile f : toFail) {
    if (t != null && r != null) {
      ....
    }
  ....
}
```

Предупреждение PVS\-Studio: [V6060](https://pvs-studio.ru/ru/docs/warnings/v6060/) The 't' reference was utilized before it was verified against null\. [ProcessSessionWrap\.java 210](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/flow/ProcessSessionWrap.java#L210)

Здесь анализатор указывает нам на то, что объект `t` был использован до того, как прошёл проверку на `null`\. Дабы окончательно убедиться, что этот объект разыменовывается, заглянем в [метод `Throwables.stringStackTrace()`](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/util/Throwables.java#L28):

```cpp
public static String stringStackTrace(Throwable e) {
  StringWriter sw = new StringWriter(500);
  PrintWriter pw = new PrintWriter(sw);
  e.printStackTrace(pw);
  pw.flush();
  sw.flush();
  return sw.toString();
}
```

Здесь объект, который мы передаём, действительно разыменовывается\. То есть, пытаясь обработать объект исключения, мы можем поймать исключение\! Или нет?

На самом деле проверка `t != null` в этом фрагменте кода просто лишняя\. При [единственном вызове метода](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java#L490), в котором анализатор выдал предупреждение, в него передаётся [константа](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java#L142), созданная через `new`:

```cpp
public class ExecuteGroovyScript extends AbstractProcessor {
  ....

  public static final Relationship REL_FAILURE = new Relationship
      .Builder()
      .name("failure")
      .description("FlowFiles that failed to be processed")
      .build();

  ....

  public void onTrigger(
    final ProcessContext context, 
    final ProcessSession _session
  ) throws ProcessException {
  ....
    if (toFailureOnError) {
      session.revertReceivedTo(
        REL_FAILURE, 
        StackTraceUtils.deepSanitize(t)
      );
    } else {....}
    ....
  }
```

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

**Фрагмент 5**

```cpp
public void finishTransferFlowFiles(
  final CommunicationsSession commSession
) throws IOException {
  if (postResult == null) {
    new IllegalStateException(....);
  }
  ....
}
```

Предупреждение PVS\-Studio: [V5303](https://pvs-studio.ru/ru/docs/warnings/v5303/) The object was created but it is not being used\. The 'throw' keyword could be missing\. [SiteToSiteRestApiClient\.java 919](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-commons/nifi-site-to-site-client/src/main/java/org/apache/nifi/remote/util/SiteToSiteRestApiClient.java#L919)

Трёхочковый\!

Мы создали объект исключения со всей нужной информацией\. Казалось бы, всё готово, можно бросать, но\.\.\. ой\. Мы его создали и благополучно забыли выдать\.\.\.

> Так трудно смотреть на Exception, который не можешь бросить\.\.\.
>
> © Неизданный сборник программистских цитат

## Классический NullPointerException

Не обойдём стороной ещё одну традицию — ошибки разыменования `null`\.

**Фрагмент 6**

```cpp
private boolean replaceNodeStatus(
  final NodeIdentifier nodeId, 
  final NodeConnectionStatus currentStatus, 
  final NodeConnectionStatus newStatus
) {
  if (newStatus == null) {               // <=
    logger.error("....", nodeId, currentStatus, newStatus);
    logger.error("", new NullPointerException());
  }
  
  ....

  if (newStatus.getState() ==            // <=
      NodeConnectionState.REMOVED) {     
    ....
  } else {....}
}
```

Предупреждение PVS\-Studio: [V6008](https://pvs-studio.ru/ru/docs/warnings/v6008/) Potential null dereference of 'newStatus'\. [NodeClusterCoordinator\.java 448](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/node/NodeClusterCoordinator.java#L448)

Итак, что у нас происходит? Если переменная `newStatus` равна `null`, мы записываем это в лог, мол, "всё под контролем, замечено" и спокойно двигаемся далее\.

А что нас ждёт "далее"? Всего через несколько строк — разыменование этой самой `newStatus` вне зависимости от того, равна она нулю или нет\. Вероятно, разработчики просто забыли `return` внутри условия\.

![1285_nifi_ru/image2.png](https://import.viva64.com/docx/blog/1285_nifi_ru/image2.png)

**Фрагмент 7**

```cpp
public void addFileStatus(
  final FileStatus parent, 
  final FileStatus child
) {
  Set<FileStatus> children = fileStatuses.computeIfAbsent(
    parent.getPath(), k -> new HashSet<>()
  );
  if (child != null) {                           // <=
    children.add(child);
    if (
     child.isDirectory() && 
     !fileStatuses.containsKey(child.getPath())
    ) {
      fileStatuses.put(child.getPath(), new HashSet<>());
    }
  }

  pathToStatus.put(parent.getPath(), parent);
  pathToStatus.put(child.getPath(), child);      // <=
}
```

Предупреждение PVS\-Studio: [V6008](https://pvs-studio.ru/ru/docs/warnings/v6008/) Potential null dereference of 'child'\. [MockFileSystem\.java 265](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/test/java/org/apache/nifi/processors/hadoop/util/MockFileSystem.java#L265)

Ещё один поистине хрестоматийный пример\. Сначала проверили, что `child` не равен `null`, а после взяли и разыменовали его вне условия\.

Также, как и в прошлом примере, такие фокусы приведут к `NullPointerException` в том случае, если `child` будет равен `null`\.

## Все равны?

В Apache NiFi нашёлся целый пласт срабатываний анализатора PVS\-Studio на случаи, где что\-то пошло не по плану при сравнении объектов\.

**Фрагмент 8**

```cpp
public void onTrigger(
  final ProcessContext context, 
  final ProcessSession session
) {
  final String listingStrategy = context.getProperty(
    LISTING_STRATEGY
  ).getValue();

  if (BY_TIMESTAMPS.equals(listingStrategy)) {       // <=
    listByTrackingTimestamps(context, session);
  } else if (BY_ENTITIES.equals(listingStrategy)) {  // <=
    listByTrackingEntities(context, session);
  } else if (NO_TRACKING.equals(listingStrategy)) {  // <=
    listNoTracking(context, session);
  } else {
    throw new ProcessException(....);
  }
}
```

Предупреждения PVS\-Studio: 

* [V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [ListGCSBucket\.java 437](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-gcp-bundle/nifi-gcp-processors/src/main/java/org/apache/nifi/processors/gcp/storage/ListGCSBucket.java#L437)
* [V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [ListGCSBucket\.java 439](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-gcp-bundle/nifi-gcp-processors/src/main/java/org/apache/nifi/processors/gcp/storage/ListGCSBucket.java#L439)
* [V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [ListGCSBucket\.java 441](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-gcp-bundle/nifi-gcp-processors/src/main/java/org/apache/nifi/processors/gcp/storage/ListGCSBucket.java#L441)

В этом фрагменте анализатор ругается на то, что мы сравниваем два несовместимых типа: `AllowableValue` и `String`\. Без контекста звучит довольно страшно, но на самом деле здесь всё довольно просто: у типа `AllowableValue` есть метод `getValue()`, который возвращает строковое значение, подходящее для сравнения в этом контексте:

```cpp
public class AllowableValue implements DescribedValue {
  private final String value;
  ....
  public String getValue() {
        return this.value;
  }
}
```

Итого нужно было просто добавить вызов этого метода перед вызовом `equals()`:

```cpp
....
if (BY_TIMESTAMPS.getValue().equals(listingStrategy)) {
  listByTrackingTimestamps(context, session);
} else if (BY_ENTITIES.getValue().equals(listingStrategy)) {
  listByTrackingEntities(context, session);
} else if (NO_TRACKING.getValue().equals(listingStrategy)) {
  listNoTracking(context, session);
} else {
  throw new ProcessException(....);
}
....
```

<details>
   <summary>Подобные срабатывания</summary>

[V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [AbstractListProcessor\.java 419](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-extension-utils/nifi-listed-entity/src/main/java/org/apache/nifi/processor/util/list/AbstractListProcessor.java#L419)

[V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [AbstractListProcessor\.java 422](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-extension-utils/nifi-listed-entity/src/main/java/org/apache/nifi/processor/util/list/AbstractListProcessor.java#L422)

[V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [AbstractListProcessor\.java 425](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-extension-utils/nifi-listed-entity/src/main/java/org/apache/nifi/processor/util/list/AbstractListProcessor.java#L425)

[V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [AbstractListProcessor\.java 428](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-extension-utils/nifi-listed-entity/src/main/java/org/apache/nifi/processor/util/list/AbstractListProcessor.java#L428)

[V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [ListS3\.java 484](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java#L484)

[V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [ListS3\.java 486](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java#L486)

[V6058](https://pvs-studio.ru/ru/docs/warnings/v6058/) The 'equals' function compares objects of incompatible types: AllowableValue, String\. [ListS3\.java 488](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java#L488)


</details>
**Фрагмент 9**

```cpp
public synchronized void addFlowSnapshot(
  final VersionedExternalFlow versionedExternalFlow
) {
  final String version;
  if (metadata == null) {
    bucketId = DEFAULT_BUCKET_ID;
    flowId = "flow-" + flowIdGenerator.getAndIncrement();
    version = "1";
  } else {
    bucketId = metadata.getBucketIdentifier();
    flowId = metadata.getFlowIdentifier();
    version = metadata.getVersion();
  }
  ....
  final Optional<VersionedExternalFlow> optionalSnapshot = 
    snapshots.stream().filter(
      snapshot -> 
        snapshot.getMetadata().getVersion() == version  // <=
    ).findAny();
  ....
}
```

Предупреждение PVS\-Studio: [V6013](https://pvs-studio.ru/ru/docs/warnings/v6013/) Strings 'snapshot\.getMetadata\(\)\.getVersion\(\)' and 'version' are compared by reference\. Possibly an equality comparison was intended\. [InMemoryFlowRegistry\.java 165](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/registry/flow/InMemoryFlowRegistry.java#L165)

В этом фрагменте у нас имеются две строки `snapshot.getMetadata().getVersion()` и `version`, которые сравниваются с помощью оператора `==`\. Приводит это к тому, что мы сравниваем не содержимое этих объектов, а их ссылки\. То есть это условие будет равно только в том случае, если обе ссылки будут вести на один и тот же объект\.

Коварство этой ошибки также в том, что из\-за [String Pool](https://stackoverflow.com/questions/1881922/questions-about-javas-string-pool) она может воспроизводиться не всегда\.

Чтобы поправить ситуацию и сравнивать строки по содержанию, необходимо вместо оператора `==` использовать метод `equals()`\.

**Фрагмент 10**

```cpp
public boolean equals(final Object o) {
  if (this == o) {
    return true;
  }

  if (o == null
      || getClass() != o.getClass()
      || !Arrays.equals(
           o.getClass().getGenericInterfaces(),  // <=
           o.getClass().getGenericInterfaces()   // <=
       )                                        
     ) {
       return false;
     }
  ....
}
```

Предупреждение PVS\-Studio: [V6009](https://pvs-studio.ru/ru/docs/warnings/v6009/) Function 'equals' receives an odd argument\. The 'o\.getClass\(\)\.getGenericInterfaces\(\)' argument was passed several times\. [EqualsWrapper\.java 126](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/nifi-mock/src/main/java/org/apache/nifi/util/EqualsWrapper.java#L126)

Ошибка с использованием `equals()` внутри override'а `equals()`\. **К\-К\-К\-КОМБО\!**

Здесь мы вызываем `equals()` для сравнения двух массивов, но\.\.\. злосчастный copy\-paste испортил нам все планы\. В этом переопределении `equals` мы сравниваем текущий объект с тем, что передан в метод\. Но получается, что сравниваем результаты выполнения `getClass().getGenericInterfaces()` у одного и того же объекта\.

То есть просто опечатка, которую можно было бы исправить следующим образом:

```cpp
public boolean equals(final Object o) {
  if (this == o) {
    return true;
  }

  if (o == null
      || getClass() != o.getClass()
      || !Arrays.equals(
           getGenericInterfaces(),  
           o.getClass().getGenericInterfaces()   
       )                                        
     ) {
       return false;
     }
  ....
}
```

## Ваши аргументы неубедительны

**Фрагмент 11**

```cpp
static Properties load(File file, String propertiesType) {
  ....
  try (
    InputStream inputStream = new BufferedInputStream(
      new FileInputStream(file)
    )
  ) {
    rawProperties.load(inputStream);
  } catch (Exception e) {
    throw new RuntimeException(
      String.format(
        "Loading {} Properties [%s] failed", 
        propertiesType, 
        file
      ), e);
  }
  ....
}
```

Предупреждение PVS\-Studio: [V6046](https://pvs-studio.ru/ru/docs/warnings/v6046/) Incorrect format\. A different number of format items is expected\. Arguments not used: 2\. [PropertiesLoader\.java 43](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-properties-loader/src/main/java/org/apache/nifi/minifi/properties/PropertiesLoader.java#L43)

При вызове `String.format()` мы передали большее количество аргументов, чем можно подставить в строку\. Но подождите\! В строке же два места для подстановки\!

Да, это правда, но в этом фрагменте кода разработчики интересным образом смешали синтаксис строк для `String.format()` и логгера, из\-за чего один из аргументов в этом вызове `String.format()` всегда остаётся в пролёте\.

<details>
   <summary>Подобные срабатывания</summary>

V6046 Incorrect format\. A different number of format items is expected\. Arguments not used: 2\. [PropertiesLoader\.java 34](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-properties-loader/src/main/java/org/apache/nifi/minifi/properties/PropertiesLoader.java#L34)

V6046 Incorrect format\. A different number of format items is expected\. Arguments not used: 1\. [PropertiesPersister\.java 110](https://github.com/apache/nifi/blob/d9246807ea115f65dc3871b94a2011e1b6001c7d/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/main/java/org/apache/nifi/minifi/c2/command/PropertiesPersister.java#L110)


</details>
## Заключение

Вот мы и подобрались к завершению\. Обо всех найденных анализатором ошибках мы сообщим разработчикам проекта, открыв Issue в репозитории на GitHub\.

Каждая статья с проверкой проекта в нашем блоге — подтверждение того, что ошибки допускают все, и, что самое главное, в этом нет ничего страшного\. Важно вовремя находить и исправлять их\. 

Поэтому, если вас заинтересовали примеры ошибок, найденных анализатором PVS\-Studio в Apache NiFi, вы можете попробовать найти ошибки уже в своём проекте абсолютно бесплатно, получив лицензию [здесь](https://pvs-studio.ru/ru/pvs-studio/try-free/)\.

Чистого кода вам, друзья\!