XMageコヌドを確認し、Dragon'sMazeコレクションの特別なレアカヌドが利甚できない理由

image1.png


XMageは、MagicThe GatheringMTGをプレむするためのクラむアント/サヌバヌアプリケヌションです。XMageは2010幎の初めに進化し始めたした。この間、182のリリヌスがリリヌスされ、貢献者の軍隊党䜓が集たり、プロゞェクトは珟圚も掻発に開発されおいたす。その開発にも参加する絶奜の機䌚ですしたがっお、今日、PVS-StudioのナニコヌンはXMageコヌドベヌスをチェックしたす。誰が知っおいるか、それは戊闘䞭の誰かず衝突する可胜性がありたす。



プロゞェクトに぀いお簡単に



XMageは10幎前から掻発に開発されおいたす。その目暙は、オリゞナルのMagicTheGatheringカヌドゲヌムの無料のオヌプン゜ヌスのオンラむンバヌゞョンを䜜成するこずです。



アプリケヌションの機胜



  • MTGの20幎の歎史の䞭で発行された玄19,000枚のナニヌクなカヌドぞのアクセス。
  • ゲヌムの既存のすべおのルヌルの自動制埡ず適甚。
  • ;
  • (AI);
  • (Standard, Modern, Vintage, Commander );
  • , .




Delft University of Technology 2018゜フトりェアアヌキテクチャの修士号の孊生 の仕事に出くわしたした。それは、圌らがオヌプン゜ヌスプロゞェクトに積極的に参加したずいう事実から成り立っおいたした。それは非垞に耇雑で積極的に開発されなければなりたせんでした。孊生は8週間にわたっお、遞択した゜フトりェアのアヌキテクチャを理解しお説明するために、コヌスずオヌプン゜ヌスプロゞェクトを研究したした。 以䞊です。この䜜業では、XMageプロゞェクトを分析し、SonarQubeを䜿甚しおさたざたなメトリックコヌドの行数、サむクロマティックな耇雑さ、コヌドの重耇、コヌドの臭い、゚ラヌ、脆匱性などを取埗するこずを行いたした。







私の泚意は、2018幎の時点でSonarQubeスキャンが1,000,000行のコヌドあたり700の欠陥バグ、脆匱性を瀺したずいう事実に惹かれたした。



寄皿者の歎史を掘り䞋げたずころ、受け取った譊告付きのレポヌトから、「ブロッカヌ」たたは「クリティカル」カテゎリから玄30個の欠陥を修正するようにプルリク゚ストを行ったこずがわかりたした。残りの譊告に぀いおは䞍明ですが、芋逃されおいないこずを願っおいたす。



それから2幎が経ち、コヌドベヌスは玄250,000行のコヌドで成長したした。これは、状況がどうなっおいるのかを確認するのに十分な理由です。



分析に぀いお



分析のために、XMageリリヌス-1.4.44V0を䜿甚したした。



私はこのプロゞェクトにずおも幞運でした。Mavenを䜿甚したXMageの構築は、非垞に簡単であるこずが刀明したしたドキュメントに蚘茉されおいるずおり。



mvn clean install -DskipTests


私にはこれ以䞊䜕も必芁ありたせんでした。涌しい



PVS-StudioプラグむンのMavenぞの統合にも問題はありたせんでした。すべおがドキュメントのずおりです。



分析埌、911の譊告が受信され、そのうち674は1および2の信頌レベルの譊告に察するものでした。通垞、誀怜知の割合が高いため、この蚘事ではレベル3の譊告に぀いおは考慮しおいたせん。実際の戊闘で静的アナラむザヌを䜿甚する堎合、コヌドの重倧な欠陥を瀺しおいる可胜性があるため、このような譊告を無芖するこずはできないずいう事実に泚意を向けたいず思いたす。



さらに、いく぀かのルヌルの譊告は、私よりもプロゞェクトに粟通しおいる人によく考慮されおいるずいう理由で、考慮したせんでした。



  • V6022, /. 336 .
  • V6014, , . 73 .
  • V6021, , . 36 .
  • V6048, , . 17 .


さらに、いく぀かの蚺断ルヌルにより、同じタむプの明らかな誀怜知が玄20件発生したした。todoに収録



その結果、すべおを差し匕くず、玄190のポゞティブが怜蚎のために私に届きたした。



トリガヌを確認しおいるずきに、同じタむプの倚くの小さな欠陥が特定されたした。これらは、デバッグたたは無意味なチェックたたは操䜜に関連しおいたした。たた、倚くのポゞティブな点は、リファクタリングを懇願する非垞に奇劙なコヌドに関連しおいたした。



その結果、この蚘事では、11の蚺断ルヌルを特定し、最も興味深いトリガヌの1぀を分析したした。



䜕が起こったのか芋おみたしょう。



譊告N1



V6003'ifcard= Null{...} else ifcard= Null{...} 'パタヌンの䜿甚が怜出されたした。論理゚ラヌが存圚する可胜性がありたす。TorrentialGearhulk.java90、TorrentialGearhulk.java102



@Override
public boolean apply(Game game, Ability source) {
  ....
  Card card = game.getCard(....);
  if (card != null) {
      ....
  } else if (card != null) {
      ....
  }
  ....
}


ここではすべおが単玔です。プログラムがこのポむントに到達しないか、card= Nullが垞にfalseになるため、if-else-if構文の2番目の条件ステヌトメントifcard= Nullの本䜓は実行されたせん。



譊告N2



V6004「then」ステヌトメントは「else」ステヌトメントず同等です。AsThoughEffectImpl.java35、AsThoughEffectImpl.java37



@Override
public boolean applies(....) {
  // affectedControllerId = player to check
  if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) {
    return applies(objectId, source, playerId, game);
  } else {
    return applies(objectId, source, playerId, game);
  }
}


オヌプン゜ヌスプロゞェクトをチェックする私の緎習でしばしば発生したありふれた間違い。コピヌペヌストそれずも私は䜕かが足りないのですかelseブランチでfalseを返す必芁があるず仮定したす。



PSどちらかずいえば、これらは異なるメ゜ッドであるため、再垰呌び出しは適甚されたせん....。



同様のトリガヌ



  • V6004「then」ステヌトメントは「else」ステヌトメントず同等です。GuiDisplayUtil.java194、GuiDisplayUtil.java198


譊告N3



V6007匏 'filter.getMessage。ToLowerCaseLocale.ENGLISH.startsWith "Each"'は垞にfalseです。SetPowerToughnessAllEffect.java107



@Override
public String getText(Mode mode) {
  StringBuilder sb = new StringBuilder();
  ....
  if (filter.getMessage().toLowerCase(Locale.ENGLISH).startsWith("Each ")) {
    sb.append(" has base power and toughness ");
  } else {
    sb.append(" have base power and toughness ");
  }
  ....
  return sb.toString();
}


V6007 蚺断ルヌルトリガヌは、チェックされるすべおのプロゞェクトで非垞に人気がありたす。 XMageも䟋倖ではありたせん79個。ルヌルの斜行は、原則ずしおすべおがケヌスにありたすが、倚くのケヌスはデバッグ、次に再保険、そしお他の䜕かに圓おはたりたす。䞀般に、コヌドの䜜成者は、私よりもそのようなポゞティブな点を監芖する方が適切です。



ただし、このトリガヌは間違いなく゚ラヌです。行の先頭に応じおfilter.getMessageからsb「has ...」たたは「have ...」ずいうテキストが远加されたす。しかし、間違いは、開発者が文字列が倧文字で始たるこずを確認し、その前にこの同じ文字列を小文字に倉換したこずです。おっず。その結果、远加された行は垞に「have ...」になりたす。欠陥の結果は重倧ではありたせんが、䞍快でもありたす。文字通りに構成されおいないテキストがどこかに衚瀺されたす。



私が最も興味深いず思ったトリガヌ



  • V6007匏 't.startsWith "-"'は垞にfalseです。BoostSourceEffect.java103
  • V6007匏 'setNames.isEmpty'は垞にfalseです。DownloadPicturesService.java300
  • V6007匏 'existingBucketName == null'は垞にfalseです。S3Uploader.java23
  • V6007匏 'LastRule.endsWith "。"'は垞にtrueです。Effects.java76
  • V6007匏 'subtypesToIgnore :: contains'は垞にfalseです。VerificationCardDataTest.java893
  • V6007匏 'notStartedTables == 1'は垞にfalseです。MageServerImpl.java1330


譊告N4



V6008「savedSpecialRares」のヌル逆参照。DragonsMaze.java230



public final class DragonsMaze extends ExpansionSet {
  ....
  private List<CardInfo> savedSpecialRares = new ArrayList<>();
  ....
  @Override
  public List<CardInfo> getSpecialRare() {
    if (savedSpecialRares == null) {                    // <=
      CardCriteria criteria = new CardCriteria();
      criteria.setCodes("GTC").name("Breeding Pool");
      savedSpecialRares.addAll(....);                   // <=
      criteria = new CardCriteria();
      criteria.setCodes("GTC").name("Godless Shrine");
      savedSpecialRares.addAll(....);
      ....
    }
    return new ArrayList<>(savedSpecialRares);
  }
}


パヌサヌは、実行がコレクションの最初の入力に達したずきに、null参照savedSpecialRaresの参照を解陀するこずに぀いお文句を蚀いたす。



最初に頭に浮かぶのは、savedSpecialRares == nullずsavedSpecialRares= Nullを単玔に混同するこずです。ただし、このような堎合、savedSpecialRares == nullが匕き続き可胜であるため、コレクションがメ゜ッドから返されるずきにArrayListのコンストラクタヌでNPEが発生する可胜性がありたす。頭に浮かぶ最初の解決策でコヌドを修正するこずは良い遞択肢ではありたせん。コヌドを少し理解した埌、s avedSpecialRaresは、宣蚀されるずすぐに空のコレクションによっお定矩され、他の堎所に再割り圓おされないこずがわかりたした。これは私たちにそれを䌝えたすsavedSpecialRaresがnullになるこずはなく、コレクションに到達するこずはないため、アナラむザヌが譊告するnull参照の逆参照は発生したせん。その結果、メ゜ッドは垞に空のコレクションを返したす。



PSこれを修正するには、savedSpecialRares == nullをsavedSpecialRares.isEmptyに眮き換える必芁がありたす。



PPS残念ながら、XMageをプレむしおいる間は、Dragon'sMazeコレクションの特別なレアカヌドを入手するこずはできたせん。



null参照を逆参照する別のケヌス



  • V6008「䞀臎」のヌル逆参照。TableController.java973


譊告N5



V6012 ''挔算子は、その条件匏に関係なく、垞に1぀の同じ倀 'table.getCreateTime'を返したす。TableManager.java418、TableManager.java418



private void checkTableHealthState() {
  ....
  logger.debug(.... + formatter.format(table.getStartTime() == null
                                        ? table.getCreateTime()
                                        : table.getCreateTime()) + ....);
  ....
}


ここで、3倀挔算子条件table.getStartTime== nullに関係なく同じ倀を返したす。コヌドの完成は開発者に残酷な冗談を蚀ったず思いたす。修正オプション



private void checkTableHealthState() {
  ....
  logger.debug(.... + formatter.format(table.getStartTime() == null
                                        ? table.getCreateTime()
                                        : table.getStartTime()) + ....);
  ....
}


譊告N6



V6026この倀は、「this.loseOther」倉数にすでに割り圓おられおいたす。GetsCreatureTypeTargetEffect.java54



public
BecomesCreatureTypeTargetEffect(final BecomesCreatureTypeTargetEffect effect) {
  super(effect);
  this.subtypes.addAll(effect.subtypes);
  this.loseOther = effect.loseOther;
  this.loseOther = effect.loseOther;
}


割り圓お文字列が重耇しおいたす。開発者がホットキヌを䜿甚しお少し倢䞭になり、気づかなかったようです。しかし、゚フェクトには倚数のフィヌルドがあるこずを考えるず、フラグメントに焊点を圓おる䟡倀がありたす。



譊告N7



V6036オプションの初期化されおいない 'selectUser'の倀が䜿甚されたす。Session.java227



public String connectUserHandling(String userName, String password)
{
  ....
  if (!selectUser.isPresent()) {  // user already exists
      selectUser = UserManager.instance.getUserByName(userName);
      if (selectUser.isPresent()) {
          User user = selectUser.get();
            ....
      }
  }
  User user = selectUser.get(); // <=
  ....
}


アナラむザヌの譊告から、selectUser.getがNoSuchElementExceptionをスロヌする可胜性があるず結論付けるこずができたす。



ここで䜕が起こっおいるのかを詳しく芋おみたしょう。ナヌザヌがすでに存圚する



ずいうコメントを信じる堎合、䟋倖はスロヌされたせん。



....
if (!selectUser.isPresent()) {  // user already exists
  ....
}
User user = selectUser.get()
....


この堎合、プログラムの実行は条件付きステヌトメントの本䜓には入りたせん。そしお、すべおが倧䞈倫になりたす。しかし、疑問が生じたす。実行されないのに、なぜ耇雑なロゞックを持぀条件付き挔算子が必芁なのですか



しかし、コメントが䜕もない堎合はどうなりたすか



....
if (!selectUser.isPresent()) {  // user already exists
    selectUser = UserManager.instance.getUserByName(userName);
    if (selectUser.isPresent()) {
      ....
    }
}
User user = selectUser.get(); // <=
....


次に、実行は条件ステヌトメントの本䜓に入り、getUserByNameを介しおナヌザヌを再取埗したす。ナヌザヌの有効性が再床チェックされたす。これは、selectUserが初期化されおいない可胜性があるこずを瀺しおいたす。この堎合、他にブランチはありたせん。これにより、問題のコヌド行でNoSuchElementExceptionが発生したす。



譊告N8



V6042匏はタむプ「A」ずの互換性がチェックされたすが、タむプ「B」にキャストされたす。 CheckBoxList.java586



/**
 * sets the model - must be an instance of CheckBoxListModel
 * 
 * @param model the model to use
 * @throws IllegalArgumentException if the model is not an instance of
 *           CheckBoxListModel
 * @see CheckBoxListModel
 */
@Override
public void setModel(ListModel model) {
  if (!(model instanceof CheckBoxListModel)) {
    if (model instanceof javax.swing.DefaultListModel) {
       super.setModel((CheckBoxListModel)model);         // <=
    }
    else {
      throw new IllegalArgumentException(
          "Model must be an instance of CheckBoxListModel!");
    }
  }
  else {
    super.setModel(model);
  }
}


コヌドの䜜成者は、ここで䜕かに぀いお混乱しおいたす。最初に、モデルがCheckBoxListModelでないこずを確認し、その結果、オブゞェクトをこのタむプに明瀺的にキャストしたす。このため、setModelメ゜ッドは、そこに到達するずすぐにClassCastExceptionをスロヌしたす。CheckBoxList.java



ファむルは2幎前に远加され、このバグはそれ以来コヌドに残っおいたす。どうやら、間違ったパラメヌタのテストはなく、䞍適切なタむプのオブゞェクトでこのメ゜ッドを実際に䜿甚するこずはないので、それは生きおいたす。 突然誰かがこのメ゜ッドに接続しおJavadocを読み取るず、ClassCastExceptionではなくIllegalArgumentExceptionが発生するこずが予想されたす。



..。意図的にこの䟋倖に遭遇する人はいないず思いたすが、誰が知っおいたすか。



ドキュメントを考えるず、コヌドはおそらく次のようになりたす。



public void setModel(ListModel model) {
  if (!(model instanceof CheckBoxListModel)) {
     throw new IllegalArgumentException(
        "Model must be an instance of CheckBoxListModel!");  
  }
  else {
    super.setModel(model);
  }
}


譊告N9



V6060「プレヌダヌ」参照は、nullに察しお怜蚌される前に䜿甚されたした。VigeanIntuition.java79、VigeanIntuition.java78​​



@Override
public boolean apply(Game game, Ability source) {
    MageObject sourceObject = game.getObject(source.getSourceId());
    Player player = game.getPlayer(source.getControllerId());
    Library library = player.getLibrary();                           // <=
    if (player != null && sourceObject != null && library != null) { // <=
        ....
    }
}


V6060は、オブゞェクトがnullであるかどうかがチェックされる前に、オブゞェクトがアクセスされおいるこずを開発者に譊告したす。このルヌルのトリガヌは、オヌプン゜ヌスプロゞェクトのチェックに関する蚘事によく芋られたす。通垞、この理由は、リファクタリングの倱敗たたはメ゜ッドの契玄の倉曎です。getPlayerメ゜ッドの宣蚀に泚意を払うず、すべおがすぐに実行されたす。



// Result must be checked for null.
// Possible errors search pattern: (\S*) = game.getPlayer.+\n(?!.+\1 != null)
Player getPlayer(UUID playerId);


譊告N10



V60722぀の類䌌したコヌドフラグメントが芋぀かりたした。おそらく、これはタむプミスであり、「playerA」の代わりに「playerB」倉数を䜿甚する必芁がありたす。 SubTypeChangingEffectsTest.java162、SubTypeChangingEffectsTest.java158、SubTypeChangingEffectsTest.java156、SubTypeChangingEffectsTest.java160



@Test
public void testArcaneAdaptationGiveType() {
    addCard(Zone.HAND, playerA, "Arcane Adaptation", 1); // Enchantment {2}{U}
    addCard(Zone.BATTLEFIELD, playerA, "Island", 3);

    addCard(Zone.HAND, playerA, "Silvercoat Lion");
    addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
    addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion");   // <=

    addCard(Zone.HAND, playerB, "Silvercoat Lion");
    addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
    addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion");   // <=

    ....

    for (Card card : playerB.getGraveyard().getCards(currentGame)) {
        if (card.isCreature()) {
            Assert.assertEquals(card.getName() + " should not have ORC type",
                    false, card.getSubtype(currentGame).contains(SubType.ORC));
            Assert.assertEquals(card.getName() + " should have CAT type",
                    true, card.getSubtype(currentGame).contains(SubType.CAT));
        }
    }
}


この゚ラヌがテストにあるこずを確認したら、「たあ、これらはテストです」ず考えお、芋぀かった欠陥の䟡倀をすぐに䞋げるこずができたす。もしそうなら、私はあなたに同意したせん。結局のずころ、テストは開発においおかなり重芁な圹割を果たしプログラミングほど目立たないものの、リリヌスに欠陥が珟れるず、すぐにテスト/テスタヌに​​指を向け始めたす。したがっお、欠陥のあるテストは受け入れられたせん。では、なぜそのようなテストが必芁なのですかなぜそれらにリ゜ヌスを浪費するのですかtestArcaneAdaptationGiveType



メ゜ッドは、「ArcaneAdaptation」カヌドをテストしたす。各プレむダヌには、特定のプレむ゚リアにカヌドが配られたす。そしお、コピヌペヌストのおかげで、playerAは「Cemetery」プレむ゚リアで2枚の同䞀の「SilvercoatLion」カヌドを手に入れたした。だから䜕も埗られなかった。次に、いく぀かの魔法ずそれ自䜓をテストしたす。珟圚の図面のplayerBの



「墓地」にテストが来るず、「墓地」には䜕もなかったため、テストの実行がルヌプに入るこずはありたせん。これは、テストを開始したずきに叀き良きSystem.out.printlnで芋぀けたした。 修正されたコピヌペヌスト







....
addCard(Zone.HAND, playerA, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion");   // <=

addCard(Zone.HAND, playerB, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
addCard(Zone.GRAVEYARD, playerB, "Silvercoat Lion");   // <=
....


コヌドを埮調敎した埌、テストを実行するず、playerBの墓地にいるクリヌチャヌのチェックが機胜し始めたした。アベニュヌ、System.out.println



修正前ず修正埌の䞡方でテストが緑色になり、幞運です。しかし、プログラム実行のロゞックを倉曎する線集の堎合、そのようなテストはあなたに䞍利益をもたらし、゚ラヌがあったずしおも正垞に完了したこずを通知したす。



同じコピヌ-他の堎所に貌り付けたす



  • V60722぀の類䌌したコヌドフラグメントが芋぀かりたした。おそらく、これはタむプミスであり、「playerA」の代わりに「playerB」倉数を䜿甚する必芁がありたす。PaintersServantTest.java33、PaintersServantTest.java29、PaintersServantTest.java27、PaintersServantTest.java31
  • V60722぀の類䌌したコヌドフラグメントが芋぀かりたした。おそらく、これはタむプミスであり、「playerA」の代わりに「playerB」倉数を䜿甚する必芁がありたす。SubTypeChangingEffectsTest.java32、SubTypeChangingEffectsTest.java28、SubTypeChangingEffectsTest.java26、SubTypeChangingEffectsTest.java30


譊告N11



V6086疑わしいコヌドのフォヌマット。'else'キヌワヌドが欠萜しおいる可胜性がありたす。DeckImporter.java23



public static DeckImporter getDeckImporter(String file) {
  if (file == null) {
    return null;
  } if (file.toLowerCase(Locale.ENGLISH).endsWith("dec")) {   // <=
    return new DecDeckImporter();
  } else if (file.toLowerCase(Locale.ENGLISH).endsWith("mwdeck")) {
    return new MWSDeckImporter();
  } else if (file.toLowerCase(Locale.ENGLISH).endsWith("txt")) {
    return new TxtDeckImporter(haveSideboardSection(file));
  }
  ....
  else {
    return null;
  }
}


蚺断ルヌルV6086は、誀ったif-else-ifフォヌマットを蚺断し、elseの省略を意味したす。



このコヌドスニペットはこれを瀺しおいたす。この堎合、null匏が返されるため、フォヌマットが䞍正確であっおも䜕も起こりたせんが、そのようなケヌスを芋぀ける必芁はないので、芋぀けるのはすばらしいこずです。elseを



省略するず、予期しない動䜜が発生する可胜性がある堎合を考えおみたしょう。



public SomeType smtMethod(SomeType obj) {
  ....
  if (obj == null) {
    obj = getNewObject();
  } if (obj.isSomeObject()) {
    // some logic
  } else if (obj.isOtherSomething()) {
    obj = calulateNewObject(obj);
    // some logic
  } 
  ....
  else {
    // some logic
  }
  return obj;
}


ここで、obj == nullの堎合、問題のオブゞェクトには䜕らかの倀が割り圓おられ、elseが欠萜しおいるず、新しく割り圓おられたオブゞェクトがif-else-ifチェヌンに沿っおチェックされ始めたすが、オブゞェクトはすぐに方法。



結論



XMageのチェックは、最新の静的アナラむザヌの機胜を明らかにする別の蚘事です。珟代の開発では、゜フトりェアの耇雑さが増すに぀れお、それらの必芁性は高たるだけです。たた、リリヌス、テスト、ナヌザヌフィヌドバックの数に関係なく、バグは垞にコヌドベヌスに䟵入するための抜け穎を芋぀けたす。では、防埡に別の障壁を远加しおみたせんか



ご存知のように、アナラむザヌは誀怜知を起こしやすい傟向がありたすPVS-Studio Javaを含む。これは、明らかな欠陥ず混乱しすぎるコヌドの䞡方の結果である可胜性がありたすああ、アナラむザヌはそれを理解したせんでした。あなたはそれらを理解しお扱い、ためらうこずなくすぐに退䌚する必芁がありたすが、誀怜知がそれらの修正を埅っおいる間、あなたはいずれかの方法を䜿甚するこずができたす譊告を抑制したす。



結論ずしお、私たちのりェブサむトからアナラむザヌをダりンロヌドしお、個人的にアナラむザヌに「觊れる」こずをお勧めしたす。





この蚘事を英語を話す聎衆ず共有したい堎合は、翻蚳リンクを䜿甚しおくださいマキシムステファノフ。XMageのコヌドを確認し、ドラゎンの迷路コレクションの特別なレアカヌドを入手できない理由。



All Articles