日本ソフトウェア科学会第 38 回大会 (2021 年度) 講演論文集

静的型付き言語で動的に生成された型が含むバグの早期発見に向けた研究
横井 駿平

千葉 滋

C#はリフレクション操作によってコンパイル時に存在しない型を実行時に生成することが可能な言語であるが，この機能を用いたプログラムはバグを含みやすく，また発生したエラーの情報からバグの原因を特定することが通常のプ
ログラムの場合よりも困難であるため，実行時の型生成を伴うプログラムの記述は敬遠されてきた．本研究では動的な型生成を行うリフレクション計算に起因する実行時エラーについて，生成される型に型構築時の実行時フローの情報を attribute として埋め込むこと，および埋め込まれた情報をエラー発生時に参照することでエラーの原因と考えられる部分をエラーメッセージ内で示唆し，この種のバグの早期発見，早期解決を支援するための仕組みを提案する．

ASP.NET では，リフレクション操作によって動的に1 はじめにメソッド選択を行ったのち，動的コード生成によってC#はリフレクション操作によって実行時に型を生選択されたメソッドを実行するためのコードをその場成することが可能な言語であるが，そのような操作はで生成することによって動的なメソッド実行と高速なバグの原因となりやすく，また型生成操作を含むプロ動作の両立を実現している．

グラムのバグは通常のプログラムの場合と比較してこのように動的な型生成操作は高機能なライブラ

エラー発生時に出力されるエラーメッセージからエリやフレームワークを作る上で重要になるが，型生成ラーの原因を推測することが難しい．

操作に起因する実行時エラーメッセージの改善に関す

実行時に型を生成する操作は通常のプログラムのる議論はこれまで行われてこなかった．本研究では，中ではあまり用いられないが，処理の高速化や高機能型生成時の実行フロー情報を生成された型に埋め込なライブラリの実現のため，主にライブラリ開発者にむことにより，動的に生成された型およびそれを利用よって用いられている．この技術を用いているもののするコードに含まれるバグを早期発見するための手例として C#による Web アプリケーション開発用フ法を提案する．以下ではまず動的型生成操作そのものレームワークである ASP.NET が挙げられる，Webおよびそれに起因するエラーとエラーメッセージにつアプリケーションはその性質上，アプリケーション実いて説明し (2 章)、続いてエラーメッセージを改善す

行時に実行すべきメソッドを動的に決定しなければるための手法の提案およびその実装方法について説ならない場面が存在する．リフレクション操作を行う

明を行う (3 章)．そして本研究と関連する研究を紹介ことでこの処理は実現可能であるが，通常のプログ

し (4 章)，最後に今後の課題を述べる (5 章)．

ラムの実行速度に対してリフレクション操作は極めて低速であるため，実用アプリケーション内でリフレ2 動的型生成クション操作を多用することは推奨されない．そこで動的な型生成処理とは，実行時のリフレクション計算によってコンパイル時には存在しない型を実行時にShumpei Yokoi, Shigeru Chiba, 東京大学大学院 情報理工学系研究科, Graduate School of Information Science and Technology, The University of Tokyo.

生成する手法である．以下のプログラム 1 は，int 型のフィールド x および y を持つクラス Vector2 を実

行時に生成する C#プログラムの疑似コードである．

このプログラムは 1 とほぼ同じものであるが，3 行目の DeﬁneField メソッドで指定したフィールドの名前

プログラム 1
12345

動的な型生成の例

var typeBuilder = modBuilder.DefineType("Vector2",TypeAttributes.Class,typeof(object));
var xBuilder = typeBuilder.DefineField("x", typeof(int),FieldAttributes.
Public);var yBuilder = typeBuilder.DefineField("y", typeof(int),FieldAttributes.
Public);var vec2 = typeBuilder.CreateType()!;var v = Activator.CreateInstance(t)!;が異なっている．このプログラムでは 2 行目と 3 行目で異なる同名のフィールド x の追加を行っている．その結果，生成した型 Vector2 へのアクセスを行う際，System.Reﬂection.AmbiguousMatchException 例外が発生する．この例外によるエラーメッセージはアクセスしようとしたフィールドが曖昧であったことしか示唆しないため，このエラーが同名のフィールドを 2回定義したことにより発生したものであることを特定するのは難しい．
我々は，動的型生成を原因とするエラーが発生した
際，なるべく早く原因を特定しバグを取り除くためのプログラム 1 の 5 行目に登場する変数 v は，ソース仕組みが必要であると考えた．原因の特定を容易にコード内に現れないクラスである Vector2 型のインするためには，エラーの発生時にプログラマが参照スタンスである．

可能な情報を増やすことが重要である．なぜならば，これらの動的な型生成処理の内容はコンパイル時十分な情報が存在すれば問題の発生原因を絞り込むにチェックされないため，動的型生成に起因する問題ことが可能となるためである．本研究では，動的に型(必要なメソッドの実装忘れ等) は実行時エラーとしを生成するリフレクション計算を対象とし，問題発生て検出される．ところが，実行時エラーメッセージは時に出力されるエラーメッセージの改善によるバグのエラーの内容そのものやエラーの直接の原因を示すも原因の早期発見を目指す．のでしかないため，問題解決のために修正すべき部分動的に型を生成するリフレクション計算に起因するを特定するための情報としては不十分な場合も多い．

エラーには大きくわけて「型を生成するコード内の例として以下のプログラム 2 を挙げる．これは同名エラー」と「生成されたコード内のエラー」，そしてのフィールドを同じ型に 2 つ定義してしまうプログ「生成された型を利用するコード内のエラー」が存在

ラムの疑似コードである．

する．生成されたコード内，つまりコンパイル時には直接コードに現れない部分の実行によるエラーの存

プログラム 2
12

3

4
567

動的な型生成の例

var typeBuilder = modBuilder.DefineType("Vector2",TypeAttributes.Class,typeof(object));
var xBuilder = typeBuilder.DefineField("x", typeof(int),FieldAttributes.
Public);var yBuilder = typeBuilder.DefineField("x", typeof(int),FieldAttributes.
Public);var vec2 = typeBuilder.CreateType()!;var v = Activator.CreateInstance(t)!;// 以下の行で例外発生
v.x = 100;

在が，動的な型生成を含むプログラムに発生しうるエラーの特徴である．この種のエラーには元のソースコード内にエラーの発生箇所と呼べる部分が明確に存在しない可能性があり，そのことが問題解決をより一層困難にしている．

2. 1 実行時エラーの例
前述のように，動的な型生成に起因するエラーにはいくつかの種類がある．この節ではこれらのミスのうち典型的なものの例をサンプルコードを用いて紹介する．
プログラム 3 は，インタフェース型 IActor を実装したクラスを動的に生成する C#のプログラムを簡略化したものである．C#の標準ライブラリを用いてイ

この例のようにプログラムが短い場合、ソースコーンタフェース型を実装する型を動的に生成する際にド全体を確認することでエラーの発生原因がスペルは SetInterfaceImplementation メソッドを呼び出すミスであることが確認出来るが、より複雑なコードの必要があるが，このコードはメソッド呼び出しを欠中にこのようなエラーが含まれていた場合、エラーのいているため，実行時，生成したクラスを IActor 型

情報のみから原因を特定することは困難である．にキャストする部分 (行番号 12) で実行時例外が発生
プログラム 4する．
プログラム 3123interface 実装忘れの例...

2

var typeBuilder = modBuilder.DefineTypepublic interface IActor("Person",TypeAttributes.Class,{typeof(object));
void Function();

35}
...

6

var typeBuilder = modBuilder.DefineType4("DynamicActor",TypeAttributes.
Class,typeof(object));
var methodBuilder = typeBuilder.
DefineMethod("Function",
MethodAttributes.Public |MethodAttributes.Virtual,typeof(void),Type.EmptyTypes);var exp = Expression.Block(Expression.Call(typeof(Console).
GetMethod(name: "WriteLine",types: new Type[]{typeof(string)})!),Expression.Constant("Hello!"));
Expression.Lambda(exp).CompileToMethod(methodBuilder);var t = typeBuilder.CreateType()!;var dy = (IActor)Activator.
CreateInstance(t)!;dy.Function();5478910111213

フィールド名誤りの例

167

var age = typeBuilder.DefineField("age",typeof(int),FieldAttributes.
Public);var t = typeBuilder.CreateType()!;dynamic dy = Activator.CreateInstance(t)!;dy.Age = 10;
Console.WriteLine($"Age: {dy.Age}");2. 2 型生成リフレクションに起因する実行時エラー
この節では，動的型生成を行うリフレクション計算に起因する実行時エラーをその発生タイミングで分類し列挙する．

1. CreateType 呼び出し時，生成しようとした型に不備がある場合

CreateType メソッドは，型を表すオブジェクトから Type 型のインスタンスを生成するメソッドである．このメソッドの内部では型を表すオブジェクトの持つ情報が参照され，その整合性を確認した後に Type 型のインスタンスが生成される．この種のエラーの例として，実装したインタプログラム 4 は，生成した型が持つフィールドのフェースが要求するメソッドを生成した型が持っ名前を誤ったためにフィールドアクセスの際に実行ていない場合や同名のフィールドを 2 つ以上定時エラーが発生するプログラムの疑似コードである．

義した場合などが挙げられる．

この例において，生成した型の持つフィールドの名前

2. 生成した型のインスタンスを既存の型にキャスは age であるが，フィールドアクセスを行う部分でトしたとき，キャスト先の型との間に互換性がなはフィールド Age にアクセスしようとしている．そい場合のため、このプログラムを実行した場合，フィールド先述の例のように実装すべきインタフェースやAge へのアクセス部分 (行番号 6) で missing-ﬁeld を継承すべき基底クラスが指定されていない場合原因とする実行時エラーが発生する．

や，誤った型をキャスト先に指定した場合が該当する．InvalidCastException として検出される．

ようとした型に不備がある場合，元のソースコード内

3. MissingMethodException 等，生成した型に原因と考えられる行が存在するならばその行をエが期待されるメンバを持っていないことによる実ラーメッセージ内で指摘する．例えば，1 つのメソッ

行時例外が発生するような場合

ドをオーバーライドするメソッドが 2 つ以上定義さメンバの追加を忘れたケースや，スペルミスなどれている場合，当該メソッドのオーバーライドメソッによって本来想定していたメンバが追加されないドを実行時に指定した全ての箇所をエラー原因の候場合などが該当する．存在しないフィールドへの補として列挙する．2. 2 節で述べた実行時エラーにつアクセスや存在しないメソッドの呼び出しとしていても同様に，それぞれ適切なエラーメッセージを表検出される．

示する．
エラー発生原因の特定につながるエラーメッセージ

2. 3 既存の処理系における動的型生成の問題点の扱いを生成するためには，型の構築時に行われたエラーの原因であると考えられる操作を参照し，その操作を以上でも繰り返し述べた通り，C#における動的な行った行の情報をエラーメッセージ内に含めるべきで型生成処理はバグを含みやすいものであり，また発生ある．そのためには，型構築の実行時に操作の履歴をしたエラーの原因を特定するのも困難であった．それ何らかのかたちで記録しておく必要がある．提案手法にもかかわらず現在においてもこの問題が解決されでは，型構築に関係する操作の実行履歴を実行時に適ていない理由として我々が考えているものは以下の 2

宜その型自身に埋め込むことで，エラー発生時の実行点である．

フローの追跡を可能とする．なお，エラーを発生させ

1. 動的な型生成処理を行う機能は言語機能の全体た型に過去に行われた操作のうちどれがエラーの原から見れば大きいものではないため，言語機能を因であるかは，発生したエラーの種類によって個別に拡張することにより得られる恩恵が機能拡張に判断する (3. 2 節で個別に説明する)．

必要なコストに対して小さく，問題解決の優先度が低い

提案手法では実行時エラー発生時に実行時エラー発生時に型構築に関する操作の実行履歴の分析を行

2. 動的な型生成処理に限らず一般にリフレクショう。このとき実行履歴に加えて制御フローの静的な情ン計算はバグを含みやすいものであると認知さ報も分析に使う。制御フローの静的な情報はコンパイれているが，それ故にリフレクション計算自体をル時に取得し、プログラム中に埋め込んでおく。この避けてプログラミングを行うことが推奨されて情報により、条件分岐などで実行時に選択されなかっきたため，問題解決のモチベーションが低い

た実行パスに含まれる操作についてもエラーメッセーつまるところ，問題そのものを回避することによっジに含めることができる。例えば、実行時に行われたてプログラマは問題を「解決」してきたのである．し誤りを含む操作だけでなく実行時に必要な操作が行かし，これらの問題は何らかの方法で解決すべきでわれなかった (条件分岐によってメソッドの追加が行ある．

われないケースがあるなど) ことによるバグの発生についても原因の特定につながる実行時エラーメッセー

3 実行時フロー情報の埋め込みによるエラーメッセージの改善

ジを生成することが出来る．
プログラマによるこれらのバグの原因の特定が困

我々は，動的型生成を行うリフレクション計算を原難である理由のひとつに，実行時エラーメッセージの因とする実行時エラーの発生時，エラーの原因となる情報量が不十分であるためエラーの発生原因を元の間違いを含む可能性のある行をエラーメッセージ内でソースコードの広い範囲から探さなくてはならない指摘することによりバグの原因の早期発見を支援すことが挙げられる．提案手法の仕組みを用いることでるための仕組みを提案する．この仕組みでは，生成しプログラマは動的型生成を行うリフレクション計算に起因する実行時エラーの発生時にエラーの原因を絞たものである．このプログラムもプログラム 4 と同り込めるようになるため，バグの原因を早期に発見す様スペルミスが原因の実行時エラーを含んでいるが，ることが出来るようになると期待出来る．

このプログラムの実行時に出力されるエラーメッセー
ジはフィールド age の情報を元にスペルミスの可能

3. 1 実現方法性を指摘する．なお，このプログラムにおいても実行

通常の C#における動的な型生成のリフレクション

時エラーは 7 行目で発生する．

計算は，以下のような手順で行われる．

1. 型を表すオブジェクトを作成する
2. 作成したオブジェクトの持つメソッドを呼び，型の情報を修飾する．ここではオブジェクトに対して行うことの出来る操作の一部を挙げる

プログラム 5...

2

let person = new Class[name = "Person

3

let age = new Field[name = "age",type"]();• メソッドの追加• フィールドの追加

4• コンストラクタの追加

5• ジェネリックパラメータの設定• 基底クラスの設定

6• インタフェースの実装

7• オーバーライドメソッドの設定

提案手法におけるフィールド名誤りの例

18= int]();let person = person.AddField(age);
typevar person_t := person.CreateType();
var dy = new person_t();dy.Age = 10;
Console.WriteLine($"Age: {p.Age}");3. 型を表すオブジェクトの CreateType メソッドを呼び出し，Type 型のインスタンスを得る

3. 2 エラーメッセージの例

4. Activator.CreateInstance メ ソッド を 用 い て ，

この節では 2. 2 節で示したエラーについて，様々な前項で得た Type 型のインスタンスが表す型のイケースにおいて生成されるエラーメッセージの具体的ンスタンスを生成するな例を挙げる．

我々は，生成する型を表すオブジェクトに対し型構3. 2. 1 CreateType 呼び出し時，生成しようと築フローの情報を適宜追加することにより提案手法をした型に不備がある場合実現する．より具体的には型を表すオブジェクトに対• 同名メンバが重複して定義された場合する AddMethod や AddField などの操作や条件分

このエラーは CreateType 呼び出し時に発生す岐を行った直後に，実行時のスタックトレース情報かる．CreateType の呼び出し時，生成される型をら生成した当該部分のソースコード内における位置や表すオブジェクトの attribute から実行時に同名操作内容の情報を SetCustomAttribute メソッドを用

メンバの定義を行った部分の情報を取り出し，エいて生成する型を表すオブジェクトに追加し，生成さラーメッセージ内で該当箇所を列挙する．

れる型に attribute として型構築フローの情報を埋め• 基底クラスや実装したインタフェースが要求す込む．このようにして埋め込んだ情報を CreateType

るメソッドが欠落した場合の呼び出し時やメンバアクセス時など実行時エラー

このエラーは CreateType 呼び出し時に発生すの発生時に参照し、発生したエラーの内容を元に過る．追加されたメソッドの中に要求されたメソッ去の操作からエラーに繋がるものを探し出すことで，ドと近い名前を持つものや，同名であるが型の異エラー原因の特定につながる情報を含んだエラーメッなるものを探す．ここで，メソッドの名前の「近セージを生成する．

さ」は編集距離によって決定する．
「近い」メソッ以下のプログラム 5 は，プログラム 4 と同内容のドが発見された場合，発見されたメソッドを追加プログラムを提案手法の拡張文法に従って書き直しした部分の情報をエラーメッセージに含める．
「近い」メソッドが発見されなかった場合にはメソッ

い」名前を持つフィールドがその型に存在する場

ドの追加を忘れたものと判断し，CreateType の合，スペルミスがエラーの原因であると判断し呼び出し自体をエラーの原因とする．

「近い」名前のフィールドを追加した部分の情報• オーバーライド設定の欠落をエラーメッセージに含める．そうでない場合

このエラーは CreateType 呼び出し時に発生すは型生成処理内におけるフィールドの追加忘れる．オーバーライドすべきメソッドと同名/同であると判断し，その型を生成した CreateType型のメソッドが存在するがオーバーライドが適メソッドの呼び出し部分の情報をエラーメッセー

切に設定されていない場合がこれに該当する．

ジに含める．また，型生成の実行時フローが条件

CreateType 呼び出しをエラー原因として指摘

分岐を含んでいる場合，選択されなかったパスがし、オーバーライドを設定するためのメソッドのアクセスしようとしたものと同名のフィールドを呼び出しを追加するように提案する．

追加する可能性についてチェックを行う．選択さ• 互換性のないメソッドのオーバーライドれなかったパスがそのようなフィールドの追加操

このエラーは CreateType 呼び出し時に発生す作を含んでいた場合，条件分岐部分もエラー原因る．オーバーライド設定対象のミスを疑う．型にの候補として指摘する．

追加されたメソッドの中に本来オーバーライドが設定されるべきメソッドと「近い」メソッドが3. 3 実装方法存在した場合，そのメソッドを追加した部分をエ

この節では，提案したシステムの具体的な実装案にラー原因として指摘する．

ついて解説する．提案したシステムは C#をベースと

3. 2. 2 生成した型のインスタンスを既存の型にしたものであるが，既存の C#の文法のみではシステキャストしたとき，キャスト先の型との間ムの構築には不十分である．よって我々は，C#の文に互換性がない場合法を拡張することによって提案したシステムを実現す• 互換性のない型へのキャストることにした．現時点で拡張文法に対応したコンパイ

このエラーはキャスト演算の実行時に発生する．

ラは完成していないが，以下で示す方針に従うことでインタフェースの実装あるいは基底クラスへの追実装が可能であると考えている．

加を行う部分を実行時フローが通過しなかった

C#コンパイラのリファレンス実装である Roslyn はことによるエラーを疑う．型構築の実行時フローオープンソースソフトウェアとして開発されている．

から実行時に通らなかったパスの情報を取得し，そのため，Roslyn のソースコードに変更を加えるこ通らなかったパスの中に本来行うべき処理が見つとで，C#の文法に加えて拡張文法にも対応したコン

かった場合はそのパスを通らない原因となった条パイラを作成することが可能である．

件分岐箇所をエラー原因の候補として指摘する．
そうでない場合，設定忘れあるいは互換性を持たせる予定のない型への誤ったキャストであると判断し、CreateType 呼び出しおよびキャスト演算をエラー原因の候補とする．

3. 2. 3 MissingMethoException 等，生成した型が期待されるメンバを持っていないことによる実行時例外が発生するような場合• 存在しないフィールドへのアクセス

Roslyn によるコンパイルのフローは以下のような構成になっている．

1. Parse プログラムの字句解析および構文解析を行う

2. Declaration プログラム内の宣言やインポートしたメタデータから名前付きシンボルの宣言を分析する

3. Binder 解析したシンボルとプログラム内の識別子を対応付ける

このエラーはフィールドアクセスの実行時に発4. Emit 解析結果から IL を生成し，出力する生する．アクセスしようとしたフィールドと「近本システムの実装のため，拡張コンパイラはパース処理の後にソースコードの変換処理を行う．こ

レートの使い方に誤りがあった場合、使い方そのものの方針は現在の C#の文法においても foreach 文やのミスを原因とするのではなくテンプレートの展開

IEnumerator/IEnumerable 型を戻り値として返す所の失敗を原因とする大量のコンパイルエラーメッセー

謂「イテレータ構文」を用いたメソッドの実装に用いジが生成されてしまうため，テンプレートを活用したられている．元のソースコード内に存在するメソッドプログラミングを正しく行うことのできるプログラの追加やインタフェースの実装などの型を表すオブマは限られていた．この論文で提案された concepts

ジェクトへの操作の後に，そのオブジェクトに対してはテンプレートが要求する性質を型に近い形でまとSetCustomAttribute メソッドを実行するコードを追めたものである．この機構の導入により，テンプレー加する．また，型の構築に関わる部分に含まれる条件トの使い方に起因するエラーのコンパイルエラーメッ

分岐に対し，条件分岐の前にそれぞれの分岐先においセージはテンプレートの展開失敗ではなく conceptsてオブジェクトに行われる可能性のある操作の情報の制約違反を原因とするものとなり，テンプレートメを Attribute にセットする処理を挿入する．これによタプログラミングを行いやすくなった．なお，この論り，例えばあるメソッドを追加する処理を行うフロー

文で提案された concepts は C++20 より C++の正を通らなかったことにより実行時エラーが発生したと式機能となった．

き，条件分岐部分がエラーの発生原因である可能性を指摘することが可能となる．
加えて，CreateType メソッドの呼び出し前および文献 [2] は，OCaml にマルチステージプログラミングの機能を導入することで型安全なメタプログラミン
グを可能とした MetaOCaml の設計および実装を示

実行時に生成した型を用いる部分，例えばプログラしたものである．MetaOCaml では動的コード生成な

ム 1 の v のような実行時に生成した型のインスタン

どのメタプログラミング操作に対して静的な型検査がスが持つフィールドへのアクセスやこのようなインス行われるため．静的検査のないシステムと比較してエタンスの既存の型へのキャストの前にもコードを挿入

ラー発生時のバグ修正が容易になることやエラーメッする．ここで挿入するコードは，直後に行おうとしてセージが結果的に読みやすくなることが期待出来るいる操作，例えばフィールドアクセスやキャスト演算が，この研究はエラーメッセージの改善を目的としたなどが実行可能であるかどうかを当該処理の前にあものではないため我々の研究の目的にはそぐわない．

らかじめ検査するものである．このとき挿入されるTyped Racket や Template Haskell など，メタプ

コードでは，型の生成時に付加された attribute の情

ログラミングの可能な静的型付き言語の処理系はその報を用いることで操作の有効性を確認し，操作が不正他にも存在する．メタプログラミングを改善するためであると判断した場合は型の構築フローの情報を元の様々な研究の中には，我々の研究と同様エラーメッにエラーの原因と考えられる操作の行番号を含んだセージの改善を目的としたものもあると考えられる．

エラーメッセージを出力する．

現在，詳細を調査中である．

4 関連研究5 まとめと今後の課題

C#における動的な型生成処理に関するエラーメッ

本研究では，C#における動的な型生成処理に起因セージを改善する実用的なライブラリや処理系は我々するバグの早期発見ならびに早期のバグ修正のため，が知る限り存在しないが，静的言語のコンパイル時エ型生成処理の実行時フローの情報を生成対象の型にラーメッセージの改善を目的とした研究は存在する．

埋め込むことによる実行時エラーメッセージの情報の文献 [1] は，C++におけるテンプレートの誤用を原改善手法を提案した．具体的な実装方法の案はあるも因としたエラーメッセージの改善を目的とした言語のの実装は未完成のため，提案手法を実現した実装を機構 concepts を提案したものである．C++のテンプ完成されることが今後の課題である．また，提案手法

レートは非常に強力な表現力を持っているが，テンプをより実用的なものとするため，本研究のターゲットのうちより早期に発見することが可能であると考えられるエラーを静的に解析するための仕組みを検討している．

参 考 文 献[1] Gregor, D., Järvi, J., Siek, J., Stroustrup, B.,
Dos Reis, G., and Lumsdaine, A.: Concepts: Lin-guistic Support for Generic Programming in C++,SIGPLAN Not., Vol. 41, No. 10(2006), pp. 291–310.
[2] Kiselyov, O.: The Design and Implementation ofBER MetaOCaml - System Description, Functionaland Logic Programming - 12th International Symposium, FLOPS 2014, Kanazawa, Japan, June 4-6,
2014. Proceedings, Codish, M. and Sumii, E.(eds.),
Lecture Notes in Computer Science, Vol. 8475,Springer, 2014, pp. 86–102.