SPDisposeCheck jest już od paru dni dostępny, i muszę powiedzieć, że tool mi się podoba – choć mam wrażenie iż nie działa na mój kod albo naprawdę takich błędów nie popełniam, whatever.

Stwierdziłem jednak, że to co jest dostępne w dokumentacji to nie może być wszystko co narzędzie dostarcza. Gdyby tak było MS by się nie opóźniał aż tak wypuszczenia go.

Więc przez te kilka dni stwierdziłem iż zajmę się jego głębszą analizą by sprawdzić nie tylko wszystkie możliwe opcje, ale także jak on tak naprawdę działa :)

Zacznijmy od rzeczy prostych.

Parametry uruchomienia

W dokumentacji jeżeli dostępnej po zainstalowaniu narzędzia możemy wyczytać:

SPDisposeCheck <path to assemblies> -debug –xml <file>

Output is provided in the form of a list of potential problems.

-debug adds additional information.

-xml outputs the errors to an XML file instead of text

I to wszystko, czy aby na pewno? Po pierwsze warto zwrócić uwagę na liczbę mnogą „assemblies”. Mianowicie nie chodzi o to by po przecinku podać 20 DLLek/Exe, ale o katalog w którym te DLLki oraz EXE można znaleźć. Czyli SPDisposeCheck działa zarówno na jednej określonej bibliotece jak i na wszystkich bibliotekach znajdujących się w podanym katalogu. Ułatwia to znacząco pracę! :)

Reszta parametrów które są opisane to:

  • debug – zwraca szczegółowe informacje na temat danej klasy/metody którą analizuje. Przydatne gdy byśmy chcieli wyśledzić dlaczego według SPDisposeCheck wystąpił błąd albo dlaczego nie wystąpił;
  • xml <plik> – wypluwa wyniki w postaci pliku XML. Niestety, opcja ta nie działa poprawnie z powodu złego ustawienia widoczności typów przez programistów MS, jednak to chyba wcale nie jest aż taką dużą wadą. Narzędzie działa z poziomu linii poleceń więc typowe przekserowanie strumienia do pliku działa poprawnie: > plik.txt.
  • Zabawną rzeczą jest to iż istnieją parametry nieudokumentowane, które swobodnie możemy wykorzystywać:
  • v1 – najbardziej zagadkowy parametr :) Po dłuższej analizie doszedłem do wniosku iż v1odpowiada pierwszej wersji dokumentów – przed aktualizacją. Zaś po aktualizacji pewne założenia dobrego programowania zostały zmienione. Stąd też ta opcja;
  • su – pokazuje błędy, które są nieudokumentowane. Więcej o błędach nieudokumentowanych później;
  • ou – pokazuje tylko i wyłącznie nieudokumentowane błędy – wykorzystywane w kodzie SPDisposeCheck jest za pomocą IF, if true to nie wykonuj tej części analizy ;) Więcej o błędach nieudokumentowanych później.

Zasady udokumentowane

Jeżeli wykorzystujemy SPDisposeCheck zgodnie z tym co jest opublikowane w instrukcji obsługi narzędzia to błędy które są analizowane są opublikowane pisie na blogu Rogera Lamb: SharePoint 2007 and WSS 3.0 Dispose Patterns by Example.

Zasada opisywania wystąpienia błędu jest dość prosta. W kodzie SPDisposeCheck znajduje się enum:

public enum SPDisposeCheckID
{
    // Numer dla nieudukomentowanej zasady
    SPDisposeCheckID_000 = 0,
    SPDisposeCheckID_100 = 100,
    SPDisposeCheckID_110 = 110,
    SPDisposeCheckID_120 = 120,
    SPDisposeCheckID_130 = 130,
    SPDisposeCheckID_140 = 140,
    SPDisposeCheckID_150 = 150,
    SPDisposeCheckID_160 = 160,
    SPDisposeCheckID_170 = 170,
    SPDisposeCheckID_180 = 180,
    SPDisposeCheckID_190 = 190,
    SPDisposeCheckID_200 = 200,
    SPDisposeCheckID_210 = 210,
    SPDisposeCheckID_220 = 220,
    SPDisposeCheckID_230 = 230,
    SPDisposeCheckID_240 = 240,
    SPDisposeCheckID_300 = 300,
    SPDisposeCheckID_310 = 310,
    SPDisposeCheckID_320 = 320,
    SPDisposeCheckID_400 = 400,
    SPDisposeCheckID_500 = 500,
    // Numer dla zasady, która ma zostać pominięta
    SPDisposeCheckID_999 = 0x3e7
}

Każda wartość enum w postaci ciągu znaków (zamiast wartości enum to nazwa elementu) odpowiada odpowiedniej zakładce na stronie z opisanymi zasadami programowania Rogera Lamb. Czyli mówiąc dosłownie, zgodnie z numeracją enum:

Dzięki temu jesteśmy wstanie dość szybko I sprawnie przejść z opisu błędu do opisu w jaki sposób go uniknąć. Osobiście zastanawiam się jak oni chcą to aktualizować? Czy zarówno Roger będzie aktualizował swój post a oni kod, czy wymyślą lepszy sposób tak jak to zrobili z FxCop – swojego czasu była dostępna wersja CHM pliku z opisem plus wersja on-line na nie istniejącym już gotdotnet. Teraz jest to częścią dokumentacji do VS.

Zasady nieudokumentowane

Tutaj zaczyna się zabawa :) skoro są nieudokumentowane, to w jaki sposób je wykrywają? W końcu ktoś musiał wymyślić, że tutaj może być błąd :) Ogólnie chodzi o to, że zasad dobrego programowania w SharePoint jest masa, część jest opisana u Rogera część w Best Practices: Using Disposable Windows SharePoint Services Objects. Dokumenty tak naprawdę w znacznej części się pokrywają, nie miałem już sił sprawdzać czy nowa wersja Best Practices jest zgodna z blogiem i czy wszystko się pokrywa.

To co zrobili autorzy tool’a to po prostu wtedy kiedy im analiza nie pasowała do jednych z udokumentowanych opcji, dawali ID enumeratora dla nieudokumentowanej zasady. Część błędów nieudokumentowanych to po prostu przejście przez całą funkcję analizująca bez zmiany jakichkolwiek parametrów i na końcu sprawdzenie czy wyświetlić błędy nieudokumentowane, lub znalezienie takiego miejsca w kodzie gdzie widać, że brakuje Dispose a jednak do żadnej z zasad nie mogą tego podpiąć. Także sytuacja kiedy użyty jest Dispose na obiektach na których nie powinien zostać użyty i także nie jest to udokumentowane (czyli po sprawdzeniu wszystkich udokumentowanych zasad, wypisują nieudokumentowaną ;)).

Sami zdecydujcie czy chcecie używać tych parametrów :) ja jestem jak najbardziej za. Jeżeli kod od SPDisposeCheck wykryje coś czego nie może nigdzie przypisać, będę o tym wiedział.

Tajemniczy Parametr v1

Wywołanie analizy z parametrem v1, zwróciło mi nagle 7 błędów w moim kodzie, mimo iż normalna analiza nic nie znalazła. Na przykład błędem było wywołanie ParentWeb.ContentTypes. Na pierwszy rzut oka wszystko jest w porządku. Bo jest :) Problem polega na tym iż SPDisposeCheck zwróci nam informacje o tym iż ParentWeb nie został u mnie przypisany do obiektu SPWeb :) Dla przykładu

ID: SPDisposeCheckID_140

Module: Xxx.SharePoint.Receivers.dll

Method: Xxx.SharePoint.Receivers.PdpFeatureReceiver.LinkFieldToContentType(Microsoft.SharePoint.SPWeb,System.String,Microsoft.SharePoint.SPField)

Statement: local0 := web.{Microsoft.SharePoint.SPWeb}get_Site().{Microsoft.SharePoint.SPSite}get_RootWeb().{Microsoft.SharePoint.SPWeb}get_ContentTypes().{Microsoft.SharePoint.SPContentTypeCollection}get_Item(contentType)

Notes: Call to GetRootWeb type and no variable to catch return value

More Information: http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx#SPDisposeCheckID_140

———————————————————-

ID: SPDisposeCheckID_170

Module: Xxx.SharePoint.Receivers.dll

Method: Xxx.SharePoint.Receivers.PdpFeatureReceiver.AddContentTypeToList(Microsoft.SharePoint.SPWeb,Microsoft.SharePoint.SPList,System.String)

Statement: local0 := web.{Microsoft.SharePoint.SPWeb}get_ParentWeb().{Microsoft.SharePoint.SPWeb}get_ContentTypes().{Microsoft.SharePoint.SPContentTypeCollection}get_Item(contentTypeName)

Notes: Call to getParentWeb type and no variable to catch return value

More Information: http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx#SPDisposeCheckID_170

———————————————————-

Kod dla którego powyższe błędy zostały zwrócone wygląda tak:

SPContentType ct = null;

ct = web.ParentWeb.ContentTypes[contentType];
 
if(ct == null)
{
    ct = web.Site.RootWeb.ContentTypes[contentType];
}

Problem jak się okazało nie leżał w moim kodzie a jedynie w tym iż parametr v1 odnosi się do zasad z poprzednich wersji dokumentów Rogera i Best Practices. W których własności ParentWebi RootWeb powinne zostać przypisane do zmiennych a następnie na nich powinien zostać wykonany Dispose. Zrobiłem więc mały test. Zamiast pobierać płynnie, potworzyłem sobie zmienne by kod wyglądał tak:

SPContentType ct = null;
 
SPWeb w = web.ParentWeb;
ct = w.ContentTypes[contentType];
 
if(ct == null)
{
    SPSite s = web.Site;
    SPWeb w1 = s.RootWeb;
    ct = w1.ContentTypes[contentType];
    s.Dispose();
}

To co dostałem przez analizę powyższego kodu to:

ID: SPDisposeCheckID_170

Module: Xxx.SharePoint.Receivers.dll

Method: Xxx.SharePoint.Receivers.PdpFeatureReceiver.LinkFieldToContentType(Microsoft.SharePoint.SPWeb,System.String,Microsoft.SharePoint.SPField)

Statement: local1 := web.{Microsoft.SharePoint.SPWeb}get_ParentWeb()

Notes: Disposable type not disposed: Microsoft.SharePoint.SPWeb

***This may be a false positive depending on how the type was created or if it is disposed outside the current scope

More Information: http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx#SPDisposeCheckID_170

———————————————————-

Zaś opis na blogu głosi:

ParentWeb property no longer requires Dispose() as previously indicated in the Whitepaper Best Practices: Using Disposable Windows SharePoint Services Objects

I wszystko jasne :)

Blokowanie zasad

Podobnie jak w FxCop, tak i w SPDisposeCheck możemy wyłączyć sprawdzanie określonych zasad dla danej klasy/metody. Wystarczy tylko pamiętać jaki ID enum pasuje do jakiej zasady ;)

[SPDisposeCheckIgnore(SPDisposeCheckID.SPDisposeCheckID_110, "Don't want to do it")]

Przykłady w jaki sposób korzystać z ignorowania zasad są umieszczone w przykładach kodu instalujących się od razu z narzędziem. Warto pamiętać o tym iż takie miejsce istnieje i zaglądać tam od czasu do czasu by sobie przypomnieć jak to się robiło :)

Oczywiście by móc korzystać z ignorowania zasad, należy dodać referencje do SPDisposeCheck.exe.

Jak oni to zrobili?

Naprawdę prosto :) Wykorzystali przestrzeń nazw System.Compiler, i standardowy wzorzec Visitorw celu załadowania i przejścia przez wszystkie moduły, atrybuty, metody itp. itd. Z tą różnicą iż przeciążyli oni częściowo klasę StandardVisitor w celu oprogramowania analizy kodu w odpowiednich miejscach – to znaczy by podczas przeglądania np.: metody wykorzystać ich kod a nie kod z System.Compiler. Dzięki czemu zaoszczędzili czas na pisaniu własnego „dekompilatora” ;)

Nie ma co szukać po netcie danych na temat wykorzystanej przestrzeni nazw, gdyż jest ona załączona do niektórych bibliotek/aplikacji Microsoftowych a jej wszystkie klasy, interfejsy, enumy itp. itd. mają widoczność internal.

Podsumowanie

To chyba wszystko :) Jeżeli coś jeszcze znajdę to na pewno to dodam do tego wpisu :)

Osobiście zachęcam do korzystania z narzędzia, a jak coś znajdziecie (jakiś bug) to zgłoście to na stronie SPDisposeCheck. Na pewno to poprawią :)