GIT: Kiedy używać Rebase oraz Merge

Posted July 31st, 2015 by Piotr Zieliński
Categories: Ogólne

Jakiś czas temu, pisałem o różnicach między Rebase oraz Merge. Dzisiaj chciałbym rozważyć kilka scenariuszy, gdzie można stosować Rebase.

Przede wszystkim jeśli różnica nie jest jasna, wtedy zawsze bezpieczniejszą opcją jest Merge. Nie ma w tym nic złego i najwyżej historia logów będzie mniej czytelna, ale  przynajmniej uniknie się niepotrzebnych problemów.

Zacznijmy od scenariusza kiedy nie używać rebase. W skrócie nie należy stosować rebase na publicznych gałęziach, gdzie nadpisane zmiany, mogą być u kogoś już w lokalnym repozytorium. Rozważmy diagram z poprzedniego wpisu. Po zmianach w feature branch, uzyskaliśmy następującą strukturę:

image

 

Po dokonaniu rebase na master, zmiany 4 oraz 5 zostaną przepisane:

image

Widzimy, że zostały one przesunięte. Techniczne będą miały zupełnie inny hash code. Problem jest taki, że cześć użytkowników mogła już pobrać kod przed dokonaniem rebase. Nie trudno domyślić się, że w momencie kiedy będą chcieli wykonać GIT PUSH, nastąpią problemy ponieważ commity 4 i 5 w ich wersji będą inne niż te na serwerze, ponieważ zostały one przepisane za pomocą Rebase. Innymi słowy zmiany 4  i 5 są inne niż te co mają pozostali użytkownicy. Skoro gałąź jest publiczna może to utrudnić życie wielu użytkownikom.

Wniosek taki, że jeśli chcemy przenieść zmiany z feature branch do master, która jest publiczną gałęzią należy używać komendy MERGE.

Rozważmy inny teraz kierunek, a mianowicie master->feature branch. Załóżmy, że tworzymy gałąź, z której nikt dalej nie będzie “branchować” ponownie. Innymi słowy zmiany w feature branch będą zsynchronizowane wyłącznie z master. W takim przypadku, użycie rebase, aby pobrać zmiany z master  jest bezpieczne i dzięki niemu mamy prostą i liniową historie zmian. Oczywiście znów zostaną nadpisane commity, ale będą to wyłącznie nasze zmiany, które nie są jeszcze dostępne w publicznej gałęzi. Nie ma w tym przypadku znaczenia to, że hash code zostanie zmodyfikowany. W sytuacji jednak, gdy kilka osób pracuję nad tym samym feature branch, wtedy napotkamy identyczne problemy jak na powyższym rysunku. Oznacza to, że wtedy należy korzystać z merge. Innymi słowy, gałąź publiczna + rozwidlenia wykluczają użycie rebase. Często jednak feature branch ma charakter bardziej prywatny i wtedy nic nie stoi na przeszkodzie, aby cieszyć się prostą historią zmian wykonaną przez rebase.

Kolejny przykład to współdzielenie tej samej gałęzi. Czasami programiści pracują po prostu na master, ewentualnie osobnej gałęzi. W tym przypadku również można rozważyć rebase i jest to bezpieczna opcja. Dlaczego? W końcu pracujemy na publicznej gałęzi, a jak wiemy już rebase+publiczna gałąź zwykle jest bardzo złym pomysłem. W tym przypadku jednak, będziemy nadpisywać wyłącznie swoje własne commity, które nie są jeszcze publiczne.  Jedyną przeszkodzą może być sytuacja, w które współdzielimy nasz commit za pomocą np. plików zmian. Jeśli to zrobimy, wtedy w przypadku rebase zostaną one przepisane i ta osoba, która otrzymała wcześniej od nas zmiany (np. poprzez email) będzie miała inne zmiany niż te po dokonaniu REBASE. Jeszcze raz podkreślam mowa tutaj o publicznej, współdzielonej gałęzi, która jednak nie ma żadnych rozwidleń. Wtedy mamy pewność, że dokonując rebase możemy jedynie przepisać własne, lokalne zmiany.

Podsumowując:

1. Feature branch –> master:  merge

2. Master->Feature branch (w celu synchronizacji) –> rebase jest w porządku o ile, ktoś nie korzysta ze wspomnianej gałęzi np. poprzez dalsze rozwidlanie.  W przeciwnym wypadku zalecany jest merge ponieważ mogą nastąpić podobne problemy jak w punkcie 1.

3. Gałęzie współdzielone (brak rozwidleń):  rebase

nUnit–Wykonywanie testów w osobnych AppDomain

Posted July 28th, 2015 by Piotr Zieliński
Categories: C#, Testy

Testy jednostkowe z natury muszą być wykonywane w izolacji. Wykonanie np. pierwszego testu nie powinno mieć żadnego znaczenia dla pozostałych. Analogicznie, kolejność ich wykonywania nie ma znaczenia. Zwykle jest to bardzo proste i osiąga się to poprzez np. mock’i.

Czasami jednak może zajść potrzeba całkowitej izolacji poprzez wykonywanie każdego testu w osobnej AppDomain. Myślę, że w 99% przypadków jednak, można bez tego obyć się. Ostatnio jednak, pisząc pewne narzędzie do Visual Studio, musiałem odizolować od siebie całkowicie to co znajduje się w pamięci. Na szczęście wystarczyło zainstalować poniższy pakiet NuGet:

Install-Package NUnit.ApplicationDomain

Następnie jakiejkolwiek testy wystarczy oznaczać odpowiednim atrybutem:

[Test, RunInApplicationDomain] public void MyTest() { }

Jeszcze raz zaznaczam, w większości przypadków fakt, że potrzebujemy wykonać test w osobnej AppDomain powinien sygnalizować, że coś złego jest z naszym kodem. W przypadku jednak niektórych testów integracyjnych ma to sens – np. kiedy w teście musimy załadować zewnętrzny DLL, który potem chcemy usunąć z pamięci ponieważ nie powinien wpływać na następne testy. W CLR jedynym sposobem na usunięcie DLL z pamięci, jest zniszczenie AppDomain. W bardzo specyficznych scenariuszach testów integracyjnych, powyższe rozwiązanie ma sens.

Bezpieczeństwo WEB: Wprowadzenie, mapowanie aplikacji część I

Posted July 25th, 2015 by Piotr Zieliński
Categories: Bezpieczeństwo

Wpis o Merge\Rebase powstanie pewnie w przyszłym tygodniu – pamiętam. Dzisiaj chciałbym rozpocząć nowy cykl o bezpieczeństwie aplikacji webowych. Niejednokrotnie o tym pisałem już, ale były to luźno powiązane ze sobą wpisy. Od tego wpisu chciałbym to zmienić i przedstawić bardziej dogłębnie tą tematykę.

Pierwsze wpisy będą stanowiły całkowite podstawy, ale mam nadzieję, że również bardziej zaawansowani programiści znajdą coś ciekawego w tym (np. wykorzystywane narzędzia).  Na końcu mam zamiar przedstawić kilka “sławnych” luk bezpieczeństwa, które miały miejsce w ASP.NET WebForms, ASP.NET MVC oraz w IIS.

Od strony technologicznej będę zajmował się głównie rozwiązaniami od MS (ASP.NET, IIS) ponieważ po prostu nie korzystałem na tyle długo z innych technologii, aby móc pisać o bezpieczeństwie w nich i najczęściej popełnianych błędach.  Oczywiście tematyka bezpieczeństwa aplikacji webowych głównie opiera się na tym samym mechanizmie i wykorzystywana technologia nie ma znaczenia. Na przykład, każda aplikacja może być podatna na SQL Injection.  Z drugiej strony jednak, czasami wykorzystuje się luki w bezpieczeństwie (np. w kodowaniu znaków) specyficzne dla danej technologii czy framework’u.

Zatem jaki jest pierwszy lub jeden z pierwszych kroków w przypadku bezpieczeństwa aplikacji? Jeśli nie znamy danej strony\aplikacji nie wiemy  jakie luki bezpieczeństwa mogą w niej występować. Z tego względu, warto rozpocząć analizę strony, która dostarczy nam informacji m.in. o wykorzystywanych technologiach, serwerze www, treści, wszelkich podstronach czy po prostu logice biznesowej, która została zaimplementowana. Luka w bezpieczeństwie może występować na każdym etapie. Jeśli dowiemy się, że wersja IIS jest nieaktualna, wtedy możemy spróbować znaleźć i wykorzystać jedną z wielu z luk bezpieczeństwa, które oficjalne są już znane i załatane w najnowszych wersjach.

Szczególnie ważne jest odkrycie wszelkich podstron czy plików. W skrajnych sytuacjach strony mogą eksponować nawet listę haseł. Wydaje się to bardzo nieprawdopodobne, ale nie jedna wielka firma czy organizacja w przeszłości przechowywała dane wrażliwe w plikach tekstowych, które były potem publicznie dostępne i wystarczyło zgadnąć po prostu URL… W praktyce strony\aplikacje są często pisane przez amatorów, outsourcowane do firm, które nie przejmują się bezpieczeństwem lub w przypadku dużych firm i napiętych terminów,  systemy webowe są na tyle skomplikowane, że prawdopodobieństwo wystąpienia błędu jest bardzo wysokie.

Innymi słowy, mapowanie dokonujemy, aby odkryć na jakie ataki może być potencjalnie podatna aplikacja. Na przykład, przeglądając zasoby możemy wywnioskować o:

  1. Wykorzystywanych technologiach (o tym konkretnym mapowaniu napiszę później)
  2. Plikach i podstronach. Czasami odkryte pliki bezpośrednio stanowią lukę w bezpieczeństwie np. logi aplikacji.
  3. Ogólnym działaniu aplikacji. Budując mapę, analizujemy działanie strony. Dzięki temu w przyszłości skupimy się na konkretnych aspektach. Jeśli podczas analizy odkryjemy formularze do logowania, wtedy spróbujemy np. ominąć autoryzację. Jeśli zobaczymy, że pewna treść jest wstrzykiwana dynamicznie, możemy pomyśleć o atakach typu XSS czy SQL Injection.
  4. Zwracanych kodach HTTP, czasach wykonywania konkretnych zapytań jak i łańcuchach interakcji. Dzięki niej, dowiemy się np. jaką serię zapytań należy wykonać, aby użytkownik został autoryzowany.
  5. Odkrycie ukrytych plików, które w żaden sposób nie są podlinkowane na publicznej stronie (o tym później).
  6. Identyfikacja punktów, gdzie wstrzykujemy dane do aplikacji. Klasycznym przykładem są formularze. Inne możliwości to Query String (argumenty w URL), ciasteczka, HTTP body, HTTP header czy po prostu zwykły URL. Oczywiście wszelkie punkty gdzie wstrzykujemy dane powinny zwrócić naszą uwagę, ponieważ potencjalnie są podatne na ataki (np. w przypadku braku walidacji).
  7. Identyfikacja usług. Dzisiejsze aplikacje webowe to nie tylko pojedynczy serwis. Często występują połączenia z zewnętrznymi usługami, zwykle typu REST.
  8. Walidacji po stronie klienta. Często strony weryfikują dane za pomocą JavaScript. Zawsze warto sprawdzić, czy ta sama weryfikacja następuje również po stronie serwera.
  9. Połączeniach z bazą danych. Jeśli wiemy, że aplikacja nie jest statyczna to być może jest podatna na SQL Injection.

Oczywiście powyższa lista to tylko mały podzbiór przykładów. Jedynie co chcę pokazać, że bez mapowania nie wiemy po prostu od czego zacząć. Po analizie, możemy taką listę ułożyć np. od najbardziej prawdopodobnych ataków do tych mniej. Nie warto poświęcać wszystkiemu jednakowej uwagi.

Mapowanie aplikacji może odbywać się na kilka sposobów. Pierwszy z nich to ręczne przeglądanie strony i zgadywanie linków. Na przykład, jeśli aplikacja wykorzystuje elmah do wykonywania logów, możemy spróbować otworzyć http://localhost:xxxx/elmah.axd.  Logi zwykle dostarczają wystarczająco dużo informacji, aby przeprowadzić już konkretny atak.

Innym sposobem jest odpalenie automatycznego narzędzia, które będzie przechodziło za nas z jednej strony na drugą – tzw. web spidering.  W praktyce jednak jest to dość ograniczone ponieważ strony często korzystają z zaawansowanej walidacji danych w formularzach czy po prostu są asynchroniczne i interakcja polega na wysłaniu zapytania AJAX.

Najlepsze rezultaty daje trzeci sposób czyli połączenie ręcznego przeglądania strony ze wsparciem narzędzi. Ręcznie możemy zalogować się czy wykonać zapytania AJAX, a narzędzie w tle za nas będzie wszystko śledzić.

W Internecie można znaleźć wiele narzędzi, zarówno darmowych jak i komercyjnych. Osobiście korzystam z Burp Suite, które ma również darmową edycję.  Program może nie jest jakoś super intuicyjny, ale ma sporo użytkowników więc łatwo znaleźć odpowiedź na jakiekolwiek pytania. Screenshot:

image

Program działa na zasadzie proxy, zatem w ustawieniach musimy ustawić odpowiedni port:

image

Ruch będzie kierowany do 8080 czyli do Burp Suite. Dzięki temu, przeglądając jakąś stronę internetową,  Burp Suite będzie budował w tle mapę za nas.

Powinniśmy zatem przejść przez wszystkie strony, zarówno jako użytkownik anonimowy jak i po autentykacji (jeśli znamy hasło). Wracając do BS ponownie, zobaczymy, że wszelkie zapytania i odpowiedzi zostały zapisane:

image

BS w międzyczasie również odkryje za nas pewne strony. Zostaną one przedstawione kolorem szarym na powyższej liście. Jeśli posortujemy wyniki po Time Requested, wtedy strony automatycznie wykryte zostaną pogrupowane razem:

image

Warto poświęcić chwilę i przeanalizować je. Możliwe, że któraś ze stron wykryta przez BS nie miała być w zamiarze autorów aplikacji publiczna.

To dopiero początek mapowania aplikacji – w przyszłym wpisie więcej informacji o tym.

GIT: Merge vs Rebase

Posted July 22nd, 2015 by Piotr Zieliński
Categories: Ogólne

Coraz więcej osób korzysta dzisiaj z GIT, zamiast starych i scentralizowanych repozytoriów. Czasami jednak mnogość komend może być przytłaczająca, zwłaszcza na początku. Dzisiaj chciałbym wyjaśnić czym różni się Merge od Rebase.

Obie komendy służą do tego samego czyli synchronizacji dwóch gałęzi kodu np. lokalnego repozytorium ze zdalną gałęzią. Efekt na końcu będzie “taki sam”, czyli połączenie lokalnego kodu, ze zmianami naniesionymi np. w gałęzi. Dlaczego zatem GIT tak to “komplikuje”? W TFS czy w SVN zwykle korzystało się z komendy typu Update i to wszystko. W GIT jest trochę inaczej i zwykle korzysta się np. z Fetch+Merge, Fetch+Rebase lub po prostu PULL.

Najlepiej pokazać to na przykładzie. Załóżmy, że mamy pewną gałąź, np. master:

image

Powyższy rysunek przedstawia 3 zmiany (commit). Następnie użytkownik tworzy gałąź, w celu zaimplementowania czegoś (feature branch) i dokonuje również kilku zmian:

image

Bardzo możliwe, że w tym samym czasie ktoś zmodyfikował master:

image

Po jakimś czasie będziemy musieli zintegrować Branch A z master. Mamy do wyboru Merge albo Rebase.

Jeśli użyjemy klasyczny merge (branch->master), wtedy struktura będzie wyglądać następująco:

image

Komenda tworzy dodatkowy commit, który scala dwie gałęzie. W historii zmian, zobaczymy coś w stylu “merge commit”, który scala je. Powstanie zatem rozwidlienie i historia zawartości nie będzie liniowa jak to widać na powyższym rysunku. Zaletą merge jest prostota bo nie modyfikujemy poprzedniego kodu – po prostu tworzymy na samej górze dodatkowy commit, który zakańcza rozwidlenie gałęzi. Wadą rozwiązania jest fakt, że przy każdym merge, powstaje dodatkowy commit, co zwykle utrudnia przeglądanie historii.

Rebase z kolei, spowoduje, że będziemy mieli  bardzo przejrzystą historie zmian:

image

Widzimy, że struktura jest liniowa. Rebase jak sama nazwa sugeruje, polega na zamianie podstawy.  W tym przypadku, commity’ z gałęzi “branch a” zostały przepisane u podstawy master. Efekt jest taki, że nie musimy tworzyć sztucznego “merge commit” i w historii zmian widzimy tylko to, co faktycznie zostało zmodyfikowane przez innych użytkowników.

Innymi słowy, Rebase tworzy liniową strukturę, ale modyfikuje historie zmian, z kolei Merge tworzy dodatkowy commit, scalający rozwidlenia, ale powoduje to powstanie nieliniowej struktury zmian.

Struktura liniowa nie jest jednak zawsze pożądana – w końcu rozwidlenie w przypadku Merge dostarcza jakąś informację o kontekście zmian. W przypadku Rebase nie wiadomo, że feature branch został scalony i stąd nastąpiły zmiany. Ponadto, ze względu, że rebase dokonuje przepisania commitów może być to czasami niebezpieczne – w końcu jest to ingerencja w już zapisany kod.

W przyszłym poście postaram się napisać kilka wskazówek dotyczących wyboru między Rebase a Merge oraz czego należy unikać (oczywiście z mojego punktu widzenia).

Visual Studio 2013–konwersja JSON\XML do klas C#

Posted July 19th, 2015 by Piotr Zieliński
Categories: Visual Studio

W Visual Studio 2013 istnieje opcja “Paste  special”, gdzie możemy wkleić dowolny JSON lub XML jako klasy C#. Opcja jest mało znana, ale bardzo przydatna, gdy pracujemy z zewnętrznymi usługami. Często ręcznie tworzymy takie klasy w celu ich późniejszej deserializacji, np. za pomocą JSON.NET. Wystarczy, że przekopiujemy dokument JSON lub XML do schowka, a następnie z menu głównego wybierzemy:

image

Załóżmy, że w schowku mieliśmy:

{"employees":[ {"firstName":"John", "lastName":"Doe"}, {"firstName":"Anna", "lastName":"Smith"}, {"firstName":"Peter", "lastName":"Jones"} ]}

Wygenerowane klasy będą wyglądać następująco:

public class Rootobject { public Employee[] employees { get; set; } } public class Employee { public string firstName { get; set; } public string lastName { get; set; } }

Nie trzeba instalować żadnych zewnętrznych pluginów – jest to część VS.

Roslyn: Analiza przepływu kontroli

Posted July 16th, 2015 by Piotr Zieliński
Categories: Roslyn

W poprzednim wpisie zajęliśmy się przepływem danych, czyli sprawdzaliśmy jakie zmienne zostały zapisane lub odczytane. Analogiczną analizą jest control flow. Dzięki niej, wiemy  w którym momencie funkcja może zakończyć działanie np. przez instrukcje “return”.

Tak jak wcześniej, najpierw musimy skompilować kod:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public string TestMethod1(string a) { int localA=3,localB=5; for(int i=0;i<=10;i++) { if(a[i]!='') break; } localB++; return a; } } "); var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var compilation = CSharpCompilation.Create("TestAssembly", new[] { tree }, new[] { mscorlib });

Kolejny krok to uzyskanie SemanticsModel:

SemanticModel model = compilation.GetSemanticModel(tree,true);

Analiza przypływu kontroli sprowadza się do:

ControlFlowAnalysis controlFlow = model.AnalyzeControlFlow(node);

Zajrzyjmy, co za pomocą ControlFlowAnalysis możemy dowiedzieć się:

public abstract class ControlFlowAnalysis { public abstract ImmutableArray<SyntaxNode> EntryPoints { get; } public abstract ImmutableArray<SyntaxNode> ExitPoints { get; } public abstract bool EndPointIsReachable { get; } public abstract bool StartPointIsReachable { get; } public abstract ImmutableArray<SyntaxNode> ReturnStatements { get; } public abstract bool Succeeded { get; } }

EntryPoints oraz ExitPoitns opisują wejścia do gałęzi kodu oraz ich wyjścia. ReturnStatements, jak nie trudno domyślić się, zawiera informacje o instrukcjach return, które zawsze kończą działanie funkcji.

Roslyn: Analiza przepływu danych–semantyczny model

Posted July 13th, 2015 by Piotr Zieliński
Categories: Roslyn

Semantyczny model dostarcza nam wiele informacji o kodzie, które zwykle uzyskuje się po kompilacji. Na przykład przeładowanie metod jest dużo łatwiejsze do określenia już po kompilacji.  Oznacza to, że nie jest już to  klasyczna, statyczna analiza kodu. Z tego względu, najpierw danych kod należy skompilować za pomocą:

var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var compilation = CSharpCompilation.Create("TestAssembly", new[] { tree }, new[] { mscorlib }); SemanticModel model = compilation.GetSemanticModel(tree);

Posiadając semantyczny model, możemy dokonać tzw. analizy przepływu danych. Jak sama nazwa wskazuje, dzięki niej będziemy wiedzieli, co dzieje się z naszymi danymi, a w szczególności:

  1. Jakie zmienne są odczytywane
  2. Jakie zmienne są zapisywane
  3. Jakie zmienne są deklarowane
  4. Co dzieje się ze zmiennymi w wewnątrz i na zewnątrz bloku

Jak widać, w celu uzyskania modelu, wystarczy wywołać GetSemanticModel na kompilacji.  Ponadto załóżmy, ze powyższy SyntacTree zawiera następujący kod:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1(string a) { int localA=3,localB=5; if(a!=null) { localA++; } localB++; } } ");

Następnie, w celu dokonania analizy instrukcji warunkowej “if(a!=null)” wystarczy:

var node = tree.GetRoot() .DescendantNodes() .OfType<BlockSyntax>() .First() .DescendantNodes() .OfType<IfStatementSyntax>() .First(); DataFlowAnalysis dataFlowResult = model.AnalyzeDataFlow(node);

Obiekt DataFlowAnalysis zawiera następujące pola:

public abstract class DataFlowAnalysis { public abstract ImmutableArray<ISymbol> VariablesDeclared { get; } public abstract ImmutableArray<ISymbol> DataFlowsIn { get; } public abstract ImmutableArray<ISymbol> DataFlowsOut { get; } public abstract ImmutableArray<ISymbol> AlwaysAssigned { get; } public abstract ImmutableArray<ISymbol> ReadInside { get; } public abstract ImmutableArray<ISymbol> WrittenInside { get; } public abstract ImmutableArray<ISymbol> ReadOutside { get; } public abstract ImmutableArray<ISymbol> WrittenOutside { get; } public abstract ImmutableArray<ISymbol> Captured { get; } public abstract ImmutableArray<ISymbol> UnsafeAddressTaken { get; } public abstract bool Succeeded { get; } }

Jak widzimy, struktura zawiera mnóstwo pól opisujących co dzieje się z danymi we wskazanym bloku, w naszym przypadku w instrukcji warunkowej.

Na przykład WrittenInside zawiera zmienne, które są zapisywane w danym bloku. Dla powyższego przykładu jest to “localA”. VariablesDeclared będzie puste ponieważ nie deklarujemy żadnych zmiennych w powyższym IF. DataFlowsIn zawiera z kolei “localA” oraz “a” ponieważ to one uczestniczą w DataFlow.

SemanticModel jest przydatny jeszcze w kilku innych analizach, o których będę pisał w następnych postach.

Roslyn: Workspace API

Posted July 10th, 2015 by Piotr Zieliński
Categories: Roslyn

Roslyn to nie tylko parsowanie kodu, ale również zarządzanie projektem\solucją za pomocą WorkspaceAPI.

Pisząc różne narzędzia dla programistów, oprócz analizy kodu, zwykle chcemy mieć informacje o kontekście danego kodu – np. nazwie pliku czy projekcie w którym znajduje się dana klasa.

Workspace API, jak nie trudno domyślić się, opiera się na abstrakcyjnej klasie Workspace.  Zwykle jednak pracować będziemy z MSBuildWorkspace, która pozwala nam zarządzać .sln czy .csproj:

MSBuildWorkspace msWorkspace = MSBuildWorkspace.Create(); Solution solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;

Wywołując OpenSolutionAsync na Workspace, otwieramy po prostu solucję. Zmienna solutionPath to zwykły string wskazujący do pliku .sln. Po otwarciu, możemy wylistować wszystkie projekty i pliki:

foreach (Project project in solution.Projects) { Console.WriteLine(project.Name); foreach (Document document in project.Documents) { Console.WriteLine("\t{0}",document.FilePath); } }

API pozwala również na zarządzanie referencjami:

foreach (Project project in solution.Projects) { foreach (ProjectReference projectReference in project.ProjectReferences) { Console.WriteLine(projectReference.ProjectId); } foreach (var metaReference in project.MetadataReferences) { Console.WriteLine(metaReference.ToString()); } }

ProjectReferencje to referencja do innego projektu w tej samej solucji. Z kolei za pomocą MetaReference uzyskujemy dostęp do wszystkich innych bibliotek z których korzystamy, włączając przy tym systemowe biblioteki.

Do dyspozycji jest również AdhocWorkspace, klasa służąca do tworzenia całych solucji i projektów od zera:

var workspace = new AdhocWorkspace(); const string projectName = "ProjectA"; ProjectId projectId = ProjectId.CreateNewId(); ProjectInfo projectInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), projectName, projectName, LanguageNames.CSharp); Project projectA = workspace.AddProject(projectInfo); var classCode = SourceText.From("class SampleClass {}"); var newDocument = workspace.AddDocument(projectA.Id, "SampleClass.cs", classCode);

Jeśli piszemy rozszerzenia do Visual Studio, wtedy zdecydowanie powinniśmy zainteresować się VisualStudioWorkspace. Wcześniej zarządzanie projektem było dość kłopotliwe, a teraz wystarczy, że z rozszerzenia uzyskamy dostęp do  VisualStudioWorkspace za pomocą importu MEF:

[Import(typeof(Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace))] public VisualStudioWorkspace myWorkspace { get; set; }

Jeśli piszemy plugin, liczący metryki kodu, wtedy za pomocą VisualStudioWorkspace wiemy, kiedy użytkownik dodaje nowy plik, projekt czy modyfikuje jakąś konfigurację. Klasa eksponuje zdarzenia takie jak WorkspaceChanged, które wystarczy po prostu monitorować.

Roslyn: Wizualizacja drzewa

Posted July 7th, 2015 by Piotr Zieliński
Categories: Roslyn

Sporo pisałem w ostatnich wpisach o drzewie jako reprezentacji kodu. Za pomocą LINQ albo CSharpSyntaxWalker jesteśmy w stanie wyświetlić jakikolwiek element kodu. Wszelkie metody reprezentowane są za pomocą  MethodDeclarationSyntax,  klasy  z kolei ClassDeclarationSyntax.

Problem w tym, że tych obiektów jest na tyle dużo, że czasami trudno domyślić się na jaki należy rzutować generyczny SyntaxNode, aby odczytać konkretne dane. Na szczęście w raz z instalacją Roslyn, do dyspozycji w Visual Studio będziemy mieli Roslyn Syntax Visualizer.

Plugin standardowo instalujemy z galerii:

https://visualstudiogallery.msdn.microsoft.com/0f18f8c3-ec79-468a-968f-a1a0ee65b388

Z menu głównego wybieramy View –> Other Windows –> Roslyn Syntax Visualizer:

image

Załóżmy, że mamy następujący kod:

using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace ConsoleApplication9 { class Program { static void Main(string[] args) { SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { if(c==5) { ; ; } } } "); Console.WriteLine(tree.ToString()); } } }

Umieszczając kursor na deklaracji klasy dostaniemy:

image

Analogicznie klikając na elemencie w TreeView, w kodzie zostanie podświetlony dany element:

image

Niebieski kolor to SyntaxNode a zielony SyntaxToken. Jeśli pojęcia nie są jasne to zapraszam do mojego pierwszego wpisu o Roslyn.

Roslyn–przepisywanie kodu

Posted July 4th, 2015 by Piotr Zieliński
Categories: Roslyn

W poprzednim wpisie przedstawiłem klasę CSharpSyntaxWalker – przydatną przy analizie drzewa kodu. Dzięki niej, automatycznie bez pisania kodu rekurencyjnego jesteśmy w stanie przejść przez każdy element kodu.

Dzisiaj o analogicznym rozwiązaniu ale służącym do przepisywania kodu a nie tylko jego analizowania. Mechanizm działa bardzo podobnie do CSharpSyntaxWalker. Wystarczy, że stworzymy klasę dziedziczącą po CSharpSyntaxRewriter:

public class CustomRewriter : CSharpSyntaxRewriter { public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node) { return null; } }

Następnie, wystarczy wywołać Visit na węźle, który chcemy przepisać:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { if(c==5 { ; ; } } } "); var rewritter = new CustomRewriter(); SyntaxNode rewrittenNode = rewritter.Visit(tree.GetRoot());

Po przepisaniu, nowy kod nie będzie zawierał dwóch pustych instrukcji, które widzimy w powyższej instrukcji warunkowej (if(c==5)):

image

Na przykład, w celu wstawienia komentarza przed każdym IF’em możemy:

public class CustomRewriter : CSharpSyntaxRewriter { public override SyntaxNode VisitIfStatement(IfStatementSyntax node) { return base.VisitIfStatement(node).WithLeadingTrivia(SyntaxFactory.Comment("//test")); } }

W narzędziach generujących kod, powyższa klasa jest bardzo przydatna. Na przykład, jeśli chcemy automatycznie wygenerować komentarze dla naszego kodu, wystarczy przeciążyć VisitMethodDeclaration:

public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) { return base.VisitMethodDeclaration(node); }

CSharpSyntaxWalker oraz CsharpSyntaxRewritter są pomocniczymi klasami. Za pomocą LINQ i standardowego API możemy osiągnąć to samo. Jak wspomniałem jednak, wymagałoby to pisania rekurencyjnego kodu i dla skomplikowanych projektów jest to po prostu niewygodne oraz może spowodować problemy z wydajnością.