Переключение на Главную Страницу Страницы: [1] 2  ОтправитьПечать
Горячая тема (более 10 ответов) Очередной глобальный баг 1С, мешающий использованию ООП (и не только) (число прочтений - 5171 )
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
25. Апреля 2017 :: 08:27
Печать  
Обнаружена крайне неприятная особенность механизма возврата значения из функций, которая в сочетании с другой глобальной особенностью приводит к непредсказуемому поведению программы, использующей функции, которые возвращают объекты.

Начнём по порядку.
Значения из функций возвращаются следующим образом: в CExecutedModule есть значение типа CValue, в которое кладётся возвращаемое оператором "Возврат" значение (см. переменную m_FuncRetVal ниже в структурах). Если говорить о внешнем вызове функций из модулей методом CBLModule7::CallAsFunc, то после отработки кода функции этот метод извлекает значение из CexecutedModule::m_FuncRetVal, помещает его по месту назначения параметра CValue &retValue, после чего пытается почистить m_FuncRetVal.

Именно ПЫТАЕТСЯ, потому как получается не очень успешно. Выполняет он это по всем признакам с помощью метода CValue::Reset. И вот тут мы подходим ко второй глобальной особенности, которая мне уже и раньше немало крови попортила. Там (в CValue::Reset и в соседних типа SetTypeByCode) какие-то очень умные*анские программисты решили не вызывать UnlinkContext(). Ну а зачем, в самом деле, пусть остаётся! В итоге, если в переменной был объект (CBLContext в смысле), то после такой "чистки" снаружи она будет выглядеть как пустая, однако ссылка на CBLContext останется со всеми примочками (CBLContext::m_RefCount, CBLContext::m_Array со ссылкой на CexecutedModule::m_FuncRetVal).

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

В итоге при возврате из функций объектов ссылка на эти объекты зависает в глубинах модуля, в результате чего любая более-менее нетривиальная программа с использованием ООП (в особенности, конечно, деструкторов) начинает вести себя совершенно непредсказуемо. Объекты удаляются не в том порядке, в котором ожидалось, причём в совершенно случайном месте программы, которое к ним вообще не имеет никакого отношения. А при определённом стечении обстоятельств программа способная вообще полностью выпасть в осадок.
  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #1 - 25. Апреля 2017 :: 08:28
Печать  
Для себя решение я, конечно, придумал. Опишу в двух словах.
1. Метод типа CValue::Reset, только с вызовом UnlinkContext(). Использую для очистки значений его везде.
2. Вызов CBLModule7::CallAsFunc выполняю примерно так:
Код
Выбрать все
 	int ExCallAsFunc(int Index,CValue &Value,PValue *Params,int ParamCount)
	{
		int Result;
		CValue &FuncRetVal=pIntInfo->pExecutedModule->m_FuncRetVal;
		if (&Value==&FuncRetVal)
		{
			CValue Temp; // Только через временную переменную, потому что при выходе из CallAsFunc почистится m_FuncRetVal и в Value будет пустота
			Result=CBLModule7::CallAsFunc(Index,Temp,ParamCount,Params);
			Value=Temp;
		}
		else
		{
			Result=CBLModule7::CallAsFunc(Index,Value,ParamCount,Params);
			FuncRetVal.UnlinkContext(); // В CValue::Reset() нет UnlinkContext(), поэтому при возврате объекта ссылка на него зависнет в m_FuncRetVal
		}
		return Result;
	} 


Тут добавлена обработка ещё одного варианта, который возникает при вызове собственных методов модуля, но не напрямую, а через контекст. В итоге вложенные вызовы CBLModule7::CallAsFunc пишут возвращаемое значение в одну и ту же переменную, из-за чего образуется бардак.

Как глобально разобраться с проблемой UnlinkContext() – хз. Только трапов наставить на соответствующие методы CValue. Но они могут и инлайниться, так что без толку. А ведь эта проблема будет возникать везде, и корректная работа будет гарантирована только при использовании собственных модулей (и то только определенным образом).

В общем, это всё, что пока хотел сказать.
  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #2 - 25. Апреля 2017 :: 08:31
Печать  
Выжимка из незаменимого IDA-файлика со структурами.
См. переменную m_FuncRetVal в CExecutedModule.
Код
Выбрать все
class CExecutedModuleObject
{
	CPtrArray Arr1;
	CPtrArray Arr2;
	CPtrArray Arr3;
};

struct CExecuteStack
{
	DWORD dwData[18];
};

class CExecutedModule
{
	CBLModule *m_pModule;
	CCompiledModule *m_pCompiledModule;
	void *m_pObject1;
	CExecuteStack *m_pStack; // массив - внутренний стек
	DWORD val_5;
	int m_StackSize; // количество переменных в стеке
	CValue *pValArr1;
	DWORD val_8;
	int m_ValCount1;
	DWORD val_10;
	DWORD m_OperPos; // Формируется в процессе выполнения EvalExpr
	DWORD m_OperCode;
	CDWordArray m_DWordArray1;
	CDWordArray m_DWordArray2;
	DWORD val_23;
	DWORD val_24;
	DWORD val_25;
	DWORD val_26;
	DWORD val_27;
	DWORD val_28;
	CValue m_FuncRetVal;
	DWORD m_dwLoadedModuleID;
	DWORD val_29;
	DWORD val_30;
	CBLContext *m_pSomeContext;
	CValue m_Value2;
	CString m_Str0;
	CString m_Str1;
	CString m_Str2;
	DWORD val_31;
	DWORD val_32;
	DWORD val_33;
	CValue *m_pValArr2;
	int m_ValCount2;
	DWORD val_34;
	CExecutedModuleObject Object;
	void *m_pObject2;
	DWORD val_35;
	DWORD val_36;
	CValue **m_ppEvalValArr____________________________________________; // Параметр ValArr метода EvalExpr
	DWORD val_37;
	DWORD val_38;
}; 


  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #3 - 29. Апреля 2017 :: 20:45
Печать  
Дефект не подтверждается при родном вызове функций внутри кода (при вызове их из языка 1С). Видимо там всё делается относительно корректно. Недоделка существует именно в методе CBLModule7::CallAsFunc. Именно там не очищается контекст в переменной, в которой хранится возвращаемое функцией значение, и проблема возникает только при использовании вызовов CallAsFunc из собственных модулей (в таких объектах 1С++, например, как ИсполняемыйМодуль, в ООП, в CModuleEventManager видел ещё, ну и т.д.). Приведённое решение с добавлением UnlinkContext после CallAsFunc в этом случае работает, "глобального" решения проблемы вроде как не требуется.
  
Наверх
 
IP записан
 
trad
1c++ power user
1c++ donor
1c++ moderator
Отсутствует



Сообщений: 3047
Местоположение: Киров
Зарегистрирован: 23. Мая 2006
Пол: Мужской
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #4 - 01. Мая 2017 :: 18:23
Печать  
если интересно, гуглинг дает такие ссылки по теме:
http://www.1cpp.ru/forum/YaBB.pl?num=1212645215
http://www.1cpp.ru/forum/YaBB.pl?num=1207661901
  

1&&2&&3
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #5 - 02. Мая 2017 :: 19:59
Печать  
Да, тема там именно эта обсуждается (я подозревал, что не первый на такое нарвался, но не представлял как такое гуглить). Однако к результату что-то это ни к какому не привело. Никакого решения в 1С++ я не увидел (а туда я первым делом слазил). То есть там этот баг должен присутствовать по определению. Я не пытался его воспроизвести, ибо соотв. объектами 1С++ не пользуюсь, но теоретически его не может не быть. Поэтому и решил, что будет не лишним обратить на это внимание.
  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #6 - 02. Мая 2017 :: 20:50
Печать  
Кстати, насчёт поста http://www.1cpp.ru/forum/YaBB.pl?num=1212645215/2#2
Тоже сталкивался с таким. При присвоении пустого значения (CValue::type == 0) вызывается CValue::Reset, в котором, как указано выше, нет UnlinkContext и ссылка на объект (если он был в переменной, которой происходит присвоение) останется. При присвоении любого другого значения ссылка на контекст успешно потрётся. Это не баг реализации ПолучитьПустоеЗначение(), а недоделка CValue::Reset, которая и приводит ко всему букету багов.
Веселился с этим глюком, когда пытался почистить статические переменные модуля методом SetStaticVarValue. Прямого доступа к переменной получить никак нельзя, а присвоение пустого значения ссылки на объекты не удаляло. Приходилось пустую строку присваивать. Так вот.
  
Наверх
 
IP записан
 
vladimirmir2012
Senior Member
****
Отсутствует


1C++ rocks!

Сообщений: 426
Зарегистрирован: 18. Мая 2011
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #7 - 03. Мая 2017 :: 13:41
Печать  
.
« Последняя редакция: 13. Сентября 2018 :: 06:27 - vladimirmir2012 »  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #8 - 03. Мая 2017 :: 20:15
Печать  
vladimirmir2012 писал(а) 03. Мая 2017 :: 13:41:
В 1С 7.7 присвоение пустой строки какому-либо объекту инициирует вызов деструктора объекта /для C++ class например производится вызов его destructor/

Речь не о языке 1С, а о внутреннем устройстве класса CValue. Безусловно присвоение переменной, ссылающейся на объект, некоторого (любого) значения должно приводить - только не к вызову деструктора, а к уменьшению счетчика ссылок на объект (а там уж если больше на него никто не ссылается, то и к вызову деструктора). И вот это не верно в частном случае присвоения значения с CValue::type==0 (в терминах языка 1С ПолучитьПустоеЗначение() без параметров). В этом случае вызывается метод CValue::Reset, в котором CValue::type становится равным 0, но очистка ссылки на объект не происходит. И ссылка висит там пока не будет присвоено какое-либо иное значение с CValue::type<>0, либо переменная не выйдет из области видимости текущего кода, в случае чего будет вызван деструктор ~CValue, который ссылку таки почистит. И вот отсутствие очистки ссылки на объект в методе CValue::Reset в операторе присваивания (см. ниже) и приводит ко всем описанным багам.

Дойдут руки - навешаю-таки трап на CValue::Reset CValue::оperator=. Уверен (надеюсь) в местах, о которых идёт речь он не заинлайнен (проверить пока времени не было, боем проверить проще) Точно не заинлайнен. Заменю его на свой метод с UnlinkContext и это разом решит все проблемы. Все затычки можно будет выкинуть.
« Последняя редакция: 04. Мая 2017 :: 18:56 - dfuy »  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #9 - 04. Мая 2017 :: 13:24
Печать  
Вру, конечно (давно дело было, подзабыл немного). При присвоении значения с CValue::type==0 не делается CValue::Reset. Вообще ничего не делается кроме установки типа. Везде UnlinkContext() есть кроме default. Вот так выглядит оператор присваивания значения в 1С:
Код
Выбрать все
.............
    else                                        // int CValue::IsTypeSafe()
    {
      if ( pSource )
        psrcType = &pSource->Type;
      else
        psrcType = 0;
      srcType = psrcType->m_TypeCode;
      this->Type.m_TypeCode = srcType;
      this->Type.m_TypeID = psrcType->m_TypeID;
      this->Type.m_Length = 0;
      this->Type.m_Precision = 0;
      this->Type.m_Flags = 0;
      switch ( srcType )
      {
        case 1u:
          CNumeric::operator=(&this->Number, &pSource->Number);
          CValue::UnlinkContext(this);
          break;
        case 2u:
          CString::operator=(&this->String, &pSource->String);
          CValue::UnlinkContext(this);
          break;
        case 3u:
          this->Date.m_Date = pSource->Date.m_Date;
          CValue::UnlinkContext(this);
          break;
        case 10u:
        case 11u:
        case 12u:
        case 13u:
        case 14u:
        case 15u:
        case 16u:
        case 17u:
        case 100u:
          this->ObjID.m_ObjID = pSource->ObjID.m_ObjID;
          this->ObjID.DBSign.m_Sign = pSource->ObjID.DBSign.m_Sign;
          m_Context = this->m_Context;
          this->m_ValTypeID = pSource->m_ValTypeID;
          if ( m_Context != pSource->m_Context )
          {
            if ( m_Context )
            {
              CBLContext::RemoveFromValues(m_Context, this);
              this->m_Context->vtable->DecrRef(this->m_Context);
            }
            srcContext = pSource->m_Context;
            this->m_Context = srcContext;
            if ( srcContext )
            {
              srcContext->vtable->IncrRef(srcContext);
              CBLContext::AddToValues(this->m_Context, this);
            }
            else if ( this->Type.m_TypeCode == 100 )
            {
              Empty.m_TypeCode = 0;
              Empty.m_Length = 0;
              Empty.m_Precision = 0;
              Empty.m_Flags = 0;
              Empty.m_TypeID = 0;
              v22 = 0;
              CValue::SetType(this, &Empty);
            }
          }
          break;
        default:
          break;
      }
    } 


В CValue::Reset UnlinkContext() как раз есть:
Код
Выбрать все
void __thiscall CValue::Reset(CValue *this)
{
  CString *m_String; // edi@1
  char *pSign; // ecx@1
  int m_length; // esi@4

  m_String = &this->String;
  CString::operator=(&this->String, ZeroString);
  CNumeric::operator=(&this->Number, 0);
  pSign = (char *)&this->ObjID.DBSign;
  this->Date.m_Date = 0;
  this->ObjID.m_ObjID = 0;
  *(_WORD *)pSign = '  ';
  pSign[2] = ' ';
  this->m_ValTypeID = 0;
  CValue::UnlinkContext(this);
  if ( this->vtable->IsTypeSafe(this) )
  {
    if ( this->Type.m_TypeCode == 2 )
    {
      m_length = this->Type.m_Length;
      if ( m_length > 0 )
        Pad(m_String, m_length, 0);
    }
  }
  else
  {
    this->Type.m_TypeCode = 0;
    this->Type.m_TypeID = 0;
    this->Type.m_Length = 0;
    this->Type.m_Precision = 0;
    this->Type.m_Flags = 0;
  }
} 


Видимо очистка m_FuncRetVal в CallAsFunc делается присвоением пустого значения. Щас покопаю...
  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #10 - 04. Мая 2017 :: 13:34
Печать  
В самом языке операцию очистки значения m_FuncRetVal уже видел, она делается через CValue::SetType(). В SetType UnlinkContext есть:
Код
Выбрать все
void __thiscall CValue::SetType(CValue *this, const CType *aType)
{
  CType *pType; // eax@3

  CValue::UnlinkContext(this);
  if ( this->vtable->IsTypeSafe(this) )
  {
    if ( this )
      pType = &this->Type;
    else
      pType = 0;
    *pType = *aType;
    this->m_ValTypeID = 0;
  }
  else
  {
    this->Type.m_TypeCode = aType->m_TypeCode;
    this->Type.m_TypeID = aType->m_TypeID;
    this->Type.m_Length = 0;
    this->Type.m_Precision = 0;
    this->Type.m_Flags = 0;
    this->m_ValTypeID = 0;
  }
} 


Операция очистки выглядит примерно так:
Код
Выбрать все
void __thiscall CExecutedModule::OperClearFuncRetVal(CExecutedModule *this)
{
  CExecutedModule *This; // esi@1
  CType Empty; // [sp+4h] [bp-18h]@1
  int v3; // [sp+18h] [bp-4h]@1

  This = this;
  Empty.m_TypeCode = 0;
  Empty.m_Length = 0;
  Empty.m_Precision = 0;
  Empty.m_Flags = 0;
  Empty.m_TypeID = 0;
  v3 = 0;
  CValue::SetType(&this->m_FuncRetVal, &Empty);
  ++This->m_OperPos;
} 

  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #11 - 04. Мая 2017 :: 14:24
Печать  
Накопал, блин. Последнее предположение подтверждается. Нашёл функцию, которую вызывает CallAsFunc. Там забавный код примерно такого типа:
Код
Выбрать все
int __thiscall CExecutedModule::CallAsFunc(CExecutedModule *this, int nMethod,
  int ParamCount, CValue **pParams, CValue *pRetVal)
{
  int result; 
  CCompileProcInfo *MethInfo;
  CValue PrevRetValue;
..............
  PrevRetValue=this->m_FuncRetVal; // Тут, как правило, лежит то самое пустое значение
..............
  MethInfo = this->m_pCompiledModule->ProcArray[nMethod];
  if ( pRetVal && MethInfo->Type == 1 ) // Type == 1 - это процедура
  {
    result = 0;
  }
  else
  {
..............
тута многа кода, вызывающего функцию
..............
    if ( MethInfo->Type == 2 && Result ) // Type == 2 - это фукция
    {
      if ( pRetVal )
        *pRetVal=this->m_FuncRetVal;
    }
    this->m_FuncRetVal=PrevRetValue; // Вот и оператор присваивания, который нифига не чистит контекст
..............
  }
  return result;
} 


Таким образом трап надо ставить не на CValue::Reset(), а на CValue::оperator=(), и там если присваиваем пустое значение, вызывать Reset, а иначе родной оператор. Попробую попозже.
« Последняя редакция: 04. Мая 2017 :: 18:46 - dfuy »  
Наверх
 
IP записан
 
vladimirmir2012
Senior Member
****
Отсутствует


1C++ rocks!

Сообщений: 426
Зарегистрирован: 18. Мая 2011
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #12 - 04. Мая 2017 :: 16:27
Печать  
.
« Последняя редакция: 13. Сентября 2018 :: 06:27 - vladimirmir2012 »  
Наверх
 
IP записан
 
dfuy
Junior Member
**
Отсутствует



Сообщений: 41
Зарегистрирован: 18. Марта 2013
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #13 - 04. Мая 2017 :: 18:40
Печать  
Да, IDA. Там HexRays Decompiler - неплохая вещь. По работе с классами там, конечно, не всё удобно. Он фактически C Decompiler, а не C++. Особенно хлопотно с виртуальными методами разбираться, но есть хитрые трюки как с его помощью и через них пробиться можно. Я про это писал тут: http://www.1cpp.ru/forum/YaBB.pl?num=1448167496
Там и ссылки есть на IDA (хотя не факт, что живые), и файлик заголовочный приложен с классами 1С, в том числе для некоторых таблицы виртуальных методов сделаны (хотя уже есть новые версии его). Если надо будет, залью.
  
Наверх
 
IP записан
 
vladimirmir2012
Senior Member
****
Отсутствует


1C++ rocks!

Сообщений: 426
Зарегистрирован: 18. Мая 2011
Re: Очередной глобальный баг 1С, мешающий использованию ООП (и не только)
Ответ #14 - 05. Мая 2017 :: 03:42
Печать  
.
« Последняя редакция: 12. Сентября 2018 :: 11:19 - vladimirmir2012 »  
Наверх
 
IP записан
 
Переключение на Главную Страницу Страницы: [1] 2 
ОтправитьПечать