Oregon Institute of Technology Static Code Analysis Goals: Run static code analysis as part of a build Know how to exclude or suppress rule violations Know how to fix rule violations Associate a rule violation with a work item Create a check-in policy that mandates a successful static code analysis first Overview: Syntactically correct code that passes all the compiler checks may still have issues that need to be resolved. If best practices weren't employed then code may not perform well, may not be scalable, or may not be secure. Such code is an accident waiting to happen later on in the project lifecycle. For this reason, code needs to be reviewed. Static code analysis allows code to be reviewed early and often during development thus saving time and resources later on in the project lifecycle. Part 1: The basics of running code analysis 1. Here you will run the static code analyzer as part of a build. You will learn how to get help about a rule violation and use this information to decide whether to exclude or suppress a rule, whether to set a rule violation as an error rather than a warning, and how to fix a rule violation. 2. Create a subdirectory in c:\ and name it StaticCodeAnalysis. Download CalcDemo.zip from the SharePoint site into the new StaticCodeAnalysis directory. Unzip the file and familiarize yourself with the code (there isn’t much there!). 3. Add a new subfolder in your Team Project’s SCC repository and name it StaticCodeAnalysis. Create a workspace mapping from this folder to the directory with the same name on your c: drive. Make this workspace mapping current. Right click on the solution and check the code into source control. 4. Open up the project properties for Calc and set up code analysis for builds. Select the “Microsoft All Rules” ruleset. Do a build and check code analysis results. 5. The next thing to consider is whether there are any rules to exclude from static analysis. Are there any rules you feel might be irrelevant? If so, open the “Microsoft All Rules” ruleset and deselect the rule or rule group to exclude. Those rule violations should disappear. You will need to save your changes in your own custom ruleset. Make sure you select your custom ruleset is selected and do a build. 6. Rather than exclude the rule altogether, you may just want to suppress it. You can do this by right-clicking on the rule violation and selecting the "Suppress message(s)" menu item. Try suppressing rules that correspond to rule violations that you want to suppress. Suppress one rule in the source and another one in the GlobalSuppressions.cs file. Note: if the rule does not correspond to specific code in a .cs file, your only option will be the GlobalSuppressions.cs file. 7. Now take a look at the remaining rule violations and decide whether they should stay warnings or changed to errors (if you feel they are serious enough to stop the build process). To do this, open your custom ruleset and change the rule or rule group status from "Warning" to "Error" by clicking on it. Save your changes. 8. Finally, actually fix all code that causes any rule violations. When you can perform a clean build on the project, you are done. Add your ruleset file to your solution and check in all pending changes. Part 2: Integration between static code analysis and Team Foundation Server 1. In part 2, you will associate a work item with a rule violation and create a check-in policy that mandates a successful static code analysis must run before files are checked-in to version control. 2. Modify your code so that you have a couple “error” rule violations and run static code analysis again. 3. Right-click on a rule violation and select the "Create Work Item->Task" menu item. Set the "Activity" to be "Development" and assign the work item to yourself. Check in your code. 4. Run the "My Tasks" query from Team Explorer to verify that the work item was added. 5. Add the Code Analysis check-in policy. Select your custom ruleset (in source control) to configure static code analysis. 6. Make a small change to your code that will cause a code analysis error. Attempt to check it in. What happens? 7. Fix your static code analysis error and check-in again. What happens? It should succeed, as the check-in policy dictates that static code analysis should succeed (no errors but warnings are OK) before check-in occurs. Part 3: Deploying a Custom Rule 1. Download and build the CustomCodeAnalysisRules project from the sharepoint site. Take some time to familiarize yourself with the code. Note the FXCop namespace Microsoft.FxCop.Sdk. Note the references to the required FXCop assemblies FxCopSdk and Microsoft.Cci. They are all located under the C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Static Analysis Tools\FxCop directory. Take a look at the XML file that provides the UI information for the rule. (Note: Depending on your op sys…the files may be located in different—but similar paths) 2. After you are familiar with the code deploy the rule assembly (dll file) to where FxCop looks for it: C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Static Analysis Tools\FxCop\Rules. 3. Now introduce a public field in the calc.h class, run code analysis and see if the rule violation appears when you run static code analysis. (Note: if it doesn’t, make sure the rule shows up in the code analysis section of the calc.dll properties and is checked.) Part 4: Dynamic Code Analysis Goals: Set up a performance session Use the reports from the sampling profiler to predict problem areas Use the reports from the instrumentation profiler to narrow down and fix problem areas Overview: In this part of the lab, you will use the Visual Studio for Team Developer Edition's profiler tool to find performance hotspots in some poorly written code. Setting up an instrumentation performance session Download the PerformanceStarterCodeCSharp.zip file from the SharePoint site and familiarize yourself with the code. Have a quick look at how the test harness application seeds the StockHelper class with some pseudo-random Stock updates and then calls the StockHelper class to perform certain operations. For each stock, it finds how many stock updates were above a certain price and then calculates the average price and standard deviation of prices. After looking at the code, you probably have a feeling that it has performance issues. We will use the profiler tool to investigate. Be sure to rename all your profile reports appropriately. Under the Analyze tab, select Performance and Diagnostics. Follow the wizard steps to add a performance session to your solution. Ensure that the performance session is using instrumentation and both the .exe and .dll projects (targets) are being analyzed. Launch the performance session and inspect the results. What do you notice? Take any of the "Top Exclusive Sampled Functions" and inspect them more closely as this is where most samples were taken and hence most time was spent. Try and figure out where they are being called from. Often, it is helpful to have a performance report capturing object allocation. Rename your (only) profiling report to something meaningful. Right click on the performance session and select properties. Ensure that the performance session is using instrumentation and capturing memory allocation and lifetime information. Launch the performance session and inspect the results. What do you notice? Does this point to excessive memory usage for certain types? Does it point to a particular method as the culprit? Before going further in the lab, rename this report something meaningful. There seem to be a lot of Stock instances and string instances. Are these all really necessary? The performance sessions point to the StockHelper->GetStockDisplayString and StockHelper>GetFromStockHistoryWithHigherValue functions as likely culprits. Take a look at the code for these methods and try figuring out what the problems are and how they could be fixed. Here are a couple hints: Look for inefficient string concatenation allocating far too many temporary objects. Consider using the StringBuilder class. Look for inefficient string comparisons. String.Equals uses the default version of String.Compare which compares with regard to culture. If this is not necessary, consider using a more targeted version of String.Compare. Look for unnecessary boxing/unboxing of the Stock value type. Note: the ArrayList collection holds and returns Object references. Consider using List<>. Look at the looping code, e.g. repeatedly accessing the properties of an object for every loop iteration when they don't change for any loop iteration. Sample Code follows: public Stock[] GetFromStockHistoryWithHigherValue(Stock stock) { List<Stock> tmp = new List<Stock>(); string name = stock.Name; int price = stock.Price; foreach (Stock s in stocks) { if (string.Compare(s.Name, name, StringComparison.Ordinal) == 0 && s.Price > price) tmp.Add(s); } return tmp.ToArray(); } public string GetStockDisplayString(Stock[] stocks) { int len = stocks.Length; StringBuilder sb = new StringBuilder(); for (int i = 0; i < len; i++) { sb.Append(stocks[i].Name); sb.Append("("); sb.Append(stocks[i].Price); sb.Append(")"); if (i != len - 1) sb.Append(","); } return sb.ToString(); } Modify your code and run performance sessions for both timing and memory allocation reports. Compare the new report results to your baseline reports.