C/C++インタプリタ CINTのデストラクタ不正呼び出し問題と格闘した話 その4

## 以前の記事

[その1](https://qiita.com/NCScript/items/b4f0d244c8392762b802)
[その2](https://qiita.com/NCScript/items/ff68760381995fe76947)
[その3](https://qiita.com/NCScript/items/66463bdad967e7d25bff)

## 修正対象

修正が必要になる関数は以下の表のとおりです。

| ファイル | 関数 |
|:--|:--|
| decl.c | G__define\_var |
| ifunc.c | G__interpret\_func |
| pcode.c | G__LD_p0\_struct |
| pcode.c | G__get_LD_p0\_p2f |
| pcode.c | G__get_LD_p1\_p2f |
| pcode.c | G__get_LD_pn\_p2f |
| pcode.c | G__get_ST_p0\_p2f |
| pcode.c | G__get_ST_p1\_p2f |
| pcode.c | G__get_ST_pn\_p2f |
| var.c | G__class_2nd\_decl |
| var.c | G__letvariable |
| var.c | G__getvariable |
| bc_exec\_asm.h | G__exec\_asm |

## decl.c

### `G__define_var`関数

ユーザー定義型の処理を行っているスコープの先頭部分に、初期化済みを表すフラグ変数を追加します。

```C:decl.c
      /**************************************************************
      * declaration of struct object, no pointer, no reference type
      **************************************************************/
      if(var_type=='u'&&
#ifndef G__OLDIMPLEMENTATION871
         (G__def_struct_member==0||-1==G__def_tagnum||
          'n'==G__struct.type[G__def_tagnum])
#else
         G__def_struct_member==0
#endif
         &&new_name[0]!='*'&&
         G__reftype==G__PARANORMAL) {

#ifndef G__NCPRO_OLDIMPLEMENTATION39
        struct G__var_array *var;
        char name[G__MAXNAME];
        char *p;
        int hash,ig15;
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        int initflag=0;
#endif

        strcpy(name,new_name);
        p=strchr(name,'[');
        if(p) *p='\0';
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        G__hash(name,hash,i)
        var = G__getvarentry(name,hash,&ig15,&G__global,G__p_local);
        if(var && '@'!=var->type[ig15]) initflag=1;
#endif
#endif
```

以下は`type`を元に戻している部分です。コンパイル済みライブラリに含まれている型だった場合は、ここで処理を行います。インタプリタで処理される型だった場合は、`G__letvariable`関数の中で処理が行われます。

```C:decl.c
        /************************************************************
        * memory allocation and table entry generation
        ************************************************************/
        store_struct_offset = G__store_struct_offset;
        if(G__CPPLINK!=G__struct.iscpplink[tagnum]) {
          /* allocate memory area for constructed object by interpreter */
          G__var_type = var_type;
#ifndef G__OLDIMPLEMENTATION1349
          G__decl_obj=1;
#endif
          G__store_struct_offset=G__int(G__letvariable(new_name,reg,&G__global
                                                       ,G__p_local));
#ifndef G__OLDIMPLEMENTATION1349
          G__decl_obj=0;
#endif
#ifndef G__OLDIMPLEMENTATION1073
          if(0==G__store_struct_offset &&
             G__asm_wholefunction && G__asm_noverflow) {
            G__store_struct_offset = G__PVOID;
          }
#endif
        }
        else {
          /* precompiled class, 
           * memory will be allocated by new in constructor function below */
          G__store_struct_offset = G__PVOID;
#ifndef G__NCPRO_OLDIMPLEMENTATION40
          if(var && '@'==var->type[ig15]) var->type[ig15] = 'u';
#endif
        }
```

バイトコードへのコンパイル時に、実行されなかったブロック内で定義された変数の`type`を差し替える処理を行います。ユーザー定義型の処理を行っているスコープの最後の部分に追加します。

```C:decl.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        if(!initflag && G__asm_loopcompile && G__no_exec_compile) {
          G__hash(name,hash,i)
          var = G__getvarentry(name,hash,&ig15,&G__global,G__p_local);
          if(var) var->type[ig15] = '@';
        }
#endif
      } /* of if(var_type=='u'&&G__def_struct_member.... */
```

## ifunc.c

### `G__interpret_func`関数

バイトコードインタプリタでの実行中に未初期化の変数に対してデストラクタが呼び出された場合は、thisポインタの値を一時的に0に変更することで呼び出しをスキップしています。

```C:ifunc.c
#ifdef G__ASM_IFUNC
  if(G__asm_exec) {
    ifn = G__asm_index;
#ifndef G__OLDIMPLEMENTATION864
#ifndef G__NCPRO_OLDIMPLEMENTATION40
    store_struct_offset = G__store_struct_offset;
    if('@'==result7->type) G__store_struct_offset = 0;
#endif
    /* delete 0 ~destructor ignored */
    if(0==G__store_struct_offset && -1!=p_ifunc->tagnum && 
       0==p_ifunc->staticalloc[ifn] && '~'==p_ifunc->funcname[ifn][0]) {
#ifndef G__NCPRO_OLDIMPLEMENTATION40
      G__store_struct_offset = store_struct_offset;
#endif
      return(1);
    }
#ifndef G__NCPRO_OLDIMPLEMENTATION40
    G__store_struct_offset = store_struct_offset;
#endif
#endif
    goto asm_ifunc_start;
  }
#endif
```

## pcode.c

### `G__LD_p0_struct`関数

バイトコードインタプリタでの実行中に未初期化の変数に対して再宣言が行われる場合は、変数管理テーブル内の`type`を元に戻した後で、スタックに積まれている`G__value`型のデータの`type`を差し替えます。

スタック上の`G__value`型のデータは、`G__interpret_func`関数に引数`result7`として渡されます。

```C:pcode.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  if('@'==var->type[ig15]) {
    var->type[ig15] = 'u';
    buf->type = '@';
  }
#endif
```

### `G__get_LD_p?_p2f`関数と`G__get_ST_p?_p2f`関数

これらの関数によって、バイトコードのロード命令とストア命令の処理で呼び出される関数が決定されます。

以下は`G__get_LD_p0_p2f`関数の修正内容で、未初期化の変数に対して`G__LD_p0_struct`関数を呼び出すように設定しています。他の5つの関数も修正のパターンは同じです。

```C:pcode.c
  if(isupper(type)) {
    if('Z'==type) done=0;
#ifndef G__OLDIMMPLEMENTATION1341
    else if('P'==type || 'O'==type) *pinst = (long)G__LD_p0_double; 
#endif
    else *pinst = (long)G__LD_p0_pointer;
  }
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  else if('@'==type) {
    *pinst = (long)G__LD_p0_struct;
  }
#endif
  else {
```

## var.c

### `G__class_2nd_decl`関数

バイトコードの出力モードを保存するための変数の宣言を、`G__class_2nd_decl`関数の先頭部分に追加します。

```C:var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  int store_no_exec_compile;
#endif
```

バイトコードの出力中に未初期化の変数に対して再宣言が行われる場合は、デストラクタの呼び出し前に`type`を元に戻した上で、ブロック内を実行せずにバイトコードの出力を行うモードに変更します。

```C:var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  store_no_exec_compile=G__no_exec_compile;
  if('@'==var->type[ig15]) {
    var->type[ig15]='u';
    G__no_exec_compile=1;
  }
#endif
```

処理後に、バイトコードの出力モードを元に戻します。

```C:var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  G__no_exec_compile=store_no_exec_compile;
#endif
```

### `G__letvariable`関数

初期化済みを表すフラグ変数の宣言を、`G__letvariable`関数の先頭部分に追加します。

```C:var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  int initflag=1;
#endif
```

変数が未初期化だった場合は、`type`を元に戻して初期化済みフラグを解除します。

```C:var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
    if('@'==var->type[ig15]) {
      var->type[ig15]='u';
      initflag=0;
    }
#endif
```

`G__class_2nd_decl`関数の呼び出しによる変数の再宣言が行われなかった場合は、`G__letvariable`関数を終了する前に再び`type`を差し替えます。

```C:var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  if(!initflag) var->type[ig15]='@';
#endif
  return(result);
```

### `G__getvariable`関数

初期化済みを表すフラグ変数の宣言を、`G__getvariable`関数の先頭部分に追加します。

```C:var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  int initflag=1;
#endif
```

バイトコードインタプリタでの実行中に未初期化の変数に対して再宣言が行われる場合は、変数管理テーブル内の`type`を元に戻します。

```C:var.c
#ifdef G__ASM
  /************************************************************
   * If G__asm_exec is set by 'for(', 'while(' or 'do{}while('
   * loop in G__exec_statements(), execute this part and skip
   * parsing string.
   ************************************************************/
  if(G__asm_exec) {
    ig15=G__asm_index;
    paran = G__asm_param->paran;
    
    for(ig35=0;ig35<paran;ig35++) {
      para[ig35] = G__asm_param->para[ig35];
    }
    
    para[paran]=G__null;
    var=varglobal;
    if(varlocal==NULL)
      G__struct_offset =0;
    else
      G__struct_offset=G__store_struct_offset;
#ifndef G__NCPRO_OLDIMPLEMENTATION40
    if('@'==var->type[ig15]) {
      var->type[ig15]='u';
      initflag=0;
    }
#endif
    goto G__exec_asm_getvar;
  }
#endif
```

各種ロード命令を処理するswitch文の中で、`G__getvariable`関数の戻り値として使用される`G__value`型の変数`result`の`type`を差し替えます。

戻り値はスタックに積まれた後で、`G__interpret_func`関数に引数`result7`として渡されます。

```C:var.c
      case 'u': /* struct, union */
        G__GET_STRUCTVAR;
        if(ig25<paran) {
          G__tryindexopr(&result,para,paran,ig25);
        }
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        if(!initflag) result.type='@';
#endif
        break;
```

実行されなかったブロックのバイトコードへのコンパイル時に呼び出された場合は、`type`を元に戻して初期化済みフラグを解除します。

```C:var.c
#ifndef G__OLDIMPLEMENTATION776
      if(G__no_exec_compile && 
         (G__CONSTVAR!=var->constvar[ig15] || isupper(var->type[ig15]) ||
          G__PARAREFERENCE==var->reftype[ig15] || 
          var->varlabel[ig15][1] || /* 2011 ??? */
          var->p[ig15]<0x1000) &&
         'p'!=tolower(var->type[ig15])) {
#else
      if(G__no_exec_compile) {
#endif
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        if('@'==var->type[ig15]) {
          var->type[ig15]='u';
          initflag=0;
        }
#endif
```

その後、`G__getvariable`関数を終了する前に再び`type`を差し替えます。

```C:var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        if(!initflag) var->type[ig15]='@';
#endif
        return(result);
      }
```

## bc_exec_asm.h

### `G__exec_asm`関数

バイトコードインタプリタの本体です。エラー発生時に変数管理テーブル内の`type`を差し替える処理は、この関数に追加する必要があります。

変数管理テーブルへのポインタとインデックスを保存するための変数の宣言を、`G__exec_asm`関数の先頭部分に追加します。

```C:bc_exec_asm.h
#ifndef G__NCPRO_OLDIMPLEMENTATION41
  struct G__var_array *store_var=NULL,*store_varB=NULL;
  int store_index=0,store_indexB=0;
  int store_tagnumB=0;
#endif
```

`G__LDST_VAR_P`命令と`G__LD_VAR`命令の実行時に、命令バッファから変数管理テーブルへのポインタとインデックスを取り出して保存しておきます。

```C:bc_exec_asm.h
#ifndef G__NCPRO_OLDIMPLEMENTATION41
      store_var=(struct G__var_array*)G__asm_inst[pc+4];
      store_index=G__asm_inst[pc+1];
#endif
```

`G__LD_IFUNC`命令の実行時に、デストラクタの呼び出しが完了したことによって変数が未初期化状態に戻った場合は、変数管理テーブルへのポインタとインデックスを差し替え処理用の変数にコピーしておきます。

また、コンストラクタの呼び出しが完了したことによって差し替え処理が不要になった場合は、ポインタをNULLに初期化しておきます。

```C:bc_exec_asm.h
#ifndef G__NCPRO_OLDIMPLEMENTATION41
      if(store_var && '~'==funcnamebuf[0]) {
        store_varB=store_var;
        store_indexB=store_index;
        store_tagnumB=store_var->p_tagtable[store_index];
        store_var=NULL;
      }
      else if(store_varB && 0==strcmp(funcnamebuf,G__struct.name[store_tagnumB])) {
        store_varB=NULL;
      }
#endif
```

最後に、`G__LD_FUNC`命令と`G__LD_IFUNC`命令のエラー処理を行うif文の中に、`type`の差し替え処理を追加します。

```C:bc_exec_asm.h
      if(G__return!=G__RETURN_NON) {
#ifndef G__NCPRO_OLDIMPLEMENTATION41
        if(store_varB) {
          store_varB->type[store_indexB] = '@';
          store_varB->p_tagtable[store_indexB] = -1;
          store_varB->p_typetable[store_indexB] = -1;
        }
#endif
        G__asm_exec=0;
        return(1);
      }
```

以上で、バイトコードインタプリタでの実行中に変数の再宣言が行われた場合に発生していた、未初期化の変数に対してデストラクタが不正に呼び出されてしまう問題の修正は終わりです。

私がCINTのソースコードに行った他の修正の中で規模の大きい物については、今後もQiitaに解説記事を投稿する予定です。比較的簡単な修正については、[Twitter](https://twitter.com/NCScript)で解説したいと思います。