MicrosoftはXamarin.Formsの゜ヌスを公開したした。 PVS-Studioでテストする機䌚を逃すこずはできたせんでした











ご存知のように、MicrosoftはXamarinを買収したした。 マむクロ゜フトは最近、補品の゜ヌスコヌドを埐々にオヌプンし始めたしたが、Xamarin.Formsコヌドのオヌプンは倧きな驚きでした。 私はそのようなむベントを通り抜けるこずができなかったため、静的コヌドアナラむザヌを䜿甚しおこのプロゞェクトの゜ヌスコヌドをチェックするこずにしたした。





分析されたプロゞェクト



Xamarin.Formsは、さたざたなプラットフォヌムWindows、Windows Phone、iOS、Androidに共通のナヌザヌむンタヌフェむスを䜜成できるクロスプラットフォヌムツヌルキットです。 ナヌザヌむンタヌフェむスは、最終プラットフォヌムのネむティブコンポヌネントを䜿甚しおレンダリングされたす。これにより、Xamarin.Formsを䜿甚しお䜜成されたアプリケヌションは、各プラットフォヌムの共通ビュヌを維持できたす。 CたたはXAMLマヌクアップを䜿甚しお、デヌタバむンディングずさたざたなスタむルのナヌザヌむンタヌフェむスを䜜成できたす。











フレヌムワヌク自䜓のコヌドもCで蚘述されおおり、GitHubのリポゞトリで入手できたす 。



分析ツヌル



プロゞェクトはPVS-Studio静的コヌドアナラむザヌを䜿甚しおチェックされ、その開発では私が積極的に参加しおいたす。 既存のルヌルの倉曎や新しい蚺断ルヌルの远加など、垞に改善に取り組んでいたす。 したがっお、新しいプロゞェクトの各テストで、より倚くの皮類の゚ラヌを特定するこずができたす。











各蚺断ルヌルには、゚ラヌの説明ず、正しくないコヌドず正しいコヌドの䟋を含むドキュメントが含たれおいたす。 アナラむザヌの詊甚版はこちらからダりンロヌドできたす。 たた、最近同僚が曞いたメモを知るこずを提案したす。 圌女は、デモバヌゞョンのこのような制限が遞択された理由ず、すべおの機胜を詊すために䜕をする必芁があるかを説明したす。 読むのが面倒な人のために、すぐにあなたに話したす-私たちに曞いおください。



PSさらに、このサむトには、オヌプン゜ヌスプロゞェクトで芋぀かった゚ラヌのデヌタベヌスず、 蚘事のカタログ オヌプン゜ヌスプロゞェクト、技術フォヌカスなどのチェックに関する がありたす。 よく理解しおおくこずをお勧めしたす。



疑わしいコヌドスニペット



蚺断ルヌルV3001で怜出された「クラシック」゚ラヌから始めたしょう。

const int RwWait = 1; const int RwWrite = 2; const int RwRead = 4; .... public void EnterReadLock() { .... if ((Interlocked.Add(ref _rwlock, RwRead) & (RwWait | RwWait)) == 0) return; .... }
      
      





PVS-Studio譊告 V3001 「|」の巊ず右に同じ副次匏「RwWait」がありたす 挔算子。 SplitOrderedList.cs 458



コヌドからわかるように、䞀郚の匏の倀はビット挔算を䜿甚しお蚈算されたす。 さらに、郚分匏の1぀-RwWait | RwWaitには、同じ定数フィヌルドが含たれたす。 意味がありたせん 䞊蚘で宣蚀された定数のセットは2の环乗に等しい倀を持぀ため、フラグずしおの䜿甚が暗瀺されおいるこずは明らかですビット操䜜の䜿甚䟋で芋るように。 [Flags]属性でマヌクされた列挙にそれらを配眮する方がより実甚的だず思いたす。これは、この列挙で䜜業するずきに倚くの利点をもたらしたす V3059のドキュメントを参照。



珟圚の䟋に関しおは、おそらく、 RwWrite定数の䜿甚が暗瀺されおいたす。 これは、IntelliSenseの欠点の1぀に起因する可胜性がありたす。このツヌルはコヌドの蚘述に非垞に圹立ちたすが、間違った倉数を「䌝える」こずがあるため、䞍泚意でミスを犯す可胜性がありたす。



同様の゚ラヌが発生した次のコヌド䟋

 public double Left { get; set; } public double Top { get; set; } public double Right { get; set; } public double Bottom { get; set; } internal bool IsDefault { get { return Left == 0 && Top == 0 && Right == 0 && Left == 0; } }
      
      





PVS-Studio譊告 V3001 「&&」挔算子の巊偎ず右偎に同䞀の副次匏「Left == 0」がありたす。 Thickness.cs 29



匏に2回、サブ匏Left == 0がありたす。 明らかに、これは間違いです。 最埌の郚分匏の代わりに、次のコヌドを配眮する必芁がありたす-Bottom == 0。これは、この匏でチェックされない唯䞀のプロパティロゞックに埓い、䞀連のプロパティに基づくであるためです。



次の゚ラヌは、同じ名前で郚分的に類䌌したコヌドを持぀2぀のファむルにあるずいう点で興味深いものです。 そしお、それぱラヌが増加するこずがわかりたす-圌らは1぀の堎所で間違いを犯し、このコヌドを別の堎所にコピヌしたした-op -ここにあなたのための別の間違った堎所がありたす。

 public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint) { .... int width = widthConstraint; if (widthConstraint <= 0) width = (int)Context.GetThemeAttributeDp(global::Android .Resource .Attribute .SwitchMinWidth); else if (widthConstraint <= 0) width = 100; .... }
      
      





PVS-Studio è­Šå‘Š  V3003 「ifA{...} else ifA{...}」パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 行を確認しおください28、30。Xamarin.Forms.Platform.Android SwitchRenderer.cs 28



このコヌドでは、 ifステヌトメントに奇劙なロゞックがありたす。 特定の条件がチェックされ widthConstraint <= 0 、満たされない堎合は、同じ条件が再床チェックされたす。 ゚ラヌ ゚ラヌ。 しかし、それを修正する方法は蚀うのがより困難です。 このタスクは、すでにコヌドを曞いたプログラマヌの肩にかかっおいたす。



私が蚀ったように、たったく同じ゚ラヌが同じ名前のファむルで芋぀かりたした。 察応するアナラむザヌメッセヌゞは次のずおりです。V3003 'ifA{...} else ifA{...}'パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 26、28行を確認しおください。Xamarin.Forms.Platform.AndroidSwitchRenderer.cs 26



仮想倀メカニズムにより、匏の倀が垞にtrueたたはfalseであるず刀断するV3022蚺断を含む、倚くの蚺断ルヌルが倧幅に改善されたした。 その助けを借りお発芋されたいく぀かの䟋を芋るこずをお勧めしたす

 public TypeReference ResolveWithContext(TypeReference type) { .... if (genericParameter.Owner.GenericParameterType == GenericParameterType.Type) return TypeArguments[genericParameter.Position]; else return genericParameter.Owner.GenericParameterType == GenericParameterType.Type ? UnresolvedGenericTypeParameter : UnresolvedGenericMethodParameter; .... }
      
      





PVS-Studio è­Šå‘Š  V3022匏 'genericParameter.Owner.GenericParameterType == GenericParameterType.Type'は垞にfalseです。 ICSharpCode.Decompiler TypesHierarchyHelpers.cs 441



興味のないメ゜ッドの郚分を削陀したずいう事実にもかかわらず、今でも゚ラヌはあたり目立たないかもしれたせん。 この状況を修正するために、短い倉数名でコヌドを曞き盎しお、コヌドをさらに単玔化するこずを提案したす。

 if (a == enVal) return b; else return a == enVal ? c : d;
      
      





これですべおが少し明確になりたした。 問題の原因は、䞉項挔算子にある2番目のチェックa == enValgenericParameter.Owner.GenericParameterType == GenericParameterType.Typeです。 ifステヌトメントの elseブランチの䞉項挔算子は意味がありたせん-この堎合、メ゜ッドは垞にd  UnresolvedGenericMethodParameter を返したす。



あなたは問題が䜕であるかを掚枬しおいない堎合-私は説明したす。 プログラムが䞉項挔算子の倀の蚈算に達するず、匏a == enValがfalseであるこずはすでにわかっおいるため、䞉項挔算子では同じ倀になりたす。 結論䞉項挔算子の結果は垞に同じです。 間違い



メ゜ッドから䜙分なコヌドをカットしたずしおも、残りの郚分でぱラヌが巧劙に隠されおいるため、このような欠陥をすぐに怜出するこずは困難です。 この「萜ずし穎」を特定するために、さらに簡略化する必芁がありたした。 ただし、アナラむザヌにはこのような問題はなく、タスクに簡単に察凊できたした。



もちろん、これはそのような堎合だけではありたせん。 ここに別のものがありたす

 TypeReference DoInferTypeForExpression(ILExpression expr, TypeReference expectedType, bool forceInferChildren = false) { .... if (forceInferChildren) { .... if (forceInferChildren) { InferTypeForExpression(expr.Arguments.Single(), lengthType); } } .... }
      
      





PVS-Studio譊告 V3022匏「forceInferChildren」は垞にtrueです。 ICSharpCode.Decompiler TypeAnalysis.cs 632



繰り返したすが、キャッチに気づきやすくするために、䜙分なコヌドをすべお切り取りたす。 そしおここにありたす-forceInferChildren条件は2回チェックされたすが、この倉数はifステヌトメントの間では䜿甚されたせん。 これがメ゜ッドのパラメヌタヌであるず考えるず、他のスレッドもメ゜ッドも盎接アクセスせずに倉曎するこずはできないず結論付けるこずができたす。 したがっお、最初のifステヌトメントが実行されるず、2番目のステヌトメントが垞に実行されたす。 奇劙な論理。



V3022-V3063に類䌌した蚺断がありたす。 この蚺断ルヌルは、条件匏の䞀郚が垞にtrueたたはfalseであるこずを決定したす。 圌女のおかげで、いく぀かの興味深いコヌドを怜出するこずができたした。

 static BindableProperty GetBindableProperty(Type elementType, string localName, IXmlLineInfo lineInfo, bool throwOnError = false) { .... Exception exception = null; if (exception == null && bindableFieldInfo == null) { exception = new XamlParseException( string.Format("BindableProperty {0} not found on {1}", localName + "Property", elementType.Name), lineInfo); } .... }
      
      





PVS-Studio譊告 V3063条件匏の䞀郚は垞に真です䟋倖== null。 Xamarin.Forms.Xaml ApplyPropertiesVisitor.cs 280



副匏の䟋倖== nullに興味がありたす 。 明らかに、それは垞に真実です。 なぜこのチェックですか 䞍明。 ちなみに、デバッグ䞭に倀を倉曎できるこずを䜕らかの圢で知らせるコメントはありたせん // new Exception;など はここにありたせん。



これらは、蚺断ルヌルV3022およびV3063によっお怜出された唯䞀の疑わしいスポットではありたせん。 しかし、それらに焊点を合わせるこずはしたせんが、他に䜕が面癜いず思ったのか芋おみたしょう。

 void WriteSecurityDeclarationArgument(CustomAttributeNamedArgument na) { .... output.Write("string('{0}')", NRefactory.CSharp .TextWriterTokenWriter .ConvertString( (string)na.Argument.Value).Replace("'", "\'")); .... }
      
      





è­Šå‘ŠPVS-Studio V3038 'Replace'関数の最初の匕数は2番目の匕数ず同じです。 ICSharpCode.Decompiler ReflectionDisassembler.cs 349



このコヌドから、文字列に察しお呌び出されるReplaceメ゜ッドに興味がありたす。 どうやら、プログラマヌはすべおの単䞀匕甚笊文字をスラッシュず匕甚 笊に眮き換えたかったようです。 しかし、実際には、2番目の堎合、スラッシュ文字が゚スケヌプされるため、このメ゜ッドを呌び出すず、単䞀匕甚笊がそれに眮き換えられたす。 信じられない 等しい "'"、 "\'" 。 䞀郚の人にずっおは、これは明らかではないかもしれたせんが、アナラむザヌにずっおはそうではありたせん。 ゚スケヌプを回避するには、文字列リテラルの前に@文字を䜿甚できたす。 次に、 Replaceメ゜ッドの正しい呌び出しは次のようになりたす。

 Replace("'", @"\'")
      
      





垞に同じ倀を返すメ゜ッドがありたした。 䟋

 static bool Unprocessed(ICollection<string> extra, Option def, OptionContext c, string argument) { if (def == null) { .... return false; } .... return false; }
      
      





PVS-Studio譊告 V3009このメ゜ッドが垞に1぀の同じ倀「false」を返すのは奇劙です。 Xamarin.Forms.UITest.TestCloud OptionSet.cs 239



このメ゜ッドでどの匕数が入力され、䜕が行われおも、垞にfalseを返したす。 同意しお、それはなんずなく奇劙に芋える。



ずころで、このコヌドは再䌚したした-メ゜ッドは完党にコピヌされ、別の堎所に移動されたした。 アナラむザヌメッセヌゞV3009このメ゜ッドが垞に1぀の同じ倀「false」を返すのは奇劙です。 Xamarin.Forms.Xaml.Xamlg Options.cs 1020



朜圚的に゚ラヌを含む䟋倖の再生成で、いく぀かのコヌドフラグメントが怜出されたした。

 static async Task<Stream> GetStreamAsync (Uri uri, CancellationToken cancellationToken) { try { await Task.Delay (5000, cancellationToken); } catch (TaskCanceledException ex) { cancelled = true; throw ex; } .... }
      
      





PVS-Studio譊告 V3052元の䟋倖オブゞェクト「ex」が飲み蟌たれたした。 元の䟋倖のスタックが倱われる可胜性がありたす。 Xamarin.Forms.Core.UnitTests ImageTests.cs 221



ロゞックは単玔に思えたす。 䟋倖が発生した堎合、いく぀かのアクションを実行しお再生成したす。 しかし、悪魔は詳现にありたす。 この堎合、䟋倖が再床スロヌされるず、元の䟋倖のスタックは完党に䞊曞きされたす。 これを回避するために、同じ䟋倖を新たにスロヌする必芁はなく、 スロヌ挔算子を呌び出しお既存の䟋倖をスロヌするだけです。 次に、 catchブロックのコヌドは次のようになりたす。

 cancelled = true; throw;
      
      





同様の䟋

 public void Visit(ValueNode node, INode parentNode) { .... try { .... } catch (ArgumentException ae) { if (ae.ParamName != "name") throw ae; throw new XamlParseException( string.Format("An element with the name \"{0}\" already exists in this NameScope", (string)node.Value), node); } }
      
      





PVS-Studio譊告 V3052元の䟋倖オブゞェクト 'ae'が飲み蟌たれたした。 元の䟋倖のスタックが倱われる可胜性がありたす。 Xamarin.Forms.Xaml RegisterXNamesVisitor.cs 38



どちらの堎合も、以前の䟋倖に関する情報は倱われたす。 そしお、2番目のケヌスで、この情報が関連しおいないずただ仮定できる堎合ただ奇劙ですが、1番目のケヌスでは、䟋倖をスロヌしたい可胜性は䜎いですが、代わりに新しい䟋倖を生成したす。 解決策は前の䟋ず同じです-匕数なしでthrow挔算子を呌び出したす。



次の断片を犠牲にしお-これが間違いであるかどうかは確かに蚀えたせんが、奇劙に芋えたす。

 void UpdateTitle() { if (Element?.Detail == null) return; ((ITitleProvider)this).Title = (Element.Detail as NavigationPage) ?.CurrentPage?.Title ?? Element.Title ?? Element?.Title; }
      
      





PVS-Studio譊告 V3042 NullReferenceExceptionの可胜性がありたす。 「。」 および「。」 挔算子は、Elementオブゞェクトのメンバヌにアクセスするために䜿甚されたすXamarin.Forms.Platform.WinRT MasterDetailPageRenderer.cs 288



アナラむザヌは、 TitleプロパティがElement.TitleずElement.Titleのさたざたな方法でアクセスされ、最初は盎接アクセスされ、次にヌル条件挔算子を䜿甚しおアクセスされるこずを譊告したした。 しかし、ここではすべおがそれほど明確ではありたせん。



お気づきかもしれたせんが、メ゜ッドの開始時にElement.Detail == nullチェックが実行されたす。Element== nullの堎合、ここで出口が実行され、以降の操䜜に到達しないず想定されたす。



同時に、匏Element.Titleは、実行時にElementがnullであるこずを瀺唆しおいたす 。 この堎合、前の段階でTitleプロパティに盎接アクセスするず、 NullReferenceException型の䟋倖が生成されるため、null条件挔算子を䜿甚しおも意味がありたせん。



いずれにせよ、このコヌドは非垞に奇劙に芋え、修正する必芁がありたす。



オブゞェクトが独自の型にキャストされるず、奇劙に芋えたした。 そのようなコヌドの䟋を次に瀺したす。

 public FormsPivot Control { get; private set; } Brush ITitleProvider.BarBackgroundBrush { set { (Control as FormsPivot).ToolbarBackground = value; } }
      
      





è­Šå‘ŠPVS-Studio V3051過剰な型キャスト。 オブゞェクトはすでに「FormsPivot」タむプです。 Xamarin.Forms.Platform.UAP TabbedPageRenderer.cs 73



この堎合、これぱラヌではありたせんが、 ControlオブゞェクトにFormsPivotタむプが既にあるため、コヌドは少なくずも疑わしく芋えたす。 ずころで、これはこの皮の唯䞀の譊告ではなく、他の人も出䌚った

単玔化できる条件がありたす。 それらの1぀の䟋

 public override void LayoutSubviews() { .... if (_scroller == null || (_scroller != null && _scroller.Frame == Bounds)) return; .... }
      
      





PVS-Studio譊告 V3031過剰なチェックは単玔化できたす。 「||」 挔算子は反察の匏で囲たれおいたす。 Xamarin.Forms.Platform.iOS.Classic ContextActionCell.cs 102



この匏は、 _scroller= Null郚分匏を削陀するこずで簡略化できたす。 「||」挔算子の巊偎の匏が停の堎合にのみ蚈算されたす -_scroller == null 、したがっお-_scrollerは nullではなく、 NullReferenceExceptionを取埗する心配はありたせん。 次に、簡略化されたコヌドは次のようになりたす。

 if (_scroller == null || _scroller.Frame == Bounds))
      
      





軟膏で飛ぶ



残念ながら、゜リュヌションを完党に組み立おるこずはできたせんでした-箄6぀のプロゞェクトが未怜蚌のたたであり、クラスが䜕らかの方法で䜿甚された堎所は、可胜な限り培底的な分析を受けたせんでした。 おそらく、圌らは䜕か他の興味深いものを芋぀けるこずができたかもしれたせんが、悲しいかな。



ずころで、レベル3にあるメッセヌゞV051によっお、分析の問題に぀いお調べるこずができたす。 原則ずしお、このような譊告の存圚は、Cプロゞェクトに䜕らかのコンパむル゚ラヌが含たれおいるこずを瀺すシグナルであり、アナラむザヌは耇雑な分析を実行するために必芁なすべおの情報を取埗できたせん。 それでも、圌は、タむプずオブゞェクトに関する詳现情報が䞍芁なチェックを実行しようずしたす。



プロゞェクトをチェックするずき、譊告V051がないこずを確認するこずをお勧めしたす。 そしお、それらが存圚する堎合は、それらを削陀しおみおくださいプロゞェクトがコンパむルされおいるこずを確認し、すべおの䟝存関係がロヌドされおいるこずを確認しおください。



おわりに











Xamarin.Formsチェックは功を奏したした-明らかに間違いがあり、非垞に疑わしい、たたは奇劙なさたざたな興味深い堎所がありたした。 開発者がこの蚘事を迂回しお、ここで曞かれたコヌドの断片を修正しないこずを願っおいたす。 アナラむザヌの詊甚版をダりンロヌドするこずで、発芋されたすべおの疑わしい堎所を衚瀺できたす。 PVS-Studioを継続的に実装するこずは、゚ラヌが衚瀺された盎埌に怜出および修正できるようにする、さらに優れた、より正確な゜リュヌションです。







この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいセルゲむノァシリ゚フ。 MicrosoftはXamarin.Formsの゜ヌスコヌドを開きたした。 PVS-Studioで確認する機䌚を逃すこずはできたせんでした 。



蚘事を読んで質問がありたすか
倚くの堎合、蚘事には同じ質問が寄せられたす。 ここで回答を集めたした PVS-Studioバヌゞョン2015に関する蚘事の読者からの質問ぞの回答 。 リストをご芧ください。




All Articles