﻿# PVS\-Studio и 3DO\-эмуляторы

Зарекался больше не касаться эмуляции 3DO консоли, каюсь\. Но тут у меня появилась возможность поработать с такой экзотической штукой как статический анализатор кода, а именно PVS\-Studio\. Первое на чем я решил опробовать анализатор конечно же стал мой эмулятор консоли 3DO \(Phoenix Project\)\. В начале 90\-х была такая приставка, первая 32\-х битная консоль с CD\-приводом, помню нам с братом ее отец из Москвы привез, с тех пор никак оторваться не могу :\-\)\. Ну а раз подвернулся случай, то за одно и все основные проекты по эмуляции 3DO проверим\. Итак, поехали\.\.\.

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

## FreeDOCore – оригинальное ядро из репозитория в GoogleCode



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

_Сайт проекта_: [http://www\.freedo\.org/](http://www.freedo.org/)\.

_Ревизия_: 8\.

_Справка_: первый и можно сказать единственный эмулятор данной консоли, все остальные так или иначе основаны на данном коде\. 



**Ошибка** **записи\.** 

**V523:** **The** **'then'** **statement** **is** **equivalent** **to** **the** **'else'** **statement\.** 

**Line** **673** **\-** **clio\.cpp**

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

Такую прелесть обычный компилятор даже за Warning не считает:

```cpp
if((cregs[0x404])&0x200)
{
  ptr=0;
  while(len>=0)
  {
    b3=_xbus_GetDataFIFO();                
    ...
  }
  cregs[0x400]|=0x80;
}
else
{
  ptr=0;
  while(len>=0)
  {
    b3=_xbus_GetDataFIFO();                
    ...
  }
  cregs[0x400]|=0x80;
}
```

В данном случае не только лишний код, но и ошибка эмуляции, протокол XBUS двунаправленный, а в данном случае он работает всегда на чтение, что конечно не критично для эмуляции привода компакт\-дисков, но все же является грубой и потенциально опасной ошибкой для эмулируемых игр – а вдруг какой из них взбредет в голову прожечь болванку?\! А если серьезно, то вместо записи в эмулируемый интерфейс XBUS произойдет порча памяти, указанной в DMA регистрах, ну и конечно с таким подходом никак не получится сэмулировать такой раритет, как FZ\-EM256 3DO Memory Unit\.

**Ошибка** **чтения\.** 

**V614:** **Potentially** **uninitialized** **variable** **'val'** **used\.** 

**Line** **803** **\-** **clio\.cpp**

![0247_FreeDOCore_ru/image4.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image4.png)

Сначала я подумал, что это ерунда, но вдруг вспомнил о привидениях в FIFO\.\.\.

```cpp
unsigned short  __fastcall _clio_EIFIFO(unsigned short channel)
{
  unsigned int val,base,mask;
  ...
  if(FIFOI[channel].StartAdr!=0)//channel enabled
  {
    ...
  }
  return val;
}
```

Здесь в ряде случаев возможно чтение непредсказуемых значений, поскольку переменная _**val**_ инициализируется только при выполнении условия\. В теории FIFO DSP процессора после опустошения должно возвращать последнее считанное из него значение, эдакого призрака\. И хотя на практике чтения из пустого FIFO происходить не должны, но кто знает, вдруг после исправления заведется еще одна игра?

Итого две достойных внимания ошибки, честно говоря, думал будет больше\.

## FourDO – модифицированное ядро из репозитория на SourceForge

![0247_FreeDOCore_ru/image5.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image5.png)

_Сайт проекта_: [http://www\.fourdo\.com/](http://www.fourdo.com/)\.

_Ревизия_: 387\.

_Справка_: Данный проект прожил две жизни, первая – когда авторы начали самостоятельно писать эмулятор с нуля, но увы пациент впал в кому, а после вскрытия исходных кодов FreeDO, началась вторая жизнь, уже с новыми органами\. Итак, посмотрим, как приживаются имплантаты\.\.\.

**Исправленная, но все еще ошибка\.**

![0247_FreeDOCore_ru/image6.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image6.png)

Сразу хочу отметить последнюю ошибку в оригинальном ядре \(V614: Potentially uninitialized variable 'val' used\. Line 803 \- clio\.cpp\), с привидениями там расправились без лишних \(или таки с лишними?\) разговоров:

```cpp
unsigned short  __fastcall _clio_EIFIFO(unsigned short channel)
{
  unsigned int val,base,mask;

  base=0x400+(channel*16);
  mask=1<<channel;

  if(FIFOI[channel].StartAdr!=0)
  {
    ...
  }
  else
  {
    val=0;
    // JMK SEZ: What is this? 
    // It was commented out along with this whole "else" block,
    // but I had to bring this else block back from the dead
    // in order to initialize val appropriately.
  }

  return val;
}
```

А зря расправились\! Истинная проблема осталась нерешенной, зато со стороны все красиво, и о проблеме никто бы мог больше и не вспомнить\. Наиболее элегантным решением было бы объявить переменную _**val**_ как _**static**_ и инициализировать нулем, а более правильным – вынести за пределы функции и внести в список переменных для быстрых сохранений, ну а блок _**else**_ – удалить, чтобы не смущал\.

**Неисправленная ошибка\.** 

**V523:** **The** **'then'** **statement** **is** **equivalent** **to** **the** **'else'** **statement\.** 

**Line** **673** **\-** **clio\.cpp**

![0247_FreeDOCore_ru/image7.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image7.png)

Здесь не ступала нога "Создателя" – все как в оригинале\. Для тех, кто не в теме, "Создатель" \- это один из авторов FourDO – Виктор \(уж не знаю, его это имя или нет, тот еще Штирлиц \), он же автор 3DOPlay, еще одного форка FreeDO, ошибки в нем те же что и в оригинале\. С 3DOPlay была забавная история: Виктор решил потроллить и сказал, что он Создатель Эмулятора 3DO, а разработчики FreeDO якобы украли у него код\. К великому моему стыду, я как соавтор FreeDO, не смог пройти мимо и принимал активное участие в боевых действиях против его проекта 3DOPlay \- отличное надо сказать название, но кто\-то ляпнул \- три дупла и понеслась\.\.\. В итоге Виктор перешел в команду FourDO, и надо отдать должное, он единственный, кто хоть что\-то сделал в плане развития эмуляции 3DO, помимо авторов первоисточника\. 

**Пока еще не ошибка\.**

**V550: An odd precise comparison: Rez2T \!\= 0\. It's probably better to use a comparison with defined precision:** **fabs\(A** **\-** **B\)** **\>** **Epsilon\.** 

**Line 778 – madam\.cpp**

![0247_FreeDOCore_ru/image8.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image8.png)

Приведенный ниже код полностью работоспособен, но вызывает серьезную обеспокоенность\.

```cpp
static double Rez0T,Rez1T,Rez2T,Rez3T;
...
Rez2T=(signed int)((M20*V0+M21*V1+M22*V2)/65536.0);
if(Rez2T!=0) M=Nfrac16/(double)Rez2T;
else M=Nfrac16;
```

В оригинальном проекте _**Rez2T**_ имел тип _**int**_, вероятно авторы таким образом решили избавиться от предупреждений по преобразованию типов, избавились, но если вдруг кто\-то решит убрать принудительное приведение к типу _**signed int**_ – появится риск словить исключение сопроцессора при делении _**Nfrac16**_ на _**Rez2T**_\.

И еще один кусочек кода вызывает у меня серьезную обеспокоенность на этот раз здоровьем разработчиков из команды FourDO:

```cpp
void __fastcall _qrz_PushARMCycles(unsigned int clks)
{
    uint32 arm,cnt;
    int timers=21000000; //default
    int sp=0;
    if(sdf>0) sdf--;
    if(sf>0) sf--;
    if(unknownflag11>0)unknownflag11--;
    if(ARM_CLOCK<0x5F5E10)ARM_CLOCK=0x5F5E10;
    if(ARM_CLOCK>0x2FAF080)ARM_CLOCK=0x2FAF080;
    if(speedfixes>0&&speedfixes<0x186A1) 
      {/*sp=0x2DC6C0;*/ speedfixes--;}
    else if(speedfixes>0x186A1&&speedfixes<0x30D41)
      {/*if(sdf==0)sp=0x4C4B40; */speedfixes--;}
    else if(speedfixes<0) {sp=0x3D0900; speedfixes++;}
    else if(speedfixes>0x30D41) {/*sp=0x249F00;*/ speedfixes--;}
    else if(speedfixes==0x30D41||speedfixes==0x186A1) speedfixes=0;
    if((fixmode&FIX_BIT_TIMING_2)&&sf<=2500000) 
      {sp=0; timers=21000000; if(sf==0)sp=-(0x1C9C380-ARM_CLOCK);}
    if((fixmode&FIX_BIT_TIMING_1)/*&&jw>0*/&&sf<=1500000)
      {/*jw--;*/timers=1000000;sp=-1000000;}
    if((fixmode&FIX_BIT_TIMING_4)/*&&jw>0*/)
      {/*jw--;*/timers=1000000;sp=-1000000;}
    if((fixmode&FIX_BIT_TIMING_3)&&(sf>0&&sf<=100000)/*&&jw>0*/)
      {/*jw--;*/timers=900000;}
    if((fixmode&FIX_BIT_TIMING_5)&&sf==0/*&&jw>0*/)
      {/*jw--;*/timers=1000000;}
    if((fixmode&FIX_BIT_TIMING_6)/*&&jw>0*/)
      {/*jw--;*/timers=1000000; if(sf<=80000)sp=-23000000;}
    if(fixmode&FIX_BIT_TIMING_7){sp=-3000000; timers=21000000;}
    if((sf>0x186A0&&!(fixmode&FIX_BIT_TIMING_2))||
       ((fixmode&FIX_BIT_TIMING_2)&&sf>2500000))
         sp=-(12200000-ARM_CLOCK);
    if((ARM_CLOCK-sp)<0x2DC6C0)sp=-(0x2DC6C0-ARM_CLOCK);
    if((ARM_CLOCK-sp)!=THE_ARM_CLOCK)
    { THE_ARM_CLOCK=(ARM_CLOCK-sp);
        io_interface(EXT_ARM_SYNC,(void*)THE_ARM_CLOCK); 
        //fix for working with 4do
    }
    arm=(clks<<24)/(ARM_CLOCK-sp);
    qrz_AccARM+=arm*(ARM_CLOCK-sp);
    if( (qrz_AccARM>>24) != clks )
    {
        arm++;
        qrz_AccARM+=ARM_CLOCK;
        qrz_AccARM&=0xffffff;
    }
    qrz_AccDSP+=arm*SND_CLOCK;
    qrz_AccVDL+=arm*(VDL_CLOCK);
    if(_clio_GetTimerDelay())qrz_TCount+=arm*((timers)/
      (_clio_GetTimerDelay()));
}
```



Приведенный код с точки зрения анализатора правильный, но с точки зрения здравого смысла, делать это \(что "это" я могу лишь догадываться\) при учете тактов эмулируемого процессора – форменное безобразие, ниже код из оригинала:

```cpp
void __fastcall _qrz_PushARMCycles(unsigned int clks)
{
 uint32 arm;
        arm=(clks<<24)/ARM_CLOCK;
        qrz_AccARM+=arm*ARM_CLOCK;
        if( (qrz_AccARM>>24) != clks )
        {
                arm++;
                qrz_AccARM+=ARM_CLOCK;
                qrz_AccARM&=0xffffff;
        }
        qrz_AccDSP+=arm*SND_CLOCK;
        qrz_AccVDL+=arm*(VDL_CLOCK);
        if(_clio_GetTimerDelay())
            qrz_TCount+=arm*((__temporalfixes?12500000:25000000)/
               (_clio_GetTimerDelay()));
}
```

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

## Phoenix Emu\-Project – новому ядру – новые ошибки

![0247_FreeDOCore_ru/image9.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image9.png)



_Сайт проекта_: [http://www\.arts\-union\.ru](http://www.arts-union.ru) 

_Версия_: 1\.7 

_Справка_: заново написанный эмулятор 3DO, с маниакальной целью доведения эмуляции 3DO до совершенства, хотя задумывался мной как мультисистемный эмулятор с соответствующей инфраструктурой кода, но пока только 3DO\. 

**Ошибка** **–** **текстуры** **облезли\!** 

**V501:** **There** **are** **identical** **sub\-expressions** **to** **the** **left** **and** **to** **the** **right** **of** **the** **'\!\='** **operator:** **val\.flags \!\=** **val\.flags\.** 

**Line** **207** **\-** **gfx\_celengine\.h** 



![0247_FreeDOCore_ru/image10.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image10.png)



```cpp
struct gfxCelTexturePDECAttrib
{
    uint32 pre0;
    uint32 flags;

    int plutcount;
    uint16 plut[32];

    bool operator==(const gfxCelTexturePDECAttrib &val) const
    {
        if(val.pre0!=pre0)return false;
        if(val.flags!=val.flags)return false;
        if(val.plutcount!=plutcount)return false;
        for(int i=0;i<val.plutcount;i++)
        {
            if(val.plut[i]!=plut[i])return false;
        }
        return true;
    }
};
```



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

```cpp
if(val.flags!=flags)return false;
```

**Ошибка \-** **мусор** **на** **экране\!** 

**V579:** **The** **memset** **function** **receives** **the** **pointer** **and** **its** **size** **as** **arguments\.** **It** **is** **possibly** **a** **mistake\.** **Inspect** **the** **third** **argument\.** 

**Line** **36** **\-** **vdlp\_3do\.cpp**

![0247_FreeDOCore_ru/image11.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image11.png)

Тут все просто – VDLP пал жертвой опять же невнимательности при модификации связанной с добавлением поддержки игр формата PAL\. Раньше эмулятор поддерживал только NTSC игры, которых подавляющее большинство, и буфер кадра имел фиксированный размер 320 на 240 пикселей, поэтому был объявлен внутри класса в виде массива, без выделений памяти\.

```cpp

screen=new uint8[384*288*3*4];
memset(screen,0,sizeof(screen));
```

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

```cpp
memset(screen,0,sizeof(uint8)*384*288*3*4);
```



**Ошибка** **–** **а** **диска\-то** **нет\!** 

**V595: The 'adapt' pointer was utilized before it was verified against nullptr\. Check lines: 375, 376\.**

**Line 375 \- dumplibrary\.cpp**

![0247_FreeDOCore_ru/image12.png](https://import.viva64.com/docx/blog/0247_FreeDOCore_ru/image12.png)

И снова невнимательность\.\.\. До обращения к объекту надо бы проверить его корректность, поэтому последние две строчки надо поменять местами\. Иначе при отсутствии нужных образов получим исключение в процессе загрузки сохраненных игр\.

```cpp

dumpAdapter *adapt=createDumpAdapter(j,
  inf->Parent()->Attribute("attach").toString());
adapt->SetSign(signs[names[i]]);
if(!adapt)break;
```

Что тут сказать? Мне надо быть внимательней или просто отдыхать по вечерам вместо программирования эмуляторов :\-\)\.

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

 Итак, мой первый опыт показал, что статический анализ кода, вещь несомненно полезная и способная сэкономить немало времени и нервов разработчиков\. Но для эму\-сцены цена вопроса конечно высока, как и в случае с декомпилятором Hex\-Ray для ARM, который мог бы продвинуть эмуляцию 3DO на много шагов вперед\.