﻿# Nintendo Switch: Drop Test исходников эмулятора Ryujinx

Такие компании, как Sony, Microsoft и Nintendo, каждое поколение радуют своих потребителей новыми консолями и различными играми на них\. Но вот беда, часть игр является эксклюзивами своих платформ, а приставка иной раз стоит как дорогая комплектующая ПК или как полноценный компьютер\. Что же делать? На выручку приходят эмуляторы\. Рынок полон подобными проектами, некоторые даже выкладываются в сеть с открытым исходным кодом\. Обратим внимание на эмуляторы Nintendo Switch\. В сети популярными ответами станут проекты Ryujinx и Yuzu\. Давайте проведем проверку кода эмулятора Ryujinx и узнаем, сколько интересных ошибок можно найти с помощью статического анализа\.

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

## Введение

[Ryujinx](https://ryujinx.org/) \(имя Ryujinx основано на имени "Ryujin"\(Рюдзин\) \- имя Мифического \(Морского Бога\) Дракона\) \- это молодой проект эмулятора Nintendo Switch с открытым исходным кодом, написанный на C\#\. Этот эмулятор нацелен на обеспечение превосходной точности и производительности, удобного интерфейса\.

Проект Ryujinx конкурирует со своим старшим братом Yuzu, написанным на языке C\+\+, код которого уже рассматривался в одной из наших [статей](https://pvs-studio.ru/ru/blog/posts/cpp/0651/)\. Каждый из этих проектов имеет свои положительные и отрицательные стороны\. Но оставим старичка в покое, и взглянем на молодой проект с помощью нашего статического анализатора кода [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/)\. Исходный код "Дракона" был взят из его официального репозитория [на GitHub](https://github.com/Ryujinx/Ryujinx)\.

Начнем обзор ошибок проекта Ryujinx с ошибок, которые могут вызвать исключение _NullReferenceException_\.

## Возможное возникновение NullReferenceException

Частый случай среди ошибок – использование переменных, значение которых может быть _null_, без проверки на этот самый _null_\. Или же бывает, как в случае ниже\.

[V3095](https://pvs-studio.ru/ru/docs/warnings/v3095/) The 'firmwareVersion' object was used before it was verified against null\. Check lines: 1157, 1159\. MainWindow\.cs

```cpp
private void HandleInstallerDialog(FileChooserDialog fileChooser){
    ....
    
    string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}";

     if (firmwareVersion == null)
    {
        ....
    }
    ....
}
```

Здесь _firmwareVersion_ используется прежде, чем производится его проверка на _null,_ — это может вызвать соответствующую ошибку [V3095](https://pvs-studio.ru/ru/docs/warnings/v3095/)\. Данное сообщение было выдано многократно:

* V3095 The '\_profileEntry\.Text' object was used before it was verified against null\. Check lines: 34, 40\. ProfileDialog\.cs 34
* V3095 The 'owner' object was used before it was verified against null\. Check lines: 161, 166\. KThread\.cs 161
* V3095 The 'Owner' object was used before it was verified against null\. Check lines: 1084, 1103\. KThread\.cs 1084
* V3095 The '\_nsdSettings' object was used before it was verified against null\. Check lines: 44, 48\. FqdnResolver\.cs 44
* V3095 The 'texture' object was used before it was verified against null\. Check lines: 354, 362\. TextureBindingsManager\.cs 354

[V3080](https://pvs-studio.ru/ru/docs/warnings/v3080/) Possible null dereference\. Consider inspecting 'firmwareVersion'\. MainWindow\.cs 605

```cpp
public void LoadApplication(string path)
{
    ....
    firmwareVersion = _contentManager.GetCurrentFirmwareVersion();

    RefreshFirmwareLabel();

    string message =
    $"No installed firmware was found but Ryujinx was able to install firmware
      {firmwareVersion.VersionString} from the provided game.
      \nThe emulator will now start.";
    ....
}
```

Здесь же переменная _firmwareVersion_ используется без проверки\. Если взглянуть на метод _GetCurrentFirmwareVersion_, то можно увидеть, что есть вероятность того, что вернется не ссылка на объект, а _null_, что, в свою очередь, приведет к ошибке:

```cpp

public SystemVersion GetCurrentFirmwareVersion()
{
    LoadEntries();

    lock (_lock)
    {
        ....

        if (romfs.OpenFile(out IFile systemVersionFile,
            "/file".ToU8Span(),
            OpenMode.Read).IsSuccess())
        {
            return new SystemVersion(systemVersionFile.AsStream());
        }
        ....
    }

    return null;
}
```

Ошибки такого типа достаточно часто встречаются в этом проекте:

* V3080 Possible null dereference\. Consider inspecting 'region'\. KMemoryManager\.cs 46
* V3080 Possible null dereference\. Consider inspecting 'node'\. KPageTableBase\.cs 2250
* V3080 Possible null dereference\. Consider inspecting 'node'\. KPageTableBase\.cs 2316
* V3080 Possible null dereference\. Consider inspecting 'node'\. KPageTableBase\.cs 2408
* V3080 Possible null dereference\. Consider inspecting 'dimension'\. Demangler\.cs 361

[V3125](https://pvs-studio.ru/ru/docs/warnings/v3125/) The 'Owner' object was used after it was verified against null\. Check lines: 1084, 1082\. KThread\.cs 1084

```cpp
private void FreeResources()
{
    Owner?.RemoveThread(this);

    if (_tlsAddress != 0 &&
        Owner.FreeThreadLocalStorage(_tlsAddress) != KernelResult.Success)
    {
      ....
    }
    ....
}
```

В этот раз мы видим, что проверка на _null_ у нас есть, но выполняется она только один раз\. Хотя переменная используется здесь дважды\. При первой встрече с _Owner_ его метод вызывается только в том случае, когда переменная не равна _null_\. При второй же упустили этот момент\. Если же _Owner в первом случае будет_ _null_, то метод просто не вызовется, тогда как во втором случае при попытке вызвать метод может возникнуть ошибка _NullReferenceException_\.

[V3105](https://pvs-studio.ru/ru/docs/warnings/v3105/) The 'result' variable was used after it was assigned through null\-conditional operator\. NullReferenceException is possible\. Client\.cs 213

```cpp
private byte[] Receive(int clientId, int timeout = 0)
{
    ....

    var result = _client?.Receive(ref endPoint);

    if (result.Length > 0)
    {
        ....
    }
    ....
}
```

В этом фрагменте кода мы видим использование null\-conditional оператора для присвоения результата в переменную _result_\. Но в дальнейшем эта переменная не проверяется на _null_, что может вызвать ошибку в строке с условием, ведь мы не можем измерить длину _null_\.

[V3042](https://pvs-studio.ru/ru/docs/warnings/v3042/) Possible NullReferenceException\. The '?\.' and '\.' operators are used for accessing members of the 'data' object Client\.cs 254

```cpp
public void ReceiveLoop(int clientId)
{
    ....
    byte[] data = Receive(clientId);

    if (data.Length == 0)
    {
        continue;
    }
    ....
}
```

Здесь мы видим, что _data_ присваивают результат выполнения функции, но давайте взглянем внутрь неё и узнаем, что она может вернуть:

```cpp
private byte[] Receive(int clientId, int timeout = 0)
{
    ....
    var result = _client?.Receive(ref endPoint);

    if (result.Length > 0)
    {
        ....
    }

    return result;
    ....
}
```

Кажется, или мы уже где\-то видели этот код? Конечно, ранее описанная ошибка привела к ещё одной\.

## Логические ошибки

[V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'result \!\= KernelResult\.Success' is always false\. KMemoryRegionManager\.cs 169

```cpp
private KernelResult AllocatePagesImpl(....)
{
    ....
    KernelResult result = pageList.AddRange(address, blockPagesCount);

    if (result != KernelResult.Success)
    ....
}
```

Итак, первая логическая ошибка сообщает нам, что условие всегда ложно\. С чего бы это? Для этого нам нужно провести вскрытие метода _AddRange_\.

```cpp
public KernelResult AddRange(....)
{
    ....
    return KernelResult.Success;
}
```

Опустив алгоритм метода, остановимся на результате\. Вызов _return_ происходит лишь один раз, и, соответственно, единственное возможное значение переменной _result_ лишь одно\. Либо метод ещё не закончен, либо произошла излишняя проверка результата выполнения метода\. Ошибка [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) встретилась в проекте далеко не единожды:

* V3022 Expression 'result \!\= KernelResult\.Success' is always false\. KProcess\.cs 639
* V3022 Expression 'TerminationRequested' is always false\. KThread\.cs 375
* V3022 Expression 'resultCode \=\= ResultCode\.Success' is always true\. IManagerForApplication\.cs 32
* V3022 Expression 'resultCode \=\= ResultCode\.Success' is always true\. IManagerForSystemService\.cs 32
* V3022 Expression 'result \!\= ResultCode\.Success' is always false\. IApplicationFunctions\.cs 376

[V3003](https://pvs-studio.ru/ru/docs/warnings/v3003/) The use of 'if \(A\) \{\.\.\.\} else if \(A\) \{\.\.\.\}' pattern was detected\. There is a probability of logical error presence\. Check lines: 26, 30\. ZbcSetTableArguments\.cs 26

```cpp
public uint this[int index]
{
    get
    {
        if (index == 0)
        {
            return element0;
        }
        else if (index == 1)
        {
            return element1;
        }
        else if (index == 2)
        {
            return element2;
        }
        else if (index == 2)
        {
            return element3;
        }

        throw new IndexOutOfRangeException();
    }
}
```

Ошибка повторяющихся условий\. Есть несколько возможных причин её появления, старый добрый copy\-paste, или же банальная невнимательность\. Здесь, скорее всего, второй вариант\. Ситуации, вызванные опечатками с использованием чисел 0, 1, 2, частые гости в программировании\. Если эта тема вас заинтересовала, можете ознакомиться с ней подробнее в нашей [статье](https://pvs-studio.ru/ru/blog/posts/cpp/0713/)

[V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'Base \=\= null' is always false\. Demangler\.cs 2049

```cpp
private BaseNode ParseExpression()
{
    ....
    BaseNode Base = ParseExpression();
    if (Base == null)
    {
        return null;
    }

    BaseNode subscript = ParseExpression();
    if (Base == null)
    {
        return null;
    }
    ....
}
```

Итак, выше мы видим аналогичную ошибку, _Base_ дважды проверяется на _null_\. В этот раз её причиной стал, скорее всего, злополучный copy\-paste\. Из\-за этого один и тот же фрагмент содержит и следующую ошибку: [V3021](https://pvs-studio.ru/ru/docs/warnings/v3021/) There are two 'if' statements with identical conditional expressions\. The first 'if' statement contains method return\. This means that the second 'if' statement is senseless Demangler\.cs 2043

Скорее всего второе условие должно было проверить переменную _subscript_, что в свою очередь убьет сразу двух зайцев:

```cpp
BaseNode subscript = ParseExpression();
if (subscript == null)
{
    return null;
}
```

[V3009](https://pvs-studio.ru/ru/docs/warnings/v3009/) It's odd that this method always returns one and the same value of 'ResultCode\.Success'\. IApplicationFunctions\.cs 116

```cpp
public ResultCode GetDesiredLanguage(ServiceCtx context)
{
    ....
    if (firstSupported > (int)SystemState.TitleLanguage.Chinese)
    {
        Logger.Warning?.Print(LogClass.ServiceAm,
            "Application has zero supported languages");

        context.ResponseData.Write(desiredLanguageCode);

        return ResultCode.Success;
    }
    ....
    return ResultCode.Success;
}
```

В проекте Ryujinx встретилось несколько функций, работающих с набором значений _ResultCode_\. Одну мы уже встретили ранее\. Однако ни одна из них не использовала все значения, останавливаясь лишь на _Success_\. Возможно, разработчики ещё не закончили работу, или в функциях закралась ошибка\. Из\-за этого было необходимо использовать другой результат\. Мы уже видели, что другой код, работающий с результатами этих функций, может вызывать ошибки или работать неправильно\. Подобные сообщения ещё несколько раз встречались в проекте:

* V3009 It's odd that this method always returns one and the same value of 'ResultCode\.Success'\. IAddOnContentManager\.cs 52
* V3009 It's odd that this method always returns one and the same value of 'ResultCode\.Success'\. ISystemSettingsServer\.cs 30
* V3009 It's odd that this method always returns one and the same value of 'Status\.Success'\. ConsumerBase\.cs 131
* V3009 It's odd that this method always returns one and the same value of 'ResultCode\.Success'\. IBinder\.cs 14
* V3009 It's odd that this method always returns one and the same value of 'true'\. AstcDecoder\.cs 307

[V3064](https://pvs-studio.ru/ru/docs/warnings/v3064/) Potential division by zero\. Consider inspecting denominator 'blockWidth'\. AstcDecoder\.cs 71

```cpp
public AstcDecoder(
    ReadOnlyMemory<byte> inputBuffer,
    Memory<byte> outputBuffer,
    int blockWidth,
    int blockHeight,
    int width,
    int height,
    int depth,
    int levels,
    int layers)
{
    ....
    if ((uint)blockWidth > 12)
    {
        throw new ArgumentOutOfRangeException(nameof(blockWidth));
    }

    if ((uint)blockHeight > 12)
    {
        throw new ArgumentOutOfRangeException(nameof(blockHeight));
    }
    ....
            level.BlockCountX =
                (level.ImageSizeX + blockWidth - 1) / blockWidth;
            level.BlockCountY =
                (level.ImageSizeY + blockHeight - 1) / blockHeight;
    ....
}
```

Данное сообщение говорит нам о том, что может произойти деление на ноль\. Благодаря условию проверки диапазон чисел сжался от 0 до 11, что не исключает возможности присвоения этим переменным нуля\. Следует обезопасить этот участок от подобной ошибки\. 

[V3171](https://pvs-studio.ru/ru/docs/warnings/v3171/) The value used as the size of an array could reach \-1\. Consider inspecting: deviceCount\. AudioDevice\.cs 133

```cpp
public string[] ListAudioDeviceName()
{
    int deviceCount = _sessions.Length;

    if (!_isUsbDeviceSupported)
    {
        deviceCount--;
    }

    string[] result = new string[deviceCount];
    ....
}
```

Здесь ошибка кроется в том, что если _\_sessions\.Length_ будет равна нулю, то и _deviceCount_ может стать \-1, что вызовет ошибку при создании массива\. Следует сделать проверку, чтобы избежать этой ситуации\.

## Излишний код

[V3063](https://pvs-studio.ru/ru/docs/warnings/v3063/) A part of conditional expression is always true if it is evaluated: value \>\= 0\. NumberFormatter\.cs 96

```cpp
public static string FormatUint(uint value)
{
    if (value <= MaxDecimal && value >= 0)
    {
        return value.ToString(CultureInfo.InvariantCulture) + "u";
    }
    ....
}
```

Итак, анализатор сообщает нам о том, что условие _value \>\= 0 _всегда истинно\. Причина проста \-  диапазон значения типа _uint_ начинается с 0, заканчивая числом 4294967295, т\.е\. переменные типа _uint_ всегда больше или равны 0\. Вывод прост \- проверка _value_ здесь излишняя\. Так же было обнаружено ещё несколько подобных ситуаций:

* V3063 A part of conditional expression is always false if it is evaluated: backendDisconnected\. SoundIoHardwareDeviceDriver\.cs 68
* V3063 A part of conditional expression is always true if it is evaluated: info \!\= null\. SynchronizationManager\.cs 132
* V3063 A part of conditional expression is always false if it is evaluated: flush\. TextureManager\.cs 942

[V3139](https://pvs-studio.ru/ru/docs/warnings/v3139/) Two or more case\-branches perform the same actions\. Demangler\.cs 2251

```cpp
private BaseNode ParseExpression()
{
    ....
    case 'm':
        _position += 2;
        return ParseBinaryExpression("%");
    case 'M':
        _position += 2;
        return ParseBinaryExpression("%");
    ....
}
```

Старый добрый оператор _switch_\. В данном примере он довольно большой, и нет, ничего страшного, что в один момент начинается путаница\. В данном случае возможны два варианта\. Либо эти две ветки _case_ должны выполнять одну и туже операцию и их можно объединить\. Либо здесь закралась ошибка, ведомая лишь одним разработчикам\. Подобных случаев в проекте насчитывается ещё 19\.

[V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'mainNca \!\= null' is always true\. ApplicationLoader\.cs 272

```cpp
public void LoadNsp(string nspFile)
{
    ....
    if (mainNca == null)
    {
        Logger.Error?.Print(LogClass.Loader,
            "Unable to load NSP: Could not find Main NCA");

        return;
    }

    if (mainNca != null)
    {
        _device.Configuration.ContentManager.ClearAocData();
        _device.Configuration.ContentManager.AddAocData(nsp,
            nspFile,
            mainNca.Header.TitleId,
            _device.Configuration.FsIntegrityCheckLevel);

        LoadNca(mainNca, patchNca, controlNca);

        return;
    }

    ....
}
```

Анализатор сообщает нам, что второе условие в указанном месте всегда истинно\. И это очевидно, ведь прямо перед этим выполнялась уже проверка _mainNca_ ровно на противоположное значение\. От чего встает вопрос, а нужно ли вообще проводить вторую проверку, если переменная не меняется?

[V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'result \=\= null' is always false\. Demangler\.cs 2906

```cpp
private BaseNode ParseUnresolvedName(....)
{
    ....
    BaseNode qualifier = ParseSimpleId();
    if (qualifier == null)
    {
        return null;
    }
    if (result != null)
    {
        result = new QualifiedName(result, qualifier);
    }
    else if (isGlobal)
    {
        result = new GlobalQualifiedName(qualifier);
    }
    else
    {
        result = qualifier;
    }

    if (result == null)
    {
        return null;
    }
    ....
}
```

В этом участке кода мы можем заметить, что _result_ дважды проверяется на _null_, однако вторая проверка всегда ложна, ведь в любом случае _result_ присваивается новые производные от класса, _BaseNode_, который точно не равен _null_\. Проверка на _null_ переменных, которым присвоили новый экземпляр классов встречается ещё несколько раз в проекте:

* V3022 Expression 'result \=\= null' is always false\. Demangler\.cs 2848
* V3022 Expression 'result \=\= null' is always false\. Demangler\.cs 2833
* V3022 Expression 'result \=\= null' is always false\. Demangler\.cs 3094
* V3022 Expression 'result \=\= null' is always false\. Demangler\.cs 2930

[V3117](https://pvs-studio.ru/ru/docs/warnings/v3117/) Constructor parameter 'context' is not used\. IAccountServiceForAdministrator\.cs 12

```cpp
public IAccountServiceForAdministrator(ServiceCtx context,
    AccountServiceFlag serviceFlag)
{
    _applicationServiceServer = new ApplicationServiceServer(serviceFlag);
}
```

Довольно много сообщений [V3117](https://pvs-studio.ru/ru/docs/warnings/v3117/) выводит анализатор в проекте\. Подобные случаи вызываются подозрительным кодом\. Если аргумент не используется, то зачем его передавать? Возможно, эти функции недоработаны, или же разработчики просто перестраховались\. В итоге параметры не понадобились, а убрать просто забыли\. В проекте достаточно много подобных фрагментов кода:

* V3117 Constructor parameter 'context' is not used\. IAccountServiceForApplication\.cs 13
* V3117 Constructor parameter 'context' is not used\. IAccountServiceForSystemService\.cs 11
* V3117 Constructor parameter 'context' is not used\. IDeliveryCacheStorageService\.cs 12
* V3117 Constructor parameter 'memory' is not used\. NvHostAsGpuDeviceFile\.cs 17
* V3117 Constructor parameter 'condition' is not used\. IfBlock\.cs 17

[V3061](https://pvs-studio.ru/ru/docs/warnings/v3061/) Parameter 'instruction' is always rewritten in method body before being used\. EndConditionalBlock\.cs 18

```cpp
public static void Emit(byte[] instruction, CompilationContext context)
{
    // 20000000

    // Use the conditional begin instruction stored in the stack.
    instruction = context.CurrentBlock.BaseInstruction;
    ....
}
```

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

[V3030](https://pvs-studio.ru/ru/docs/warnings/v3030/) Recurring check\. The 'setFlags' condition was already verified in line 139\. InstEmitAluHelper\.cs 141

```cpp
public static void EmitGenericAluStoreA32(....)
{
    Debug.Assert(value.Type == OperandType.I32);

    if (rd == RegisterAlias.Aarch32Pc && setFlags)
    {
        if (setFlags)
        {
            // TODO: Load SPSR etc.

            EmitBxWritePc(context, value);
        }
        else
        {
            EmitAluWritePc(context, value);
        }
        ....
    }
    ....
}
```

Здесь спорная ситуация\. С точки зрения анализатора здесь есть лишняя проверка переменной _setFlags_\. Однако, судя по комментариям разработчиков, этот кусок кода в ветвях условия не доработан\. Удалять лишнюю проверку просто так не получится – код в ветвях отличается\. Но разобраться с этим кодом нужно уже сейчас\. Иначе есть риск, что он будет дописан, как есть, и тогда в коде останется ошибка с недостижимым кодом\. А в большем объёме кода её будет ещё сложнее найти\.

[V3138](https://pvs-studio.ru/ru/docs/warnings/v3138/) String literal contains potential interpolated expression\. Consider inspecting: keyHash\. CacheCollection\.cs 524

```cpp
public void AddValue(ref Hash128 keyHash, byte[] value)
{
    if (IsReadOnly)
    {
        Logger.Warning?.Print(LogClass.Gpu,
            "Trying to add {keyHash} on a read-only cache, ignoring.");
        ....
    }
    ....
}
```

Здесь кроется маленькая ошибка, которая вместо сообщения об ошибке выведет вместо неё название переменной, в которой та хранится\. Разработчик забыл указать доллар \($\), что включает форматирование строки\. 

[V3078](https://pvs-studio.ru/ru/docs/warnings/v3078/) Original sorting order will be lost after repetitive call to 'OrderBy' method\. Use 'ThenBy' method to preserve the original sorting\. ShaderConfig\.cs 413

```cpp
private static TextureDescriptor[] GetTextureOrImageDescriptors(....)
{
    ....
    foreach (var kv in dict.OrderBy(x => x.Key.Indexed)
                           .OrderBy(x => x.Key.Handle))
    {
            ....
    }
    ....
}
```

Чтобы понять, почему анализатор выдает нам предупреждение в этом месте, следует разобраться в принципах работы сортировки\. _OrderBy_ сортирует коллекцию независимо от того, были ли перед этим другие сортировки или нет\. В таком случае результат _dict\.OrderBy\(x \=\> x\.Key\.Indexed\)\.OrderBy\(x \=\> x\.Key\.Handle\) _равен _dict\.OrderBy\(x \=\> x\.Key\.Handle\)_\. Чтобы сохранить полученную ранее сортировку, необходимо использовать _ThenBy_\. Тогда это позволит сохранить первичную сортировку:

```cpp
var kv in dict.OrderBy(x => x.Key.Indexed).ThenBy(x => x.Key.Handle)
```

## Copy\-Paste

[V3013](https://pvs-studio.ru/ru/docs/warnings/v3013/) It is odd that the body of 'PrintLeft' function is fully equivalent to the body of 'PrintRight' function \(10, line 18\)\. PackedTemplateParameter\.cs 10

```cpp
public override void PrintLeft(TextWriter writer)
{
    foreach (BaseNode node in Nodes)
    {
        node.PrintLeft(writer);
    }
}

public override void PrintRight(TextWriter writer)
{
    foreach (BaseNode node in Nodes)
    {
        node.PrintLeft(writer);
    }
}
```

Яркий пример применения любимых \(и не очень\) copy\-paste\. Обе функции перебирают коллекции и вызывают для их элементов _PrintLeft_\. И ладно бы это была единственная функция для класса _BaseNode_, можно было бы убрать лишнюю функцию и радоваться\. Но у _BaseNode_ также имеется и _PrintRight_\. Это означает, что вторая функция выполняет неправильную операцию\.

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

По результатам проверки проекта Ryujinx нашим анализатором можно сказать, что проект содержит множество однотипных ошибок\. Но пока проект развивается, можно ожидать, что разработчики исправят баги и порадуют пользователей новым функционалом\. А пока, если вас заинтересовала тема статического анализа эмуляторов, приглашаю вас ознакомиться со [статьей](https://pvs-studio.ru/ru/blog/posts/cpp/0651/) по Yuzu\.