を使ってGuiceの依存性注入問題をキャッチし、修正する方法。Sensei

2021年1月25日発行
アラン・リチャードソン著
ケーススタディ

を使ってGuiceの依存性注入問題をキャッチし、修正する方法。Sensei

2021年1月25日発行
アラン・リチャードソン著
リソースを見る
リソースを見る

Sensei プロジェクト自体にも、時間をかけて構築された独自のレシピがあります。このブログ記事は、Sensei チームがレシピを構築したシナリオの一つの例です。Guiceの設定ミスにより、テスト時にランタイムでNullPointerExceptionが報告されてしまいました。

これは、コードが構文的には正しくても、配線の構成が間違っていたためにエラーが出てしまうという、多くのDependency Injectionのシナリオに一般化できます。

これは技術を学んでいるときによく起こることで、配線を忘れてしまうという単純なミスを繰り返してしまいます。しかし、これは経験豊富なプロにも起こることで、まあ...誰でもミスをするし、すべてをカバーするユニットテストを持っていないかもしれません。

 

不適切な依存性注入の配線によるランタイム例外

以下のコードは、実行時にNullPointerExceptionで失敗します。

injector = Guice.createInjector(new SystemOutModule());
CountReporter reporter = injector.getInstance(CountReporter.class);
String [] lines5 = {"1: line", "2: line", "3: line", "4: line", "5: line"};
reporter.reportThisMany(Arrays.asList(lines5));
Assertions.assertEquals(5, reporter.getCount());


このコードは構文的には正しいのですが、SystemOutModuleの設定にrequestStaticInjectionが含まれていないために失敗します。

public class SystemOutModule extends AbstractModule {
    @Override
    protected void configure() {
        binder().bind(ILineReporter.class).to(SystemOutReporter.class);
    }
}


Injector を使用して作成されたレポーターを使用しようとすると、完全にインスタンス化されておらず、reportThisMany を呼び出すと NullPointerException が発生します。

コードレビューで見落としていたり、依存性注入のトリガーとなるユニットテストを用意していなかったりして、ビルドに紛れ込んでしまったのかもしれません。

警告のサイン

この場合、警告サインが出ています。CountReporterには@Injectでアノテーションされた静的フィールドがありますが、...CountReporterクラス自体はpackage privateです。

複雑なコードベースでは、これはコードが間違っているという警告サインになるかもしれません。というのも、バインディングを設定するモジュールクラスが同じパッケージになければ、この機能は働かないからです。

class CountReporter {
    @Inject
    private static ILineReporter reporter;


もう一つのミスは、コードレビューで指摘されたかもしれませんが、SystemOutModuleのconfigureメソッドで実際にフィールドをバインドすることを忘れてしまったことです。

binder().requestStaticInjection(CountReporter.class);


もしrequestStaticInjectionのコードを書いていたら、CountReporterを使おうとしたときに発生したSyntax Errorが、単純なエラーを警告してくれたでしょう。

reporters.CountReporter」は「reporters」では公開されていません。外部のパッケージからはアクセスできません。

悲しいことに。私たちは忘れていて、コードには構文上の警告表示がありませんでした。

Sensei はどのように役立つのでしょうか?

Sensei というのも、Guiceのすべての設定配線がこのメソッドを使用する必要があり、すべての配線がこの使用例のように単純であることを保証できないからです。

Sensei のルールを書いて、コードに問題があることを示すいくつかの警告サインを探すことができます。

この場合、それはつまり

  • Injectアノテーションされたフィールドを持つクラスの検索
  • クラスが公開されていないところ。

以上のことから、配線がされている可能性は低いという警告が出ていた。

レシピを作成することで、コーディング中の早い段階で警告のサインを出すことができ、プルリクエストや技術的負債を解決してユニットテストを追加できるようにすることへの依存度を減らすことができます。

レシピの作成方法は?

私が完成させたい課題は

  • 保護されたプライベートクラスにある@Injectでアノテーションされたフィールドにマッチするレシピを作成します。

これで、これを使用しているモジュールを特定し、不足している配線コードを追加するための十分な警告が得られると思います。

私のCountReporterクラスでは、Alt+Enterで新しいレシピを作成し、最初から始めることにします。

これに名前をつけて説明を加えます。

名前ガイス。Injected Field Not Public
説明。Injectedフィールドが公開されていない場合、コードが配線されていない可能性があります。
Level:警告


私が書いた検索は、Injectとしてアノテーションされたフィールドを持つクラスを探しますが、それはpublicとしてスコープされていません。

search:
field:
with:
annotation:
type:"com.google.inject.Inject"
in:
class:
without:
modifier:"public"

修正

レシピのQuickFixでは、スコープを変更することで注入されたクラスを修正します。しかし、変更しなければならないコードはそれだけではありません。

availableFixes:

- name: "Change class to public.Remember to request injection on this class"
アクション:
- changeModifiers:
visibility:"public"
target: "parentClass"
Sensei レシピを編集する QuickFix の設定

レシピがトリガーされたときには、オブジェクトを完全にインスタンス化するためにrequestStaticInjectionを含む行を追加するという、コードで実行する手動のステップが残っています。

public class SystemOutModule extends AbstractModule {
    @Override
    protected void configure() {
        binder().bind(ILineReporter.class).to(SystemOutReporter.class);
        // instantiate via dependency injection
        binder().requestStaticInjection(CountReporter.class);
    }
}


この問題を解決するために別のレシピを書くこともできます。静的インジェクションの追加を忘れることが、コーディングの際によくあるミスにならない限り、私はそんなことはしないでしょう。

概要

もし、共通のルートパターンでミスをしてしまうことがあれば、Sensei 、問題の検出と修正に関する知識を体系化することができます。そうすれば、コードレビューを通過して本番に入ってしまうこともなくなるでしょう。

私たちが書くレシピは、ヒューリスティックなパターンを識別することがあります。つまり、それらを照合しても問題があるとは限らず、問題がある可能性が高いのです。

また、私たちが書くレシピやQuickFixは、完全に網羅されている必要はありませんが、複雑になりすぎず、問題の特定や解決に役立つ程度のものである必要があります。なぜなら、複雑になりすぎると、理解するのが難しくなり、維持するのも難しくなるからです。

---


IntelliJの「Preferences ‾ Plugins」(Mac)または「Settings ‾ Plugins」(Windows)から、「sensei secure code」を検索して、「Sensei 」をインストールすることができます。


この記事のソースコードとレシピは、Secure Code Warrior GitHub アカウントの `sensei-blog-examples` リポジトリの `guiceexamples` モジュールにあります。


https://github.com/securecodewarrior/sensei-blog-examples

についてはこちらをご覧ください。Sensei



リソースを見る
リソースを見る

著者

アラン・リチャードソン

もっと知りたい?

セキュアコーディングに関する最新の知見をブログでご紹介しています。

当社の豊富なリソースライブラリは、安全なコーディングアップスキルに対する人間的なアプローチを強化することを目的としています。

ブログを見る
もっと知りたい?

開発者主導のセキュリティに関する最新の研究成果を入手する

ホワイトペーパーからウェビナーまで、開発者主導のセキュアコーディングを始めるために役立つリソースが満載のリソースライブラリです。今すぐご覧ください。

リソース・ハブ

を使ってGuiceの依存性注入問題をキャッチし、修正する方法。Sensei

2021年1月25日発行
アラン・リチャードソン著

Sensei プロジェクト自体にも、時間をかけて構築された独自のレシピがあります。このブログ記事は、Sensei チームがレシピを構築したシナリオの一つの例です。Guiceの設定ミスにより、テスト時にランタイムでNullPointerExceptionが報告されてしまいました。

これは、コードが構文的には正しくても、配線の構成が間違っていたためにエラーが出てしまうという、多くのDependency Injectionのシナリオに一般化できます。

これは技術を学んでいるときによく起こることで、配線を忘れてしまうという単純なミスを繰り返してしまいます。しかし、これは経験豊富なプロにも起こることで、まあ...誰でもミスをするし、すべてをカバーするユニットテストを持っていないかもしれません。

 

不適切な依存性注入の配線によるランタイム例外

以下のコードは、実行時にNullPointerExceptionで失敗します。

injector = Guice.createInjector(new SystemOutModule());
CountReporter reporter = injector.getInstance(CountReporter.class);
String [] lines5 = {"1: line", "2: line", "3: line", "4: line", "5: line"};
reporter.reportThisMany(Arrays.asList(lines5));
Assertions.assertEquals(5, reporter.getCount());


このコードは構文的には正しいのですが、SystemOutModuleの設定にrequestStaticInjectionが含まれていないために失敗します。

public class SystemOutModule extends AbstractModule {
    @Override
    protected void configure() {
        binder().bind(ILineReporter.class).to(SystemOutReporter.class);
    }
}


Injector を使用して作成されたレポーターを使用しようとすると、完全にインスタンス化されておらず、reportThisMany を呼び出すと NullPointerException が発生します。

コードレビューで見落としていたり、依存性注入のトリガーとなるユニットテストを用意していなかったりして、ビルドに紛れ込んでしまったのかもしれません。

警告のサイン

この場合、警告サインが出ています。CountReporterには@Injectでアノテーションされた静的フィールドがありますが、...CountReporterクラス自体はpackage privateです。

複雑なコードベースでは、これはコードが間違っているという警告サインになるかもしれません。というのも、バインディングを設定するモジュールクラスが同じパッケージになければ、この機能は働かないからです。

class CountReporter {
    @Inject
    private static ILineReporter reporter;


もう一つのミスは、コードレビューで指摘されたかもしれませんが、SystemOutModuleのconfigureメソッドで実際にフィールドをバインドすることを忘れてしまったことです。

binder().requestStaticInjection(CountReporter.class);


もしrequestStaticInjectionのコードを書いていたら、CountReporterを使おうとしたときに発生したSyntax Errorが、単純なエラーを警告してくれたでしょう。

reporters.CountReporter」は「reporters」では公開されていません。外部のパッケージからはアクセスできません。

悲しいことに。私たちは忘れていて、コードには構文上の警告表示がありませんでした。

Sensei はどのように役立つのでしょうか?

Sensei というのも、Guiceのすべての設定配線がこのメソッドを使用する必要があり、すべての配線がこの使用例のように単純であることを保証できないからです。

Sensei のルールを書いて、コードに問題があることを示すいくつかの警告サインを探すことができます。

この場合、それはつまり

  • Injectアノテーションされたフィールドを持つクラスの検索
  • クラスが公開されていないところ。

以上のことから、配線がされている可能性は低いという警告が出ていた。

レシピを作成することで、コーディング中の早い段階で警告のサインを出すことができ、プルリクエストや技術的負債を解決してユニットテストを追加できるようにすることへの依存度を減らすことができます。

レシピの作成方法は?

私が完成させたい課題は

  • 保護されたプライベートクラスにある@Injectでアノテーションされたフィールドにマッチするレシピを作成します。

これで、これを使用しているモジュールを特定し、不足している配線コードを追加するための十分な警告が得られると思います。

私のCountReporterクラスでは、Alt+Enterで新しいレシピを作成し、最初から始めることにします。

これに名前をつけて説明を加えます。

名前ガイス。Injected Field Not Public
説明。Injectedフィールドが公開されていない場合、コードが配線されていない可能性があります。
Level:警告


私が書いた検索は、Injectとしてアノテーションされたフィールドを持つクラスを探しますが、それはpublicとしてスコープされていません。

search:
field:
with:
annotation:
type:"com.google.inject.Inject"
in:
class:
without:
modifier:"public"

修正

レシピのQuickFixでは、スコープを変更することで注入されたクラスを修正します。しかし、変更しなければならないコードはそれだけではありません。

availableFixes:

- name: "Change class to public.Remember to request injection on this class"
アクション:
- changeModifiers:
visibility:"public"
target: "parentClass"
Sensei レシピを編集する QuickFix の設定

レシピがトリガーされたときには、オブジェクトを完全にインスタンス化するためにrequestStaticInjectionを含む行を追加するという、コードで実行する手動のステップが残っています。

public class SystemOutModule extends AbstractModule {
    @Override
    protected void configure() {
        binder().bind(ILineReporter.class).to(SystemOutReporter.class);
        // instantiate via dependency injection
        binder().requestStaticInjection(CountReporter.class);
    }
}


この問題を解決するために別のレシピを書くこともできます。静的インジェクションの追加を忘れることが、コーディングの際によくあるミスにならない限り、私はそんなことはしないでしょう。

概要

もし、共通のルートパターンでミスをしてしまうことがあれば、Sensei 、問題の検出と修正に関する知識を体系化することができます。そうすれば、コードレビューを通過して本番に入ってしまうこともなくなるでしょう。

私たちが書くレシピは、ヒューリスティックなパターンを識別することがあります。つまり、それらを照合しても問題があるとは限らず、問題がある可能性が高いのです。

また、私たちが書くレシピやQuickFixは、完全に網羅されている必要はありませんが、複雑になりすぎず、問題の特定や解決に役立つ程度のものである必要があります。なぜなら、複雑になりすぎると、理解するのが難しくなり、維持するのも難しくなるからです。

---


IntelliJの「Preferences ‾ Plugins」(Mac)または「Settings ‾ Plugins」(Windows)から、「sensei secure code」を検索して、「Sensei 」をインストールすることができます。


この記事のソースコードとレシピは、Secure Code Warrior GitHub アカウントの `sensei-blog-examples` リポジトリの `guiceexamples` モジュールにあります。


https://github.com/securecodewarrior/sensei-blog-examples

についてはこちらをご覧ください。Sensei



弊社製品や関連するセキュアコーディングのトピックに関する情報をお送りする許可をお願いします。当社は、お客様の個人情報を細心の注意を払って取り扱い、マーケティング目的で他社に販売することは決してありません。

送信
フォームを送信するには、「Analytics」のCookieを有効にしてください。完了したら、再度無効にしてください。