2025年を通して、PVSスタジオのチームはオープンソースのC#プロジェクトを積極的にチェックしてきました。一年を通して、我々は多くの欠点を発見しました。だから、我々はこの巨大な多様性から最も興味深い10個を選びました。 トップをどのようにまとめましたか? There are several criteria the project code should meet to earn a place in our top list: オープンソースのプロジェクトから来ています。 問題はPVS-Studioによって検出されました。 コードには間違いが含まれている可能性があります。 the code is interesting to check; それぞれのミスはユニークです。 私たちは定期的にそのようなリストをまとめているので、興味深いエラーの印象的なコレクションを集めました。 2024年のトップ10のエラー 2023年のトップ10のミス 2022年のトップ10のミス ; top 10 errors in 2021 2020年のトップ10のミス 2019年のトップ10のミス さて、2025年のC#エラーの魅力的な深淵に潜り込もう! P.S. この記事の著者は、自分の主観的な意見に基づいてエラーを選択し、グループ化しました. あなたがこのバグやそのバグが別の場所に値すると思うなら、コメントを残すことを自由に感じてください :) 第10話 見つけてみよう 今日のトップは、上記のエラーから始まります。 .NET 9 がリリースされたばかりのようですが、わずか 1 か月前に .NET 10 がそれを置き換えました。 . article about checking .NET 9 この記事 分析に戻ろう: public static void SetAsIConvertible(this ref ComVariant variant, IConvertible value) { TypeCode tc = value.GetTypeCode(); CultureInfo ci = CultureInfo.CurrentCulture; switch (tc) { case TypeCode.Empty: break; case TypeCode.Object: variant = ComVariant.CreateRaw(....); break; case TypeCode.DBNull: variant = ComVariant.Null; break; case TypeCode.Boolean: variant = ComVariant.Create<bool>(....)); break; case TypeCode.Char: variant = ComVariant.Create<ushort>(value.ToChar(ci)); break; case TypeCode.SByte: variant = ComVariant.Create<sbyte>(value.ToSByte(ci)); break; case TypeCode.Byte: variant = ComVariant.Create<byte>(value.ToByte(ci)); break; case TypeCode.Int16: variant = ComVariant.Create(value.ToInt16(ci)); break; case TypeCode.UInt16: variant = ComVariant.Create(value.ToUInt16(ci)); break; case TypeCode.Int32: variant = ComVariant.Create(value.ToInt32(ci)); break; case TypeCode.UInt32: variant = ComVariant.Create(value.ToUInt32(ci)); break; case TypeCode.Int64: variant = ComVariant.Create(value.ToInt64(ci)); break; case TypeCode.UInt64: variant = ComVariant.Create(value.ToInt64(ci)); break; case TypeCode.Single: variant = ComVariant.Create(value.ToSingle(ci)); break; case TypeCode.Double: variant = ComVariant.Create(value.ToDouble(ci)); break; case TypeCode.Decimal: variant = ComVariant.Create(value.ToDecimal(ci)); break; case TypeCode.DateTime: variant = ComVariant.Create(value.ToDateTime(ci)); break; case TypeCode.String: variant = ComVariant.Create(....); break; default: throw new NotSupportedException(); } } 全画面モード 全画面モード 全画面モード 全画面モード 問題は見えますか?確実にそこにあります! case TypeCode.Int64: variant = ComVariant.Create(value.ToInt64(ci)); break; case TypeCode.UInt64: variant = ComVariant.Create(value.ToInt64(ci)); break; // <= 全画面モード 全画面モード 全画面モード 全画面モード PVSスタジオ警告: Two or more case-branches perform the same actions. ダイナミックVariantExtensions.cs 68 ワイ3139 I hope you have given your eyes a good workout, and your keen sight has not let you down. あなたはあなたの目に良いトレーニングを与え、あなたの鋭い視力はあなたを落とさないことを願っています。 代わりに 開発者は既存の method. This may be a copy-paste error. case TypeCode.UInt64 value.ToInt64 ToUInt64() 第9位:不適切な形式 第9の場所は、記述されたエラーにあります。 : NeoとNBitcoinのプロジェクトのチェックについての記事 public override string ToString() { var sb = new StringBuilder(); sb.AppendFormat("{1:X04} {2,-10}{3}{4}", Position, OpCode, DecodeOperand()); return sb.ToString(); } 全画面モード 全画面モード 全画面モード 全画面モード PVS-Studio warning: [CWE-685] Format incorrect. A different number of format items is expected while calling 'AppendFormat' function. Format items not used: {3}, {4}. Arguments not used: 1st. VMInstruction.cs 105 V3025 オーバーライドを呼ぶ 例外の例外が原因で、これは誤った例外です。 2 エラーを含む電話 ToString sb.AppendFormat 挿入するアルゴリズムの数は、フォーマット文字列の位置保持者数より小さいため、例外が発生します。 Even if we fix the first issue by matching the number of arguments and placeholders, the call will still throw the exception. This is because placeholder indexing starts at 0, not 1. This means the fifth argument is required for the placeholder with index 4, which is absent. 第8話 見どころ 次のエラーは、 : Lean Trading Engineのテストについて public override int GetHashCode() { unchecked { var hashCode = Definition.GetHashCode(); var arr = new int[Legs.Count]; for (int i = 0; i < Legs.Count; i++) { arr[i] = Legs[i].GetHashCode(); } Array.Sort(arr); for (int i = 0; i < arr.Length; i++) { hashCode = (hashCode * 397) ^ arr[i]; } return hashCode; } } public override bool Equals(object obj) { .... return Equals((OptionStrategyDefinitionMatch) obj); } 全画面モード 全画面モード 全画面モード 全画面モード PVSスタジオ警告: 「Legs」属性は「GetHashCode」メソッドで使用されますが、「Equals」メソッドでは欠けています。 V3192 アナリストは、A チェック on the オーバーレードと呼ばれる方法 , and found that it does not use the 財産とはいえ、 それに頼る。 インタープロセス Equals Equals Legs GetHashCode では、もっと詳しく見ていきましょうThe method: Equals public bool Equals(OptionStrategyDefinitionMatch other) { .... var positions = other.Legs .ToDictionary(leg => leg.Position, leg => leg.Multiplier); foreach (var leg in other.Legs) // <= { int multiplier; if (!positions.TryGetValue(leg.Position, out multiplier)) { return false; } if (leg.Multiplier != multiplier) { return false; } } return true; } 全画面モード 全画面モード 全画面モード 全画面モード メソッドは iterates over そのコレクションの各要素に対して、コードは、そのコレクションの中にそれを検索しようとします。 「辞書」でもあるが、この辞書は その結果、コードは、コレクションの要素が同じコレクションに存在するかどうかを確認します。 other.Legs positions other.Legs コードを置き換えることで修正できます。 同 マークされた場所で other.Legs Legs 第7位 トリッキー・イヴァルス 第7話 エラーから出る : スコットプレットのチェックについて public class CoordinateRangeMutable : IEquatable<CoordinateRangeMutable> { .... public bool Equals(CoordinateRangeMutable? other) { if (other is null) return false; return Equals(Min, other.Min) && Equals(Min, other.Min); // <= } public override bool Equals(object? obj) { if (obj is null) return false; if (obj is CoordinateRangeMutable other) return Equals(other); return false; } public override int GetHashCode() { return Min.GetHashCode() ^ Max.GetHashCode(); // <= } } Enter fullscreen mode Exit fullscreen mode PVS-Studio warnings: 「Max」属性は「GetHashCode」メソッドで使用されますが、「Equals」メソッドでは欠けています。 V3192 「Equals(Min, other.Min)」は「&&」オペレーターの左側と右側に同一のサブ表現があります。 V3001 アナリストはこのコードの部分について2つの警告を発行しました。 We'll start with the アナリストの警告によると、 不動産が使われているのは、 method but not in the トップページ > If we look at the overridden この方法は、もう一つ見ることができます。 体内で呼ばれていますので、以下のとおりご覧いただけます。 . The diagnostic rule highlighted this fragment. V3192 Max GetHashCode Equals Equals Equals Equals(Min, other.Min) && Equals(Min, other.Min) V3001 明らかに、一つの オペレーターは、The 形式です。 && Equals(Max, other.Max) アナリストは正しい── 現れていない in the method. Max Equals 第6話 トリック 前回と同様に、前回と同様に、 トップの最初の半分を締めくくります: ScottPlot をチェックする記事 public static Interactivity.Key GetKey(this Keys keys) { Keys keyCode = keys & ~Keys.Modifiers; // <= Interactivity.Key key = keyCode switch { Keys.Alt => Interactivity.StandardKeys.Alt, // <= Keys.Menu => Interactivity.StandardKeys.Alt, Keys.Shift => Interactivity.StandardKeys.Shift, // <= Keys.ShiftKey => Interactivity.StandardKeys.Shift, Keys.LShiftKey => Interactivity.StandardKeys.Shift, Keys.RShiftKey => Interactivity.StandardKeys.Shift, Keys.Control => Interactivity.StandardKeys.Control, // <= Keys.ControlKey => Interactivity.StandardKeys.Control, Keys.Down => Interactivity.StandardKeys.Down, Keys.Up => Interactivity.StandardKeys.Up, Keys.Left => Interactivity.StandardKeys.Left, Keys.Right => Interactivity.StandardKeys.Right, _ => Interactivity.StandardKeys.Unknown, }; .... } 全画面モード 全画面モード 全画面モード 全画面モード PVSスタジオ警告: アクセス不可能なコードが検出されました。「ケース」値は、マッチ表現の範囲外です。 ScottPlot.WinForms FormsPlotExtensions.cs 106 ワイ3202 内部の複数のパターン値 今の状況では、どうなっているのか、見ていきましょう。 switch まず、リストの誤った要素に対応する値を見るべきです。 [Flags] [TypeConverter(typeof(KeysConverter))] [Editor(....)] public enum Keys { /// <summary> /// The bit mask to extract modifiers from a key value. /// </summary> Modifiers = unchecked((int)0xFFFF0000), .... /// <summary> /// The SHIFT modifier key. /// </summary> Shift = 0x00010000, /// <summary> /// The CTRL modifier key. /// </summary> Control = 0x00020000, /// <summary> /// The ALT modifier key. /// </summary> Alt = 0x00040000 } 全画面モード 全画面モード 全画面モード 全画面モード 次に、それらをバイナリに変換しましょう: It's clear now that 間違ったリストの要素を含む。 Modifiers 価値が渡った から得られたもの。 expression. This expression excludes the Value from さらに、 , however, , そして、 除外されることもあるので、 これらの値はすでに含まれています( 間違った列挙要素のすべての非ゼロビットに対して非ゼロビットを有する)。 switch keys & ~Keys.Modifiers Keys.Modifiers keys Keys.Modifiers Shift Control Alt Modifiers Modifiers これらのすべてから、生成するビットの組み合わせは、 で、 あるいは for the 手術は存在しない。 Shift Control Alt keys & ~Keys.Modifiers 問題はその中にあるのかもしれない。 数値ではなく実装値です。 switch 第5話 全員ボクシング トップ5は、あるエラーで始まる。 : .NET 9 のチェックについて struct StackValue { .... public override bool Equals(object obj) { if (Object.ReferenceEquals(this, obj)) return true; if (!(obj is StackValue)) return false; var value = (StackValue)obj; return this.Kind == value.Kind && this.Flags == value.Flags && this.Type == value.Type; } } 全画面モード 全画面モード 全画面モード 全画面モード PVS-Studio warning: 値型の変数を「ReferenceEquals」と比較するのは、「this」がボックスされていないため間違っている。 ワンワン3161 THE 方法は、Parameters of the type. When a value type is passed, it gets ヒープで作成された参照は、他のいかなる参照にも一致しません。 ReferenceEquals Object ボクサー 以来 最初の議論として通過されると、ボクシングは毎回起こります。 method is called. So, checking via 常に戻る . this Equals ReferenceEquals false この問題は方法の動作に影響を与えないことに注意してください. However, the check using the メソッドは、参照が等しかった場合のさらなる比較を避けるために実施されました。言い換えれば、これは一種の最適化です。 ReferenceEquals コードは、チェックの後に常に実行されます。 「Equals」への呼び出しはボクシング作戦につながります。 面白いことに.NET に組み込まれたアナライザー(ルール)は )もこの問題を検出しますが、 .NET の開発者自身はそれを避けることができませんでした :) CA2013 もしかしたら、このプロジェクトでこのルールが無効になったのかもしれないが、 デフォルトでは .NET 5 から始まります。 2013年 第4話 未登録 第4話 エラーから出る : article about checking MSBuild private static void SubscribeImmutablePathsInitialized() { NotifyOnScopingReadiness?.Invoke(); FileClassifier.Shared.OnImmutablePathsInitialized -= () => NotifyOnScopingReadiness?.Invoke(); } 全画面モード 全画面モード 全画面モード 全画面モード PVSスタジオ警告: . Anonymous 関数は「OnImmutablePathsInitialized」イベントからサブスクリプションをキャンセルするために使用されます. No handlers will be unsubscribed, as a separate delegate instance is created for each anonymous function declaration. CheckScopeClassifier.cs 67 ワイ3084 この場合、代理人からサブスクリプションをキャンセルすることは、匿名機能が宣言されるたびに、新しい代理インスタンスが作成されるため、効果が発生しません。 is signed to but unsubscribes from 効果がないもの。 OnImmutablePathsInitialized delegate 1 delegate 2 第3位 オペレーター優先度の混乱 トップ > トップ > トップ > トップ > ミス 名誉ある第3位を獲得した。 NeoとNBitcoinのプロジェクトのチェックについての記事 public override int Size => base.Size + ChangeViewMessages?.Values.GetVarSize() ?? 0 + 1 + PrepareRequestMessage?.Size ?? 0 + PreparationHash?.Size ?? 0 + PreparationMessages?.Values.GetVarSize() ?? 0 + CommitMessages?.Values.GetVarSize() ?? 0; Enter fullscreen mode Exit fullscreen mode PVSスタジオ警告: [CWE-783] Perhaps the '??' operator works in a different way than it was expected. Its priority is lower than priority of other operators in its left part. RecoveryMessage.cs 35 ワイ3123 アナリストは、いくつかの このコードのための警告ですが、私は短縮性のために1つだけを含んでいます。 オペレーターの優先順位は、 operator. However, the formatting of this expression suggests developers expected the opposite. ワイ3123 ?? + 操作の順序はここで重要ですか? 質問に答えるには、サブ表現の追加の例を見てみましょう。 は : ChangeViewMessages null base.Size + ChangeViewMessages?.Values.GetVarSize() ?? 0 全画面モード 全画面モード 全画面モード 全画面モード にかかわらず、 値, sub-expression result is always 0 because adding 2位 結果 in . base.Size base.Size null null もしわたしたちが場所 in parentheses, changing the operation order, the result becomes . ChangeViewMessages?.Values.GetVarSize() ?? 0 base.Size 2nd place. The treacherous pattern 二つ目は、エラーの1つです。 : ファイルマネージャーのチェックについて protected void ChangeMode(OmnibarMode? oldMode, OmnibarMode newMode) { .... var modeSeparatorWidth = itemCount is not 0 or 1 ? _modesHostGrid.Children[1] is FrameworkElement frameworkElement ? frameworkElement.ActualWidth : 0 : 0; .... } 全画面モード 全画面モード 全画面モード 全画面モード PVS-Studio warning: [CWE-670] 'not 0 or 1' 論理パターンは期待通りに動作しない場合があります. 'not' パターンは、'or' パターンの最初の表現にのみ匹敵します. Files.App.Controls Omnibar.cs 149 ワイ3207 では、もっと詳しく見ていきましょうThe すでに問題は何なのかを推測しましたか? このパターンは余計です。その2番目の部分は何の影響もありません。 itemCount is not 0 or 1 「x が 0 または 1 でない」と言うとき、人々は通常、x が 0 または 1 でないことを暗示します。 実は意味 こうした過ちは、解雇だけでなく、 この問題は、Aにおいても議論された。 議論に基づいて、コンパイラは将来この問題を捕らえる可能性があるか、または組み込みの静的分析はそれを警告として標識します。 x is not 0 or 1 x is (not 0) or 1 NullReferenceException: list is not null or list.Count == 0 会議 1st place. How does LINQ work? 優勝は誤りである。 . This error ranks first due to its subtlety. Some developers may not consider the effects of using deferred execution methods in combination with captured variables. All the details are below: Lean Trading Engineについての記事一覧 public void FutureMarginModel_MarginEntriesValid(string market) { .... var lineNumber = 0; var errorMessageTemplate = $"Error encountered in file " + $"{marginFile.Name} on line "; var csv = File.ReadLines(marginFile.FullName) .Where(x => !x.StartsWithInvariant("#") && !string.IsNullOrWhiteSpace(x)) .Skip(1) .Select(x => { lineNumber++; // <= .... }); lineNumber = 0; // <= foreach (var line in csv) { lineNumber++; // <= .... } } 全画面モード 全画面モード 全画面モード 全画面モード PVSスタジオ警告: 「lineNumber」変数は、延期実行のLINQメソッドでキャプチャした後に変更されました. メソッドが実行されたときに元の値は使用されません. FutureMarginBuyingPowerModelTests.cs 720 ワイ 3219 THE 変数は、LINQ メソッドに転送される代表者に捕捉され、増加します。 is a deferred method, the delegate code runs while iterating over the resulting collection, not when 呼ばれる。 lineNumber Select Select While iterating over the コレクション、The variable is also incremented. As a result, each iteration increases : Delegate runs and inside the 奇妙に見えるもの。 csv lineNumber lineNumber by 2 foreach NOTE THE 任命前 開発者は、この変数がループの前に非ゼロ値を保持することができると予想していた可能性がありますが、それは不可能です。 ゼロから始まり、それを変える唯一の場所は、 上記のように、代表者はイテレーション中に走り、それ以前ではなく、開発者たちは、代表者がループに入る前に実行することを期待していたようだ。 lineNumber = 0 foreach lineNumber foreach 結論 私たちは、著者の視点から最も興味深い警告を通過しました:) このコレクションは、コード開発の面で興味深く、挑発的なものだと思います。 プロジェクトに類似の問題があるかどうかを確認したい場合は、静的アナライザーを使用する時です。 . ダウンロードリンク