﻿# Изучаем подозрительные места в коде AWS SDK для \.NET

Сегодня под нашим скальпелем оказался проект AWS SDK для \.NET\. Мы посмотрим на подозрительные места из исходного кода, разберёмся, что в них происходит, и попробуем воспроизвести некоторые проблемы\. Заваривайте кофе и устраивайтесь поудобнее\.

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

## Немного деталей об анализе

**Что за проект?**

AWS\.SDK для \.NET — проект, который помогает работать с Amazon Web Services, Amazon S3, Amazon DynamoDB и т\. п\. Исходники взял со [страницы проекта на GitHub](https://github.com/aws/aws-sdk-net)\. Если нужна точная версия, вот SHA коммита: 93a94821dc8ff7a0073b74def6549728da3b51c7\.

**Чем проверяли?**

Код проверял [анализатором PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) через плагин для Visual Studio\. 

**Что\-то ещё?**

Некоторые предупреждения могут показаться вам знакомыми — местами код не поменялся со времени [прошлой проверки](https://pvs-studio.ru/ru/blog/posts/csharp/0605/)\. Если предупреждения казались мне достаточно интересными, я дублировал их в эту статью\.

Но хватит о деталях проверки, переходим непосредственно к разбору подозрительных мест в коде\.

## Разбор подозрительных фрагментов кода

**Issue \#1**

```cpp
public static object GetAttr(object value, string path)
{
  if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");

  var parts = path.Split('.');
  var propertyValue = value;
            
  for (int i = 0; i < parts.Length; i++)
  {
    var part = parts[i];
    
    // indexer is always at the end of path e.g. "Part1.Part2[3]"
    if (i == parts.Length - 1)
    {
      ....
      // indexer detected
      if (indexerStart >= 0)
      {
        ....
        if (!(propertyValue is IList)) 
          throw 
            new ArgumentException("Object addressing by pathing segment '{part}'
                                   with indexer must be IList");
        ....
      }
    }

   if (!(propertyValue is IPropertyBag)) 
     throw 
       new ArgumentException("Object addressing by pathing segment '{part}'
                              must be IPropertyBag");
   ....
  }
  ....
}
```



Ссылки на GitHub: [\#1](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Core/Amazon.Runtime/Internal/Endpoints/StandardLibrary/Fn.cs#L82), [\#2](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Core/Amazon.Runtime/Internal/Endpoints/StandardLibrary/Fn.cs#L93)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3138</summary>

String literal contains potential interpolated expression\. Consider inspecting: part\. Fn\.cs 82

String literal contains potential interpolated expression\. Consider inspecting: part\. Fn\.cs 93


</details>


Похоже, разработчик забыл интерполировать сообщения исключений\. Из\-за этого вместо фактического значения переменной _part_ будет использован строковый литерал _\{part\}_\.

**Issue \#2**

```cpp
private CredentialsRefreshState Authenticate(ICredentials userCredential)
{
  ....
  ICoreAmazonSTS coreSTSClient = null;
  try
  {
    ....

    coreSTSClient =  
      ServiceClientHelpers.CreateServiceFromAssembly<ICoreAmazonSTS>(....);
  }
  catch (Exception e)
  {
    ....
  }

  var samlCoreSTSClient
#if NETSTANDARD
    = coreSTSClient as ICoreAmazonSTS_SAML;
  if (coreSTSClient == null)
  {
    throw new NotImplementedException(
      "The currently loaded version of AWSSDK.SecurityToken 
       doesn't support SAML authentication.");
  }
#else
    = coreSTSClient;
#endif

  try
  {
    var credentials = samlCoreSTSClient.CredentialsFromSAMLAuthentication(....);
  }
  catch (Exception e)
  {
    var wrappedException = 
      new AmazonClientException("Credential generation from 
                                 SAML authentication failed.", 
                                e);

    var logger = Logger.GetLogger(typeof(FederatedAWSCredentials));
    logger.Error(wrappedException, wrappedException.Message);

    throw wrappedException;
  }
  ....
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Core/Amazon.Runtime/Credentials/FederatedAWSCredentials.cs#L219)\. 

<details>
   <summary>Предупреждение PVS\\\-Studio — V3019</summary>

Possibly an incorrect variable is compared to null after type conversion using 'as' keyword\. Check variables 'coreSTSClient', 'samlCoreSTSClient'\. FederatedAWSCredentials\.cs 219


</details>


Большой фрагмент кода нужен для лучшего понимания контекста\. Сама ошибка спряталась здесь:

```cpp
var samlCoreSTSClient
#if NETSTANDARD
  = coreSTSClient as ICoreAmazonSTS_SAML;
if (coreSTSClient == null)
{
  throw new NotImplementedException(
    "The currently loaded version of AWSSDK.SecurityToken 
     doesn't support SAML authentication.");
}
```

Похоже, в условии оператора _if_ на _null_ проверяется значение не той переменной — нужно было проверять _samlCoreSTSClient_\. 

Обратите внимание на следующие элементы:

* название результирующей переменной — _samlCoreSTSClient_;
* тип интерфейса, к которому выполняется приведение — _ICoreAmazonSTS\_SAML_;
* текст сообщения исключения — _"\.\.\. doesn't support SAML authentication"_\. 

SAML упоминается везде, кроме имени проверяемой переменной, — _coreSTSClient_\. :\)

Интересно, как из\-за проверки разных переменных меняется логика, если приведение выполнить не удаётся\.

При проверке _samlCoreSTSClient_: 

* \-\> приведение с помощью оператора _as_
* \-\> проверка _samlCoreSTSClient_ на равенство _null_
* \-\> выброс исключения _NotImplementedException_ 

При проверке _coreSTSClient_: 

* \-\> приведение с помощью оператора _as_
* \-\> проверка _coreSTSClient_ на неравенство _null_
* \-\> попытка вызвать метод _CredentialsFromSAMLAuthentication_
* \-\> выброс исключения _NullReferenceException_ 
* \-\> перехват исключения в _catch_
* \-\> логгирование проблемы 
* \-\> выброс исключения _AmazonClientException\._ 

То есть как минимум во внешний код прилетит исключение другого типа и с другим сообщением\. 

А вообще говоря, проверка не той переменной после использования оператора _as_ — достаточно распространённый паттерн ошибки в C\#\. [Посмотрите на другие примеры](https://pvs-studio.ru/ru/blog/examples/v3019/)\.

**Issue \#3**

```cpp
public static class EC2InstanceMetadata
{
  [Obsolete("EC2_METADATA_SVC is obsolete, refer to ServiceEndpoint 
             instead to respect environment and profile overrides.")]
  public static readonly string EC2_METADATA_SVC = "http://169.254.169.254";

  [Obsolete("EC2_METADATA_ROOT is obsolete, refer to EC2MetadataRoot 
             instead to respect environment and profile overrides.")]
  public static readonly string 
    EC2_METADATA_ROOT = EC2_METADATA_SVC + LATEST + "/meta-data";

  [Obsolete("EC2_USERDATA_ROOT is obsolete, refer to EC2UserDataRoot 
             instead to respect environment and profile overrides.")]
  public static readonly string 
    EC2_USERDATA_ROOT = EC2_METADATA_SVC + LATEST + "/user-data";

  [Obsolete("EC2_DYNAMICDATA_ROOT is obsolete, refer to EC2DynamicDataRoot 
             instead to respect environment and profile overrides.")]
  public static readonly string 
    EC2_DYNAMICDATA_ROOT = EC2_METADATA_SVC + LATEST + "/dynamic";

  [Obsolete("EC2_APITOKEN_URL is obsolete, refer to EC2ApiTokenUrl 
             instead to respect environment and profile overrides.")]
  public static readonly string 
    EC2_APITOKEN_URL = EC2_METADATA_SVC + LATEST + "/api/token";

  public static readonly string
    LATEST = "/latest",
    AWS_EC2_METADATA_DISABLED = "AWS_EC2_METADATA_DISABLED";
  ....
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Core/Amazon.Util/EC2InstanceMetadata.cs#L57)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3070</summary>

Uninitialized variable 'LATEST' is used when initializing the 'EC2\_METADATA\_ROOT' variable\. EC2InstanceMetadata\.cs 57

Uninitialized variable 'LATEST' is used when initializing the 'EC2\_USERDATA\_ROOT' variable\. EC2InstanceMetadata\.cs 60

Uninitialized variable 'LATEST' is used when initializing the 'EC2\_DYNAMICDATA\_ROOT' variable\. EC2InstanceMetadata\.cs 63

Uninitialized variable 'LATEST' is used when initializing the 'EC2\_APITOKEN\_URL' variable\. EC2InstanceMetadata\.cs 66


</details>


Обратите внимание на порядок объявления и инициализации полей\. 

Сначала объявляются поля _EC2\_APITOKEN\_URL_, _EC2\_DYNAMICDATA\_ROOT_, _EC2\_USERDATA\_ROOT_, _EC2\_METADATA\_ROOT\. _Каждое из них использует в инициализаторе поле _LATEST_\. Однако само поле на момент использования ещё не инициализировано, так как оно объявляется ниже по коду\. Как результат, при вычислении значений для полей _EC2\_\*_ будет использоваться не строка _"/latest"_, а значение _default\(string\)_ — _null_\. 

В описанном выше легко убедиться, обратившись к соответствующему API:

```cpp
var arr = new[]
{
  EC2InstanceMetadata.EC2_APITOKEN_URL,
  EC2InstanceMetadata.EC2_DYNAMICDATA_ROOT,
  EC2InstanceMetadata.EC2_USERDATA_ROOT,
  EC2InstanceMetadata.EC2_METADATA_ROOT
};

foreach(var item in arr)
  Console.WriteLine(item);
```

Результат выполнения кода:

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

Как видите, ни в одной строке нет литерала _"/latest"_\. 

А вот ошибка это или нет — вопрос открытый\. Порядок инициализации полей поменяли  [отдельным коммитом](https://github.com/aws/aws-sdk-net/commit/8cf5524d5a5cb2b6749c3d1e465770390e420a13)\. В этом же коммите поля декорировали атрибутом _Obsolete_\. Хотя если не предполагается использование фактического значения _LATEST_, лучше просто его не использовать\. Так код не будет никого смущать\. 

**Issue \#4**

```cpp
public IRequest Marshall(GetObjectTorrentRequest getObjectTorrentRequest)
{
  IRequest request = new DefaultRequest(getObjectTorrentRequest, "AmazonS3");

  request.HttpMethod = "GET";

  if (getObjectTorrentRequest.IsSetRequestPayer())
    request.Headers
           .Add(S3Constants.AmzHeaderRequestPayer,  
                S3Transforms.ToStringValue(getObjectTorrentRequest.RequestPayer
                                                                  .ToString()));

  if (getObjectTorrentRequest.IsSetRequestPayer())
    request.Headers
           .Add(S3Constants.AmzHeaderRequestPayer, 
                S3Transforms.ToStringValue(getObjectTorrentRequest.RequestPayer
                                                                  .ToString()));

  if (getObjectTorrentRequest.IsSetExpectedBucketOwner())
    request.Headers
           .Add(S3Constants.AmzHeaderExpectedBucketOwner, 
                S3Transforms.ToStringValue(
                  getObjectTorrentRequest.ExpectedBucketOwner));
  ....
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Services/S3/Custom/Model/Internal/MarshallTransformations/GetObjectTorrentRequestMarshaller.cs#L43)\. 

<details>
   <summary>Предупреждение PVS\\\-Studio — V3029</summary>

The conditional expressions of the 'if' statements situated alongside each other are identical\. Check lines: 41, 43\. GetObjectTorrentRequestMarshaller\.cs 41


</details>


Первые два оператора _if_ полностью дублируют друг друга как условиями, так и телами\. Или один из них лишний и его нужно убрать, или в одном из операторов должно быть другое условие и другие действия\.

**Issue \#5**

```cpp
public string Region 
{ 
  get 
  {
    if (String.IsNullOrEmpty(this.linker.s3.region))
    {
      return "us-east-1";
    }
    return this.linker.s3.region; 
  } 

  set 
  {
    if (String.IsNullOrEmpty(value))
    {
      this.linker.s3.region = "us-east-1";
    }
    this.linker.s3.region = value; 
  } 
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Services/DynamoDBv2/Custom/DataModel/S3Link.cs#L114)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3008</summary>

The 'this\.linker\.s3\.region' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 116, 114\. S3Link\.cs 116


</details>


Интересный код\. С одной стороны, в нём есть ошибка\. С другой — при работе исключительно со свойством _Region_ она себя не проявит\.

Сама ошибка затаилась в методе доступа _set_\. В свойство _this\.linker\.s3\.region_ всегда будет записано значение _value_: проверка _String\.IsNullOrEmpty\(value\)_ ни на что не влияет\. При этом в методе доступа _get_ также есть проверка: если _linker\.s3\.region_ — _null_ или пустая строка, свойство вернёт значение _"us\-east\-1"_\. 

Получается вот что\. Если пользователь работает только со свойством _Region_, для него нет разницы, есть ошибка или нет\. Но её в любом случае лучше исправить\.

**Issue \#6**

```cpp
internal string 
GetPreSignedURLInternal(....)
{
  ....
  RegionEndpoint endpoint = RegionEndpoint.GetBySystemName(region);
  var s3SignatureVersionOverride 
    = endpoint.GetEndpointForService("s3",
                                     Config.ToGetEndpointForServiceOptions())
              .SignatureVersionOverride;

  if (s3SignatureVersionOverride == "4" || s3SignatureVersionOverride == null)
  {
    signatureVersionToUse = SignatureVersion.SigV4;
  }

  var fallbackToSigV2 = useSigV2Fallback && !AWSConfigsS3.UseSigV4SetExplicitly;
  if (   endpoint?.SystemName == RegionEndpoint.USEast1.SystemName 
      && fallbackToSigV2)
  {
    signatureVersionToUse = SignatureVersion.SigV2;
  }
  ....
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Services/S3/Custom/AmazonS3Client.Extensions.cs#L111)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3095</summary>

The 'endpoint' object was used before it was verified against null\. Check lines: 111, 118\. AmazonS3Client\.Extensions\.cs 111


</details>


Странный порядок работы с потенциальными _null_\-значениями притягивает баги\. Бывает, что сначала значение используется, потом проверяется на _null_\. И здесь начинаются головоломки: то ли это ошибка и возможно исключение, то ли просто проверка избыточная, а _null_\-значения в переменной быть не может, то ли ещё что\.\.\.

Здесь аналогичная ситуация\. Сначала к переменной _endpoint_ обращаются безусловно \(_endpoint\.GetEndpointForService_\), а ниже по коду используют оператор условного доступа \(_endpoint?\.SystemName_\)\.

**Issue \#7**

```cpp
public class GetObjectMetadataResponse : AmazonWebServiceResponse
{
  ....
  private ServerSideEncryptionMethod 
    serverSideEncryption;

  private ServerSideEncryptionCustomerMethod 
    serverSideEncryptionCustomerMethod;
  ....

  public ServerSideEncryptionCustomerMethod  
    ServerSideEncryptionCustomerMethod 
  { 
    get
    {
      if (this.serverSideEncryptionCustomerMethod == null)
        return ServerSideEncryptionCustomerMethod.None;

      return this.serverSideEncryptionCustomerMethod;
    }
    set { this.serverSideEncryptionCustomerMethod = value; } 
  }


  // Check to see if ServerSideEncryptionCustomerMethod property is set
  internal bool IsSetServerSideEncryptionCustomerMethod()
  {
    return this.serverSideEncryptionCustomerMethod != null;
  }

  ....

  public ServerSideEncryptionMethod 
    ServerSideEncryptionMethod
  {
    get 
    {
      if (this.serverSideEncryption == null)
        return ServerSideEncryptionMethod.None;

      return this.serverSideEncryption; 
    }
    set { this.serverSideEncryption = value; }
  }

  // Check to see if ServerSideEncryptionCustomerMethod property is set
  internal bool IsSetServerSideEncryptionMethod()
  {
    return this.serverSideEncryptionCustomerMethod != null;
  }
  ....
}
```

Ссылки на GitHub: [\#1](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Services/S3/Custom/Model/GetObjectMetadataResponse.cs#L311), [\#2](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Services/S3/Custom/Model/GetObjectMetadataResponse.cs#L334)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3013</summary>

It is odd that the body of 'IsSetServerSideEncryptionMethod' function is fully equivalent to the body of 'IsSetServerSideEncryptionCustomerMethod' function\. GetObjectMetadataResponse\.cs 311


</details>


Предупреждаю: от похожих названий сейчас начнёт рябить в глазах\. Думаю, из\-за этого ошибка и возникла\.

В типе _GetObjectMetadataResponse_ определены свойства _ServerSideEncryptionMethod_ и _ServerSideEncryptionCustomerMethod_\. Они используют соответствующие backing поля —_serverSideEncryption_ и _serverSideEncryptionCustomerMethod_:

* _ServerSideEncryptionMethod_ \-\> _serverSideEncryption_;
* _ServerSideEncryptionCustomerMethod_ \-\> _serverSideEncryptionCustomerMethod_\. 

А ещё есть методы _IsSetServerSideEncryptionMethod_ и _IsSetServerSideEncryptionCustomerMethod_\. Как можно предположить, они тоже используют backing\-поля _serverSideEncryption_ и _serverSideEncryptionCustomerMethod_ соответственно\.\.\. но нет\. Из\-за ошибки оба метода проверяют одно и то же поле — _serverSideEncryptionCustomerMethod_\.

```cpp
// Check to see if ServerSideEncryptionCustomerMethod property is set
internal bool IsSetServerSideEncryptionCustomerMethod()
{
  return this.serverSideEncryptionCustomerMethod != null;
}

// Check to see if ServerSideEncryptionCustomerMethod property is set
internal bool IsSetServerSideEncryptionMethod()
{
  return this.serverSideEncryptionCustomerMethod != null;
}
```

Метод _IsSetServerSideEncryptionMethod_ должен проверять поле _serverSideEncryption_\.

**Issue \#8**

```cpp
public string GetDecryptedPassword(string rsaPrivateKey)
{
  RSAParameters rsaParams;
  try
  {
    rsaParams = new PemReader(
                  new StringReader(rsaPrivateKey.Trim())
                ).ReadPrivatekey();
  }
  catch (Exception e)
  {
    throw new AmazonEC2Exception("Invalid RSA Private Key", e);
  }

  RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
  rsa.ImportParameters(rsaParams);

  byte[] encryptedBytes = Convert.FromBase64String(this.PasswordData);
  var decryptedBytes = rsa.Decrypt(encryptedBytes, false);

  string decrypted = Encoding.UTF8.GetString(decryptedBytes);
  return decrypted;
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Services/EC2/Custom/Model/GetPasswordDataResponse.Extensions.cs#L48)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3114</summary>

IDisposable object 'rsa' is not disposed before method returns\. GetPasswordDataResponse\.Extensions\.cs 48


</details>


Тип _RSACryptoServiceProvider_ реализует интерфейс _IDisposable_\. Однако в этом коде метод _Dispose_ не вызывается ни явно, ни косвенно \(через использование _rsa_ в выражении _using_\)\. 

Я не могу сказать, насколько это критично в данном случае\. Но в целом кажется, что _Dispose_ для очистки данных лучше всё\-таки вызывать, особенно когда код работает с паролями и т\. п\.

**Issue \#9**

```cpp
public class ResizeJobFlowStep
{
  ....
  public OnFailure? OnFailure
  {
    get { return  this.OnFailure; }
    set { this.onFailure = value; }
  }
  ....
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Services/ElasticMapReduce/Custom/Model/ResizeJobFlowStep.cs#L171)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3010</summary>

Possible infinite recursion inside 'OnFailure' property\. ResizeJobFlowStep\.cs 171


</details>


Из\-за опечатки в _get_\-accessor'е свойства _OnFailure_ используется не backing\-поле _onFailure_, а само свойство — _OnFailure_\. При попытке получить значение свойства возникает бесконечная рекурсия, которая приводит к исключению _StackOverflowException_\. 

Ошибку легко воспроизвести, воспользовавшись соответствующим API:

```cpp
ResizeJobFlowStep obj = new ResizeJobFlowStep();
_ = obj.OnFailure;
```

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

![1057_AWS_SDK_NET_ru/image3.png](https://import.viva64.com/docx/blog/1057_AWS_SDK_NET_ru/image3.png)

**Issue \#10**

```cpp
private static void 
writeConditions(Statement statement, JsonWriter generator)
{
  ....
  IList<string> conditionValues = keyEntry.Value;
  if (conditionValues.Count == 0)
    continue;

  generator.WritePropertyName(keyEntry.Key);

  if (conditionValues.Count > 1)
  {
    generator.WriteArrayStart();
  }

  if (conditionValues != null && conditionValues.Count != 0)
  {
    foreach (string conditionValue in conditionValues)
    {
      generator.Write(conditionValue);
    }
  }
  ....
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Core/Amazon.Auth/AccessControlPolicy/Internal/JsonPolicyWriter.cs#L233)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3095</summary>

The 'conditionValues' object was used before it was verified against null\. Check lines: 233, 238\. JsonPolicyWriter\.cs 233


</details>


Код выглядит подозрительно: сначала идёт разыменование ссылки из переменной _conditionValues_, а затем её проверка на _null_\. При этом значение переменной не изменяется\. Соответственно, если ссылка была нулевой, уже при первом обращении — _conditionValues\.Count \=\= 0_ — возникнет исключение _NullReferenceException_\. 

Этот код может содержать как ошибку, так и избыточную проверку на неравенство _null_\.

Отмечу одну вещь\. У меня сложилось впечатление, что в проекте любят добавлять проверки на равенство _null_ на всякий случай\. :\) Ниже перечислю несколько таких примеров\.

```cpp
string[] settings 
  = value.Split(validSeparators, StringSplitOptions.RemoveEmptyEntries);

if (settings == null || settings.Length == 0)
    return LoggingOptions.None;
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/6fb0a41e7a4d4ba5e6cd9cf947beb65f06d8e58b/sdk/src/Core/AWSConfigs.cs#L278)\. 

Метод _String\.Split_ не возвращает _null_\. Похожая проверка есть [здесь](https://github.com/aws/aws-sdk-net/blob/6fb0a41e7a4d4ba5e6cd9cf947beb65f06d8e58b/sdk/src/Core/Amazon.Util/AWSSDKUtils.cs#L587)\. 

Другой пример похожей проверки:

```cpp
var constructors 
  = GetConstructors(objectTypeWrapper, validConstructorInputs).ToList();

if (constructors != null && constructors.Count > 0)
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/6fb0a41e7a4d4ba5e6cd9cf947beb65f06d8e58b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Utils.cs#LL278C16-L278C16)\.

Метод _Enumerable\.ToList_ не возвращает _null_, так что значение переменной _constructors_ никогда не будет равно _null_\. 

А пример ниже ближе к изначальному — тоже сначала разыменовали ссылку, а затем проверяют её значение на _null_:

```cpp
TraceSource ts = new TraceSource(testName, sourceLevels);
ts.Listeners.AddRange(AWSConfigs.TraceListeners(testName));

// no listeners? skip
if (ts.Listeners == null || ts.Listeners.Count == 0)
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/6fb0a41e7a4d4ba5e6cd9cf947beb65f06d8e58b/sdk/src/Core/Amazon.Runtime/Internal/Util/Logger.Diagnostic.cs#L143)\.

Хотя случаев, когда свойство _Listeners_ может иметь значение _null_, я не нашёл\. В \.NET возвращаемое значение свойства так и вовсе размечено null\-forgiving оператором \([ссылка на GitHub](https://github.com/dotnet/runtime/blob/3181f9c925ba65a7bbab0dc310a8abc1e3bfe68e/src/libraries/System.Diagnostics.TraceSource/src/System/Diagnostics/TraceSource.cs#L523)\):

```cpp
public TraceListenerCollection Listeners
{
  get
  {
    Initialize();
    return _listeners!;
  }
}
```

**Issue \#11**

```cpp
private static string GetXamarinInformation()
{
  var xamarinDevice = Type.GetType("Xamarin.Forms.Device, Xamarin.Forms.Core");
  if (xamarinDevice == null)
  {
    return null;
  }

  var runtime = xamarinDevice.GetProperty("RuntimePlatform")
                            ?.GetValue(null)
                            ?.ToString() ?? "";

  var idiom = xamarinDevice.GetProperty("Idiom")
                          ?.GetValue(null)
                          ?.ToString() ?? "";

  var platform = runtime + idiom;

  if (string.IsNullOrEmpty(platform))
  {
    platform = UnknownPlatform;
  }

  return string.Format(CultureInfo.InvariantCulture, "Xamarin_{0}", "Xamarin");
}
```

[Ссылка на GitHub](https://github.com/aws/aws-sdk-net/blob/cae0a7b336f1d3d2fdae653095f64a3df82f3cc1/sdk/src/Core/Amazon.Util/Internal/_netstandard/InternalSDKUtils.netstandard.cs#L70)\.

<details>
   <summary>Предупреждение PVS\\\-Studio — V3137</summary>

The 'platform' variable is assigned but is not used by the end of the function\. InternalSDKUtils\.netstandard\.cs 70


</details>


Последняя строка метода выглядит очень странно\. С помощью _String\.Format_ в шаблон _"Xamarin\_\{0\}" _подставляют строковый литерал _"Xamarin"\._ При этом значение переменной _platform_, которое может хранить необходимую информацию, игнорируется\. Выглядит странно\. 

Предположу, что выражение _return_ должно выглядеть так:

```cpp
return string.Format(CultureInfo.InvariantCulture, "Xamarin_{0}", platform);
```

Кстати, рядом есть похожий метод с получением информации о Unity\. Он написан по схожему шаблону, но возвращаемое значение уже формируется нормально:

```cpp
private static string GetUnityInformation()
{
  var unityApplication 
    = Type.GetType("UnityEngine.Application, UnityEngine.CoreModule");
  if (unityApplication == null)
  {
    return null;
  }

  var platform = unityApplication.GetProperty("platform")
                                ?.GetValue(null)
                                ?.ToString() ?? UnknownPlatform;

  return string.Format(CultureInfo.InvariantCulture, "Unity_{0}", platform);
}
```

## Заключение

Обо всех найденных проблемах я уведомил разработчиков ещё до выхода статьи — вот [ссылка на баг\-репорт](https://github.com/aws/aws-sdk-net/issues/2627)\.

Хотите проверить, нет ли в вашем проекте похожих проблем? Проанализируйте код с помощью PVS\-Studio\.

[![getTrialImageLink](https://cdn.pvs-studio.ru/media/get_trial_insert_ru.png)](https://pvs-studio.ru/ru/pvs-studio-download/)