MASARYK UNIVERSITY F A C U L T Y O F INFORMATICS Custom Roslyn Tool for Static Code Analysis MASTER'S THESIS Be. Zuzana Dankovcikova Brno, Spring 2017 MASARYK UNIVERSITY F A C U L T Y O F INFORMATICS Custom Roslyn Tool for Static Code Analysis MASTER'S THESIS Be. Zuzana Dankovcikova Brno, Spring 2017 This is where a copy of the official signed thesis assignment and a copy of the Statement of an Author is located in the printed version of the document. Declaration Hereby I declare that this paper is m y original authorial work, which I have worked out o n m y o w n . A l l sources, references, a n d literature used or excerpted during elaboration of this work are properly cited and listed i n complete reference to the due source. Be. Zuzana Dankovcikovä Advisor: Bruno Rossi, P h . D . i Acknowledgement I w i s h to thank m y advisor Dr. Bruno Rossi for his constant feedback and valuable advice d u r i n g the w o r k on this thesis. I w o u l d also like to thank m y consultants Vit Svoboda and Petr Svirak, w h o never hesitated to explain the dark corners of the Kentico C M S solution. Furthermore, to Vit Svoboda for his incredible diligence and patience w h e n deploying the analyzers. Finally, I want to thank m y parents and m y boyfriend Pavol for their love, support and patience not only in the last year but also throughout the whole course of m y studies. ii Abstract The aim of this thesis is to create a custom Roslyn tool for static code analysis that w o u l d check the compliance to the internal coding rules and guidelines followed by developers at Kentico Software, providing an automated code fix where possible. The theoretical part of the thesis discusses the theory behind the static code analysis, lists the tools available for Microsoft . N E T platform, and focuses on the . N E T C o m piler Platform explaining h o w it can be leveraged to write custom analyzers and code fixes. The practical part presents selected details of the implementation including utilized design patterns. Furthermore, it discusses w h y the performance of the analyzers is important, h o w it was measured and optimized. iii Keywords static code analysis, software quality, Roslyn, .NET Compiler Platform, C#, compilers, code review, Kentico, analyzer, code fix iv Contents 1 Introduction 1 2 Compiling Source Code 4 2.1 Lexical Analysis 5 2.2 Syntax Analysis 6 2.2.1 Error H a n d l i n g 8 2.3 Semantic Analysis 9 2.4 Intermediate Code Generation 10 3 Static Code Analysis 11 3.1 Source Code vs. Compiled Code Analysis 11 3.2 Analyzing Source Code 12 3.2.1 Building a M o d e l 12 3.2.2 Performing the Analysis 13 3.2.3 Rules 14 3.2.4 Reporting the Results 14 3.3 What Problems It Can Solve 14 3.4 Advantages 15 3.5 Disadvantages 16 3.6 Static Code Analysis Tools Available for .NET 17 3.6.1 FxCop 17 3.6.2 StyleCop 17 3.6.3 CodeRush Classic 18 3.6.4 Resharper 18 3.6.5 Analyzers Written w i t h Roslyn 18 4 The .NET Compiler Platform 20 4.1 The Compiler Pipeline 20 4.2 The .NET Compiler Platform's Architecture 22 4.2.1 Compiler APIs 23 4.2.2 Workspaces APIs 24 4.2.3 Feature APIs 24 4.3 Syntax Tree 24 4.4 Semantics of the Program 26 4.5 Analyzers and Code Fixes 27 4.5.1 Diagnostic Analyzer 28 v 4.5.2 Code Fix Provider 30 4.5.3 Deployment 31 5 Implementation of Custom Analyzers 32 5.1 The Original BugHunter 32 5.1.1 Need for Semantic Analysis 33 5.1.2 Ease of Use 34 5.1.3 Suppressing the Results 34 5.2 Defining Analyzer Categories 35 5.2.1 Abstraction over Implementation 35 5.2.2 C M S A P I Replacements 37 5.2.3 C M S A P I Guidelines 37 5.2.4 C M S Base Classes 37 5.2.5 String and Culture 37 5.3 Strategy Classes for API Replacement Analyzers 38 5.3.1 Configuration 38 5.3.2 Analyzer for Member Replacement 39 5.3.3 Analyzer for M e t h o d Replacement 40 5.4 Strategy for Method Invocation Analysis 41 5.4.1 Template M e t h o d Pattern 41 5.4.2 Usage 44 5.4.3 Analyzers for String and Culture Checks . . . . 44 5.5 Code Fixes 45 5.6 Tests 46 5.6.1 Referencing Kentico Libraries i n Tests 46 5.6.2 Faking File Information of Documents 47 5.6.3 Failing Tests on Uncompilable Sources 47 5.6.4 Tracking the C M S A P I Changes 47 6 Measuring and Optimizing the Performance 49 6.1 CMS Solution Size 49 6.2 Measuring the Performance 50 6.2.1 Performance of Separate Analzyers 51 6.3 Optimizations 53 6.3.1 O p t i m i z i n g the SystemlOAnalyzer 53 6.4 Impact on Build Times 58 6.5 Tool Evaluation 59 v i 7 Conclusion 60 A List of Attachments 64 B Questionnaire 65 C Installation and Configuration 67 C . l NuGet packages with BugHunter Analyzers 67 C.2 Configuration 68 C.2.1 Rule Set File - Project Level Settings 68 C.2.2 Disabling Analyzers i n Code 69 vii List of Tables 5.1 Analyzers sorted into categories 36 6.1 Statistics about the projects with installed analyzers 50 viii List of Figures 2.1 The compiler phases, adopted from [1] 5 2.2 Abstract syntax tree 7 3.1 The process of static analysis, adopted from [2] 12 4.1 Compiler pipeline, adopted from [16] 21 4.2 . N E T Compiler Platform architecture, adopted from [15] 22 4.3 Syntax tree of an invocation expression 25 4.4 A n example of a code fix live preview 30 5.1 Class diagram of ApiReplacementForMemberAnalyzer 40 5.2 Class diagram of MethodlnvocationAnalyzer depicting the template method design pattern 42 5.3 Object diagram of EventLogArgumentsAnalyzer using inner class deriving from MethodlnvocationAnalyzer 44 6.1 Diagram for Report-Analyzer.psl script 52 6.2 Activity diagram of SystemlOAnalyzer 56 6.3 Box plots of execution times for different versions of the SystemlOAnalyzer (each dataset contains one hundred measurements) 57 6.4 Box plots comparing the M S B u i l d time of the C M S solution w i t h and without BugHunter analyzers (each dataset contains one hundred measurements) 58 C . l Setting the analyzer's severity for project 68 ix 1 Introduction Software bugs have been around for as long as the software itself. Even for a p r o g r a m that has been thoroughly tested, the bugs are inevitable. The longer they are present i n the code, the more expensive and difficult it is for them to be removed. Every year, billions of dollars are lost due to software that does not perform as expected. W h i l e the conformance to the customer requirements is mostly validated by functional testing, there is more to software quality than that. To cover the attributes like maintenance, readability, reusability or testability, that directly affect customers' perception of product's quality, other inspection techniques need to be involved as well. There are different possibilities ranging from formal inspections of the code, peer code reviews, pair programming, to use of automated tools for static code analysis, that can be applied to ensure the quality of the code. The key to achieving quality software lies i n the combination of these techniques. With the ever-growing sizes of the software products, arose the need for automated tools that w o u l d help with identifying code issues. A p plying static code analysis on a software project brings a first form of code review before the software is even r u n . They can be applied i n the earliest stages of the software development lifecycle. Unlike testing, static code analysis can be done on unfinished or even uncompilable source code. Moreover, it identifies the root causes of the bugs, unlike dynamic testing w h i c h only points out their consequences. Use of static code analysis tools provides an obvious solution because they excel at unveiling bugs that are commonly missed by unit, integration or system testing. Furthermore, they are cheaper, repeatable, and more efficient than manual code reviews. For a software company like Kentico, that develops a product for thousands of other developers and marketers building their o w n business on the top of Kentico's C M S , h i g h source code quality is extremely important. It means faster time-to-market, less maintenance effort, fewer bugs, and therefore reduced costs for customer support. It enables engineers to focus on developing new features rather then dealing w i t h unmaintainable code base. 1 i . INTRODUCTION Over the course of last ten years, many internal guidelines and best practices were established at Kentico. Since it became increasingly harder to keep a track of all those rules d u r i n g the m a n u a l code reviews, a console application B u g H u n t e r was developed. It checked for the most common mistakes and violations of internal rules within the Kentico solution. The checks were performed by simple string matching. Even though its simplicity shrank the initial costs of developing such a tool to m i n i m u m , the reliability paid the price. Occasional false positive results meant that some pieces of the code had to be written i n an obscure way i n order to pass the BugHunter check. D u e to lack of any semantic analysis, many issues were not detected at all, and with false negatives slipping also through the code reviews, this could have caused bugs i n the production. The goal of this thesis is to create a n e w static code analysis tool, that w o u l d replace the original C# checks i n BugHunter, and w o u l d mitigate the flaws of the previous solution. The tool should be written using the n e w . N E T Compiler Platform (code-named "Roslyn") and should be integrated to both Visual Studio and the continuous integration process at Kentico. The new BugHunter analyzers should raise warnings for code that does not c o m p l y to Kentico's internal guidelines. Where suitable, the tool should also provide an instant code fix. Since the tool w i l l be deployed i n the production, it needs to be thoroughly tested and be efficient, so that it does not degrade the developers' experience. The implemented analyzers w i l l not be concerned w i t h naming or ordering conventions, since there are already many (also Roslyn-based) tools dealing w i t h these problems. These can be easily configured to specific needs of the development team. Instead, the n e w analyzers w i l l only focus o n the rules w h i c h require tailored approach that cannot be found i n other available solutions. Discussing the thesis structure, the second chapter provides a theoretical background behind the static code analysis tools i n general source code compilation. It shortly describes the phases of the compilation process and focuses on the analysis w h i c h is fundamental for any static code analysis tool. In the third chapter, the concept of the static code analysis is described. It informs the reader about the possibilities of analyzing the compiled code and then focuses o n the analysis of the source code. It 2 i . INTRODUCTION provides an overview of the problems that can be solved by static code analysis, and lists the advantages and disadvantages. The last section presents the tools for static code analysis available for . N E T platform. The fourth chapter of the thesis concludes the theoretical part by introducing the . N E T Compiler Platform w h i c h was utilized during the development. It gives an insight into the internals of the CSharp compiler, familiarizes the reader w i t h the immutable data structures used when working with Roslyn, and demonstrates how the Diagnostic APIs (Application Programming Interfaces) can be applied to build custom static code analyzers and code fixes. The second part of the thesis is concerned with the implementation of the new BugHunter analyzers. In the fifth chapter the original BugHunter application is introduced, along w i t h its issues, that are being addressed by this thesis. It defines the analyzers' categories and shows some of the design patterns that were utilized during the development. The chapter shortly mentions the implemented code fixes and discusses the importance of tests. Lastly, the sixth chapter focuses on the performance aspect of the developed tool. It explains h o w the performance of the separate analyzers was measured and specifically points out the iterative optimization of one particular analyzer. It also assesses the impact of the analyzers on b u i l d times and summarizes the views of the development team on the new tool. 3 2 Compiling Source Code In this chapter, the theory behind the source code compilation is explained. It focuses primarily o n the analysis part of a compilation process which is fundamental for the rest of the thesis as it is a concept behind all static code analysis tools. A s per [1], a compiler is a p r o g r a m that can read a p r o g r a m i n a source language and translate it into a semantically equivalent program i n a target language while reporting any errors detected during the translation. The compiler may sometimes rely o n other programs. For example, preprocessor is responsible for collecting the source code to be fed to the compiler by expanding shorthands (macros) into source language statements. The compilation process can be divided into two parts: analysis and synthesis; commonly referred to as front end and back end of the compi- ler. The purpose of the analysis part is to break u p the source program into chunks and b u i l d u p a grammatical structure that it corresponds to, based o n the source language grammar. This structure is subsequently transformed into a n intermediate representation of the source program. A l o n g the way, the compiler collects information about the program and stores it into a data structure called symbol table. If any errors i n syntax or semantics are encountered, analysis part shall inform the programmer about the problem. Otherwise, both intermediate representation and symbol table are passed to the synthesis part where they are used for the construction of the target program. The two m a i n steps of compilation process internally consist of different phases as s h o w n i n Figure 2.1. Each phase transforms one representation of source language into another, and passes it to the following phase while working w i t h the symbol table during the process. In synthesis phase, a n optional machine-independent optimizations can take place and are done on the top of the intermediate representation. After the target machine code is generated, additional machinedependent code optimizations are performed. For the purpose of this thesis, mainly the analysis part is relevant and the following sections w i l l elaborate o n its respective phases. 4 2. COMPILING SOURCE C O D E character stream Lexical Analyzer token stream Syntax Analyzer syntax tree (AST) T3 01 4-» o Semantic Analyzer CD ^ , / syntax tree (AST) ^ ^ ^ ^ ^ • 1 Intermediate Code Symbol ^^^S^BH Generator Table 1 intermediate representation— T 1 Machine-Independent J Q ^ ^ ^ ^ Code Optimizer ntermediate representation Code Generator target-machine code Machine-Dependent Code Optimizer I target-machine code T3 C 01 u CD 01 Figure 2.1: The compiler phases, adopted from [1] 2.1 Lexical Analysis The compilation process starts with lexical analysis or scanning. The scanner transforms a stream of characters of the source program, as written by the programmer, into a series of meaningful sequences called lexemes. Most programming languages allow for an arbitrary number of white spaces to be present i n the source text to aid readability. However, white spaces, similarly as comments, are unimportant for the target code generation itself, and thus the lexical analyzer is responsible for discarding them completely. 5 2. COMPILING SOURCE C O D E In order to be able to correctly recognize the lexeme, lexical analyzer may need to read ahead. For example, i n C - l i k e languages if the scanner sees < character, it cannot decide whether it is a lexeme for "less than " operator or it is s part of "less than or equal to" lexeme. In order to do that, it needs to read ahead and see if the following character is = or not. Reading ahead is usually implemented with an input buffer which the lexical analyzer can read from and push back to. A s pointed out i n [1], the use of buffer also boosts the performance since fetching block of characters is more efficient than fetching one at a time. The lexical analyzer typically uses regular expressions to identify the lexemes and for each lexeme, it outputs a token (or token object) of the form (token-name, attribute-name). (2.1) For a n input sequence total = 42 + base * interest (2.2) the scanner output could be (id, 0) (=) (num, 1) (+) (id, 2) (*){id, 3). (2.3) Lexemes can be d i v i d e d into logical groups such as identifiers, relational operators, arithmetical operators, constants or keywords as seen i n the example above. The scanner often uses regular expressions to identify tokens. Each identifier (id) has an attribute w h i c h points to the entry of the symbol table, where information about identifier name, type or position i n the source text is stored. Similar holds for constants like 42 i n the example. In the (2.3) example, the assignment and addition symbols d o not have attributes, but different representation can be used, such as (bin-op, 2). In this case, bin-op w o u l d denote it is a binary operator. A number two w o u l d be a pointer to the symbol table w i t h all the symbols for binary operations, while the second index suggests that it represents a n addition. 2.2 Syntax Analysis The stream of token objects along w i t h partially populated symbol table is a n input for the subsequent compiler phase - syntax analysis, 6 2. COMPILING SOURCE CODE (id, 2) (id,3) Figure 2.2: Abstract syntax tree or parsing. The parser has to verify that the sequence of token names can be produced by the source language grammar. For a well-formed program, it shall output a syntax tree, often referred to as a n abstract syntax tree (AST)1 . The resulting A S T for the token stream generated i n (2.2) is depicted i n Figure 2.2. The tree shows h o w multiplication precedence rule of the grammar has been applied o n the expression. The syntax analyzer uses a context free grammar (CFG) to form the syntax tree. The C F G is defined by a 4-tuple consisting of [1]: Terminals - token names (first component of the token) as obtained from the previous compilation step. Nonterminals - syntactic variables that help to impose the hierarchical structure of the language and represent set of strings. Start symbol - a special nonterminal w h i c h set of strings represents the language generated by the grammar. Productions - rules that specify how nonterminals can be rewritten to sequences of zero or more terminal and nonterminal symbols. 1. The AST is an intermediate representation of the source program in which each interior node represents an operation (programming construct) with the children of the node representing the arguments of that operation. A s opposed to parse syntax tree, i n which interior nodes are nonterminals of the grammar, ASTs are more lightweight and they might omit some nodes which exist purely as a result of grammar's production rules [2]. 7 2. COMPILING SOURCE C O D E A n example of a production denoting the construction of a whilecycle w o u l d be stmt —> while ( expr ) { stmt}, (2.4) where nonterminals stmt and expr stand for a statement and expression respectively (defined further by other productions). Symbols i n bold represent terminals of the grammar (open and close parenthesis, and curly braces, while keyword). 2.2.1 Error Handling There are several types of errors that can be encountered during the compilation process: • lexical errors, such as misspelling the identifier name, • syntactic errors, for example a missing semicolon, • semantic error, like incorrect number of function arguments, • logical errors, that do not really prevent the p r o g r a m f r o m compiling, but can indicate possible mistakes (for instance using the assignment operator = instead of the comparison operator == i n condition of an if-statement}. It is parser's responsibility to report the presence of potential syntactic error and recover from the error i n order to continue w i t h syntactic analysis and be able to detect any subsequent errors. In [3], two m a i n strategies for the error recovery are presented: panic-mode and phrase-level recovery. Panic-Mode Recovery In this method, after the parser encounters an error, it searches for a synchronizing token (usually delimiters such as semicolon, close brace or end keyword) and, until found, all the symbols are thrown away one by one. Even though panic-mode recovery often discards significant amount of input while searching for the synchronization token, it is guaranteed not to end u p i n an infinite loop. Phrase-Level Recovery Another approach the parser can take to recover f r o m an erroneous input is to try to perform a local correction. This can be achieved b y 8 2. COMPILING SOURCE CODE replacing the prefix of the following input by some tokens that would enable syntactic analyzer to continue parsing. A prime example of phrase-level recovery is inserting a missing semicolon or replacing comma w i t h a semicolon. Even though this technique is very powerful, as it can cope w i t h all possible problems i n the input, it might lead to infinite loops (e.g. always inserting symbols ahead of the current symbol). 2.3 Semantic Analysis A l t h o u g h the syntax analysis is able to check the conformance of the program to the grammar of the source language, it is not an ultimate tool. Some language rules cannot be implied by C F G and an additional step is needed to ensure semantic consistency. To do this, the semantic analyzer uses the A S T and the information from symbol tables collected i n the previous phases. While working, it can also add more details about symbols or even modify the AST. A vital part of semantic analysis for any statically typed language2 is type checking. Semantic analyzer has to ensure, that each operator is applied to matching operands. For example, a multiplication operator can be called w i t h either a pair of integers or a pair of floating-point numbers, that also implies the result of the operation. If the semantic analyzer encounters an expression where multiplication is used w i t h numbers of different types, it must perform a type conversion called coercion. To coerce the integer into floating-point representation, it may be necessary to alter the A S T and insert an additional node to explicitly state that integer should be treated as floating-point [1]. Semantic analyzer utilizes the information f r o m symbol table to perform all sorts of other checks, to prevent semantic errors such as: • wrong arguments - number and types of arguments applied to a function call, • multiple declaration - variable w i t h the same name declared more than once per scope, • undeclared variable - usage of a variable before its declaration. 2. In statically typed language, type errors are reported by compiler during translation process, whereas in dynamically typed languages conversions between incompatible types are only discovered during runtime and can cause program failure. 9 2. COMPILING SOURCE C O D E 2.4 Intermediate Code Generation The semantic analysis is followed b y an intermediate code generation which completes the front end part of the compilation process. Depending o n the specific compiler implementation, the intermediate representation (IR), that is the result of this phase, can take different forms. The IR should be easy to produce and easy to translate into the target machine code. For some compilers, the IR may be the abstract syntax tree itself. Together w i t h symbol table, IR is passed to the back end part of the compiler - synthesis, where machine independent optimizations can be performed. These contain control flow analysis where control flow graph is constructed a n d utilized i n the subsequent data flow analysis. A s a result of these optimizations, compiler might remove dead code from the IR or perform other optimizations that w i l l lead to shorter and more efficient target code [1]. The following chapters o n static code analysis a n d . N E T C o m p i l e r Platform w i l l build u p o n the fundamentals presented here and show how these concepts are relevant w h e n considering an implementation of a static code analyzer. 10 3 Static Code Analysis This chapter introduces the concept of static code analysis. It compares the analysis of source and compiled code, and focuses o n the source code analysis, listing its advantages a n d disadvantages. The e n d of the chapter provides an overview of static code analysis tools available for . N E T platform. A s per [4] a n d [5], static code analysis refers to a process of assessing the program based o n its form, structure, content and documentation; a n d reasoning over its possible behaviours without actually executing the code. The aim of static code analysis is to check the compliance to specific rules and identify parts of the program that might lead to possible vulnerabilities. The term static code analysis is mostly used when speaking of an automated tool. In contrast, code inspections or code reviews are performed by humans and can benefit from using static code analysis tools. 3.1 Source Code vs. Compiled Code Analysis There are two different approaches w h e n analyzing a p r o g r a m b y an automated tool [2]: analyzing the source code (as seen by the compiler), or analyzing the compiled code - either some form of byte code 1 or an executable. Sometimes it might be very complicated, or even infeasible, to obtain the actual source code of the p r o g r a m to be analyzed a n d the only possibility is to analyze the executable code. W h e n the tool is looking at a compiled version of the program, the ambiguity of h o w the source code w i l l be translated by the compiler is removed. However, analyzing compiled code can be very complex. Even if the tool manages to decode the binary, it lacks the original type information. Moreover, the optimizations performed by the compiler obscure the original meaning of the p r o g r a m a n d m a k i n g sense of semantics out of implementation may be unattainable. Likewise, if 1. A n intermediate representation of a program, also known as "portable code", which is often an input for just-in-time compilation by interpreters. 11 3- STATIC C O D E ANALYSIS the error is found, reporting it to the programmer can be challenging since there is not always a clear mapping from binary back to source. Although the above-mentioned complications speak clearly against analyzing binaries, the situation is different w h e n analyzing byte code formats (such as Java bytecode), where the type and debugging information is present. The rest of the thesis focuses solely o n source code analysis and the following sections discuss the theory behind it. 3.2 Analyzing Source Code There are many tools for static code analysis and each can be concerned w i t h different attributes of the program. However, for a majority of them, the basic structure looks the same, as depicted i n Figure 3.1. Source code Build a model _ Perform analysis Report results Predefined rules Figure 3.1: The process of static analysis, adopted from [2] 3.2.1 Building a Model In order to analyze the program, the analysis tool must first understand it. Therefore, the initial task is to create a structured model that represents the source code. This m o d e l has a lot i n c o m m o n w i t h the A S T and symbol tables that were discussed i n Chapter 2. In fact, model building phase of static code analyzers closely mimics the front end part of the compilation process, executing lexical analysis, parsing and semantic analysis. 12 3- STATIC C O D E ANALYSIS 3.2.2 Performing the Analysis After obtaining the model, the next step is to perform the actual analysis. M a n y different algorithms can be applied i n this step and it is c o m m o n that they are combined into one solution. The approaches are often derived from techniques used by the compilers, specifically: Control Flow In order to explore different execution paths that can take place w h e n the p r o g r a m is executed, the static code analysis tool can construct a control flow graph o n the top of the AST. The nodes of the graph represent basic blocks - sequences of program instructions that w i l l be all executed once the block is entered. The edges between basic blocks represent different paths that the program can take depending on matched conditions. A n y back edges i n the graph signal potential loops i n the program execution. Tracking Data Flow Data flow analysis is used to examine how data passes through the program. Compilers utilize data flow analysis w h e n doing code optimizations i n order to remove unreachable code and allocate registers. A n example of h o w data flow analysis can be used by static analysis tools is to check that m e m o r y is always freed only once - function f ree(p) was called at most once w i t h the address stored i n pointer p. Taint Analysis According to [6], taint analysis attempts to identify variables containing possibly tainted user input using the data flow analysis technique. If these variables are used as arguments to vulnerable functions without being sanitized first, the tool reports their usage as vulnerable. The taint propagation analysis is particularly relevant for security analysis, a prime example being the detection of a potential S Q L injection. 1 3 3- STATIC C O D E ANALYSIS 3.2.3 Rules A s stated i n [7]: "...if a rule hasn't been written yet to find a particular problem, the tool will neverfind that problem." This implies that the rules that specify what the static analysis tool should report are just as important (or even more important) as the heuristics and algorithms implemented by the tool. Best tools for static code analysis externalize the rule set i n order to easily a d d , remove or alter the rules, without modifying the tool itself. 3.2.4 Reporting the Results A n often overlooked part of the static analysis is the result reporting. The [8] asserts, that if a programmer cannot understand the output of the static analysis, the results are effectively useless since a misunderstood explanation ends u p w i t h error being ignored or, worse, interpreted as a false positive. A s discussed i n [2], a good static analysis tool should provide means of grouping and sorting the results, suppressing the unwanted results (either directly i n the code with pragmas or code annotations, or alternatively i n a configuration file) and mainly explaining the results. Every issue that is detected by the tool should provide a short title followed by a detailed description of the problem, severity of the issue, recommendations o n h o w the problem can be fixed, and possible further references to the topic. The tool can additionally provide a confidence level estimating the likelihood that the finding is really correct. 3.3 What Problems It Can Solve There are different types of problems a static analysis tool can tackle. This section enumerates some of the categories applicable to static code analysis, as listed i n [2]. Type C h e c k i n g The integral part of every compiler for statically typed language. Rules are typically implied by the language itself. 1 4 3- STATIC CODE ANALYSIS Style Checking The style checker defines rules for spacing, naming, commenting and general program structure that affect mostly the readability and the maintainability of the program. Program Understanding These tools a i m to provide a high-level p r o g r a m understanding beneficial mainly for larger codebases. They are most effective w h e n integrated into the I D E s 2 where they can support go to declaration or find all references features, or even automatic program refactorings such as renaming or extracting a variable. Bug Finding The purpose of these type of static analyzers is to point out common mistakes i n the code. They report warnings i n parts of the p r o g r a m that are compliant w i t h the language specification but might not express the programmer's intent, such as ignoring the return value of a function call. Special type of b u g finding checker is security review, where specific vulnerabilities f o u n d i n the source code are reported. Security review searches for possible exploitations like buffer overflow or tainted inputs. 3.4 Advantages One of the key factors that advocate the use of tools for static analysis is h o w early i n the development process they can be applied. A s opposed to dynamic testing, static code analysis can be performed on unfinished or even uncompilable code. The longer the defect stays i n the system, the more damage it can cause and the higher are the costs of fixing it. A s stated i n [9, p. 29], the costs of fixing a defect introduced during construction of a program are 10-times higher if detected during system testing and 10 to 25-times higher i n production, than it w o u l d be to fix it while still i n development. Therefore, it is desirable 2. Integrated Development Environments 15 3- STATIC CODE ANALYSIS to detect bugs as early as possible, w h i c h is where the static code analysis can be leveraged. Static inspections detect symptoms together w i t h causes, whereas testing only points out the symptoms w i t h further effort required to find the source of the problem before it can be fixed [9, p. 472]. Manual code inspections can be very time-consuming and require high level of expertise f r o m the reviewer. Static code analysis helps to make the code review process more efficient by checking for wellk n o w n issues w h i c h do not have to be considered d u r i n g the code review. Another advantage of automated code analysis is repeatability and scalability. Code analysis tool can be part of continuous integration3 (CI) process and can be also integrated to programming IDEs. A s such, they are great for programmers w h o get instant feedback and learn more about mistakes they made. The tools enforce higher code quality and guidelines compliance. A s a result, the code should be more consistent, maintainable a n d easier to debug. 3.5 Disadvantages The Rice's theorem [11] says, that any non-trivial question about program's semantics is undecidable. A s a consequence, there w i l l never be a static analysis tool able to answer all the questions perfectly. The tools can produce false positives (a problem w h i c h does not actually exist is reported) and false negatives (the program contains a problem, but it was not reported b y the tool). Prevailing complaints against static analysis tools concern false positives. A long list of false positives means real bugs can be overlooked and programmers can eventually lose trust i n the tool. Worse, from the security perspective, though, are the false negatives. Not only the bug was not found and might cause future problems, but they also provide a false sense of security to the programmers. A s presented earlier i n this chapter, a vast majority of code i n spection tools must build a model of the source program i n order to be 3. Process in which developers contribute regularly (multiple times a day) into a shared repository where the code is continuously being verified by an automated build and suite of automated tests [10]. 16 3- STATIC CODE ANALYSIS able to analyze it. This requires duplication of compiler's logic, w h i c h itself is fairly complicated, and there is no guarantee that the tool interprets the source exactly the same as the compiler does. Moreover, for the authors of the tool, it means the parsing logic has to be always up to date w i t h the language version i n use. 3.6 Static Code Analysis Tools Available for .NET Even if imperfect, static analysis tools are still a valuable asset i n software development process. This section presents tools for static code analysis that are commonly used on the . N E T platform. 3.6.1 FxCop FxCop is a free tool by Microsoft for analyzing managed code assemblies (targeting .NET Framework) for conformance to . N E T Framework Design Guidelines4 i n areas such as design, localization, performance, security, naming or portability. It includes more than 200 predefined checks and a possibility to add custom rules using FxCop S D K 5 . It is available i n two forms: fully featured application w i t h graphical user interface and a c o m m a n d line tool that is easily integrated into an automated build process. 3.6.2 StyleCop Another tool by Microsoft is an open source project StyleCop6 for analyzing C# code for conformance to style and consistency w i t h N E T Framework Design Guidelines. Unlike FxCop, StyleCop analysis is performed on the source code, which enables it to look for a different set of style violations. The rules are d i v i d e d into categories such as documentation, naming, ordering, spacing and readability. Some of the rules are: placing the opening curly brace on a n e w line, spaces around binary operators, method names starting w i t h an upper-case. The tool is configurable and development team can specify its o w n style to be checked, for example, to enforce spaces over 4. https://msdn.microsoft.com/en-us/library/ms229042(v=vs.110).aspx 5. Software Development Kit 6. https://github.com/StyleCop/StyleCop 1 7 3- STATIC CODE ANALYSIS tabs. It is available either as a Visual Studio extension or as a N u G e t package that can be installed to the project. 3.6.3 CodeRush Classic CodeRush Classic7 is a solution-wide static code analysis tool for V i sual Studio by vendor DevExpress. It enhances the I D E w i t h more advanced features like assembly decompilation, automated code generation, advanced code selection, code formatting and cleanup. The tool focuses on developer's productivity by not only finding bugs but also providing an automated way of fixing them. It provides an A P I enabling developers to extend the basic functionality w i t h 3rd party plugins such as spell checker or copy project. The CodeRush Classic provides static analysis not only for . N E T languages, but also for JavaScript, H T M L and X M L . 3.6.4 Resharper Very similar to C o d e R u s h , ReSharper is a Visual Studio extension for . N E T developers b y JetBrains8 . It analyzes code quality of C#, Visual Basic, ASP.NET, JavaScrtipt, TypeScript, CSS, H T M L and X M L . For each of these languages it is possible to define code style and formatting to make the tool compatible w i t h the coding standards followed by a development team. ReSharper provides hundreds of quick-fixes that solve discovered problems and has support for automated solution-wide refactorings. O n the top of static analysis there are additional plugins for performance (dotTrace) and m e m o r y (dotMemory) profiling, test runner and code coverage tool (dotCover) or . N E T decompiler and assembly browser (dotPeek). 3.6.5 Analyzers Written with Roslyn The tools described above have one aspect i n common - they all need to parse the code before they can analyze it. The cost of maintaining 7. https://www.devexpress.com/Products/CodeRush 8. https://www.j etbrains.com/resharper 1 8 3- STATIC CODE ANALYSIS a custom C# parser and keeping it up to date w i t h every new language version is fairly difficult, inefficient, and of course, costly. W i t h the release of new . N E T Compiler Platform (Roslyn), w h i c h is discussed in detail in the following chapter, the need for parsing C# and Visual Basic sources is eliminated and tools that build u p o n this platform can concentrate solely on the analysis itself. Some vendors, like JetBrains, w h o invested years of development into the creation of the tools, claim [12], it does not pay off to rewrite the whole program so that it uses new Microsoft compiler. Not only would it take an enormous effort to rewrite all the functionality to use the new framework, but they w o u l d also risk destabilizing the product and losing years of optimizations and testing. Moreover, ReSharper is multilingual tool whereas . N E T Compiler platform "only" provides C# and Visual Basic parsers. Other companies, such as DevExpress w i t h C o d e R u s h 9 or M i crosoft w i t h StyleCop Analyzers, decided to use this new approach. The effects on Visual Studio performance were immediate. Neither the solution has to be parsed by the tool, nor duplicate syntax trees need to be stored. A s a result, the load times and memory consumption were significantly lowered [13]. The Roslyn APIs also gave rise to more open source projects dealing w i t h static code analysis, such as C o d e C r a c k e r 1 0 . A s challenging as it was i n the past, static code analysis is n o w rather easy, thanks to powerful analysis APIs. The following chapter takes a look at the . N E T Compiler Platform and h o w it can be used to write custom tool for the static code analysis. 9. CodeRush Classic referrs to the version before Roslyn 10. h t t p s : / / c o d e - c r a c k e r . g i t h u b . i o 1 9 4 The .NET Compiler Platform In the . N E T w o r l d , the compiler used to be a black box that given the file paths to the source text, produced an executable. This perception was changed i n 2015 w h e n Microsoft introduced the . N E T Compiler Platform (commonly referred to as "Roslyn"). Not only have been compilers for both Visual Basic and C# rewritten into an entirely managed code1 , but they also expose the internals of the compiler pipeline v i a a public . N E T A P I . This makes them a platform (also k n o w n as compiler-as-a-service) with rich code analysis APIs that can be leveraged b y developers to perform analysis, code generation, or dynamic compilation i n their o w n programs [15]. Those can be then easily integrated into Visual Studio without the hard work of duplicating compilers' parsing logic. This chapter takes a look at h o w the Roslyn A P I layers are structured, h o w the original source code is represented b y the compiler, and h o w developers can b u i l d tools u p o n the compiler's A P I . Note, that although Roslyn provides equivalent APIs for both Visual Basic and C#, this thesis only focuses o n the latter since it is relevant for the practical part of the thesis. 4.1 The Compiler Pipeline The Roslyn compilers expose an A P I layer that mirrors the traditional compiler pipeline (see Figure 4.1). Instead of a single process of generating the target program, each compilation step is treated as a separate component [16]: • Parse phase consists of lexical analysis {scanner) a n d syntactic analysis {parser). First, the lexical analyzer processes the stream of characters from the source program and groups them into meaningful sequences called lexemes. Those are subsequently processed by the syntax analyzer that creates a tree-like structure of tokens based o n the language grammar [1]. 1. Managed code refers to a code written in one of the high-level programming languages available for use with Microsoft .NET Framework that require a Common Language Runtime in order to be compiled into machine code and executed [14]. 2 0 4- T H E . N E T COMPILER PLATFORM • Symbols and metadata phase where named symbols are generated based on the declarations from the source and imported metadata. • Bind phase i n w h i c h the identifiers from the source code are matched to their respective symbols. • Emit phase where all the gathered information is used to emit an assembly. Figure 4.1: Compiler pipeline, adopted from [16] In each phase, the N E T Compiler Platform creates an object model containing gathered information and exposes it through the A P I i n form of . N E T objects. These objects are also used internally by Visual Studio 2 to support basic IDE functionality. For instance syntax tree, that is the result of the parse phase, is used to support formatting and colorizing the code i n the editor. The result of the second phase - hierarchical symbol table, is the basis for Object browser and Navigate to functionality. Binding phase is represented as an object model that exposes the result of the semantic analysis and is utilized i n Find all references or Go to definition. Finally, the E m i t phase produces the Intermediate Language (IL) byte codes and is also used for Edit and Continue feature [16]. 2. The new generation of Visual Studios leveraging the Roslyn compiler are called vNext and the first one was VS 2015. 21 4- T H E . N E T COMPILER PLATFORM 4.2 The .NET Compiler Platform's Architecture The Roslyn's architecture consists of two main layers - Compiler and Workspaces APIs, and one secondary layer - Features A P I , as seen o n Figure 4.2. The .NET Compiler Platform Feature APIs Refactorings Code Fixes Figure 4.2: . N E T Compiler Platform architecture, adopted from [15] One of the key concepts of the . N E T Compiler Platform is i m mutability. The compiler exposes hundreds of types that represent all information about the source code f r o m P r o j e c t a n d Document to SyntaxTree w i t h almost all of those types being immutable. This means, that once created, the object cannot change. In order to alter it i n any way, n e w instance must be created, either manually, or f r o m an existing instance b y applying one of many W i t h O methods that the A P I provides. The immutability enables the compiler to perform parallel work without need to create duplicate objects or applying any locks o n them. This concept is useful for the c o m m a n d line compiler but it is considered extremely important for IDEs where it enables for one document to be handled by multiple analyzers i n parallel. 22 4- T H E . N E T COMPILER PLATFORM 4.2.1 C o m p i l e r A P I s A s discussed i n the previous section, the Compiler APIs offer an object m o d e l representing the results of syntactic a n d semantic analysis produced by the respective phases of the compiler pipeline. Moreover, it also includes an immutable snapshot of a single compiler invocation, along w i t h assembly references, compiler options, a n d source files. This layer is agnostic of any Visual Studio components, a n d as such can be used i n stand-alone applications as well. There are two separate, though very similar, A P I s for Visual Basic a n d C#, each providing functionality tailored for specific language nuances. Diagnostic A P I s Apart from parsing code and producing an assembly, the compiler is also capable of raising diagnostics, covering everything from syntax to semantics, and report them as errors, warnings or information messages [15]. This is achieved through the compilers' Diagnostics APIs that allow developers to effectively plug-in to compiler pipeline, analyze the source code using the exposed object models, and surface custom diagnostics along w i t h those defined b y the compiler itself. These APIs are integrated to both M S B u i l d 3 a n d Visual Studio (2015 a n d newer), providing seamless developer experience. Since the practical part of this thesis, and both Chapter 5 a n d 6, rely o n the Diagnostic APIs to provide custom diagnostics, they are discussed i n more detail in Section 4.5. Scripting A P I s A s a part of the compiler layer, Microsoft team has introduced n e w Scripting A P I s that can be used for executing code snippets. These APIs were not shipped w i t h . N E T Compiler Platform 1.0 and are part of v2.0.0 R C 3 4 . 3. The Microsoft Build Engine https: / / g i t h u b . com/Microsof t/msbuild 4. Release candidate 3, as per h t t p s : / / g i t h u b . c o m / d o t n e t / r o s l y n / w i k i / Scripting-API-Samples [02/26/2017]. 23 4- T H E . N E T COMPILER PLATFORM 4.2.2 Workspaces APIs Workspace represents a collection of solutions, projects, and documents. It provides a single object model containing information about the projects i n a solution and their respective documents; exposes all configuration options, assembly and inter-project dependencies; and provides an access to syntax trees and semantic models. It is a starting point for performing code analysis and refactorings over entire solutions. Although it is possible to use the Workspace outside of any host environment, the most common use case is an IDE providing an instance of Workspace that corresponds to the open solution. Since the instances of S o l u t i o n are immutable, the host environment must react to every event (such as user key stroke) w i t h an update of the C u r r e n t S o l u t i o n property of the Workspace. 4.2.3 Feature APIs This layer relies on both compiler and workspaces layers and is designed to provide A P I for offering code fixes and refactorings. Features APIs were also utilized while w o r k i n g on the practical part of this thesis. 4.3 Syntax Tree A s mentioned i n the previous sections, the product of the syntactic analysis is a syntax tree. It enables developers to work w i t h the code in a managed way instead of working against a plain text. Syntax trees are used for both analysis and refactorings, where the n e w code is generated either manually or as a modified version of the existing tree. Thanks to being immutable, syntax trees are thread-safe and analysis can be done i n parallel. It is important to point out, that i n a same way the compiler constructs a syntax tree from the source text, it is also possible to roundtrip back to the text representation. Therefore, the source information must be always preserved i n full fidelity. This means that every piece of information from source must be stored somewhere within the tree, including comments, whitespaces or end-of-line characters. This repre- 2 4 4- T H E . N E T COMPILER PLATFORM sents a major difference to the general concept of compilers discussed in Chapter 2. Figure 4.3 shows a syntax tree of an invocation expression as obtained from Syntax Visualizer5 extension available i n Visual Studio. This tool is useful for understanding h o w Roslyn represents particular language constructs and is widely utilized whenever one needs to analyze the code. Following sections explain what are the m a i n building blocks of such syntax tree, referring to Figure 4.3. Syntax Nodes Syntax nodes (blue color) are non-terminal nodes of a syntax tree, meaning they always have at least one other node or token as a child. Nodes represent syntactic constructs of a language such as statements, clauses or declarations. Each type of node is represented b y a single class deriving f r o m SyntaxNode. A p a r t f r o m c o m m o n properties Parent, ChildNodes and utility methods like DescendantNodes, DescendantTokens, or DescendantTrivia, each subclass exposes specific methods and properties. The InvocationExpression node has two properties, I d e n t i f ierName and ArgumentList both of which are SyntaxNodes themselves. 5. https://roslyn.codeplex.com/wikipage?title=Syntax°/0 20Visualizer Figure 4.3: Syntax tree of an invocation expression 25 4- T H E . N E T COMPILER PLATFORM Syntax Tokens A s opposed to nodes, syntax token (green color) represent terminals of the language grammar, such as keywords, punctuation, literals and identifiers. For the sake of efficiency, SyntaxToken is implemented as a value type (C# structure) and there is only one for all kinds of tokens. To be able to tell them apart, tokens have Kind property. For example, SomeFurictiori is of k i n d I d e n t i f ierName, whereas "(" character is OpenPareriTokeri. Syntax Trivia In order to enable refactoring features, syntax trees must also store i n formation about whitespaces, comments and preprocessor directives, that are insignificant for the compilation process itself. This information is represented by another value type - S y n t a x T r i v i a (white color). Trivia are not really parts of the tree itself, rather they are properties of tokens accessible by their L e a d i n g T r i v i a and T r a i l i n g T r i v i a col- lections. 4.4 Semantics of the Program A s explained i n Chapter 2, even though syntax trees are enough to describe the proper form of the program (compliance to the language grammar), they cannot enforce all language rules, for example type checking. In order to tell whether a method is called w i t h the right number of arguments, or operator is applied to operands of the right type, it is inevitable to introduce semantics. Its one of the core compiler's responsibilities to populate symbol tables w i t h information about all elements and their properties from the source program. Attributes such as identifier name, type, allocated storage, scope; or for method names the number and types of arguments a n d their return values; are all stored i n order to be utilized later w h e n producing the intermediate language. 26 4- T H E . N E T COMPILER PLATFORM Symbols In . N E T Compiler Platform, a single entry of a symbol table is represented by a class deriving from ISymbol. The symbol represents every distinct element (namespace, type, field, property, event, method or parameter) either declared i n the source code or imported as metadata from a referenced assembly. Each specific symbol has its o w n methods and properties often directly referring to other symbols. For example, IMethodSymbol has a ReturnType property specifying what is the type symbol the method returns. Compilation A n important immutable type, that represents everything needed to compile a C# (or Visual Basic) program is a Compilation. It contains all source files, compiler options and assembly references. The compilation provides convenient ways to access any discovered symbol. For instance, it is possible to access the entire hierarchical symbol table rooted by global namespace, or look up type symbols by their common metadata names. Semantic Model W h e n analyzing a single source file of a compilation, all its semantic information is available through a semantic model. It can answer many questions such as: • What symbol is declared at the specific location i n the source? • What is the result type of an expression? • What symbols are visible from this location? • What diagnostics are reported i n the document? This makes the semantic model very useful w h e n performing static code analysis concerned w i t h more than just syntax. 4.5 Analyzers and Code Fixes Thanks to the Compiler APIs it is possible for Visual Studio to provide live static code analysis detecting any code issues as programmer types. Apart from general analyzers, that are shipped w i t h Visual Studio, it 2 7 4- T H E . N E T COMPILER PLATFORM is possible to define custom, domain specific rules. The tricky tasks of running the analysis on a background thread, showing squiggles i n the IDE, populating the Error List, and p r o v i d i n g the light bulb w i t h code fix suggestions, is left to Visual Studio. In order to understand the chapters describing the implementation part of the thesis, it is vital to k n o w h o w the Diagnostic A P I s w o r k and h o w they are leveraged w h e n writing a custom analyzer. 4.5.1 Diagnostic Analyzer A s defined i n [15], an analyzer is an instance of a type deriving from M i c r o s o f t . C o d e A n a l y s i s . D i a g n o s t i c s . D i a g n o s t i c A n a l y z e r w h i c h must be annotated w i t h D i a g n o s t i c A n a l y z e r attribute specifying the targeted programming language ( C # or Visual Basic). In the text of this thesis, these instances are referred to as end-analyzers, i n order to distinguish them from abstract analyzers or helper classes containing analysis logic. The end-analyzer can contain one or more custom rules for detecting d o m a i n specific errors or code issues. Each such rule is defined by D i a g n o s t i c D e s c r i p t o r . Once the analyzer finds an issue, the diagnostic descriptor is used to create a diagnostic w h i c h also includes data collected by the compiler, such as location. Diagnostic Descriptor For any Roslyn end-analyzer it is mandatory to override an immutable property SupportedDiagnostics. It returns an immutable array of diagnostic descriptors w h i c h consist of: Diagnosticld - unique value identifying a single rule (e.g. "BH103"). Title, MessageFormat, Description - localizable strings that w i l l be displayed i n the Error List. The message format can be also passed arguments upon diagnostic creation, to give more details about the concrete issue detected. Category - the category that the analyzer belongs to, such as code style, naming, design, etc. The list of categories defined for this thesis can be found i n Section 5.2. Def aultSeverity - one of E r r o r , Warning, Info, Hidden, None. Note, that only E r r o r severity prevents the project f r o m compiling successfully. 28 4- T H E . N E T COMPILER PLATFORM IsEnabledByDefault - w h e n this flag is set to f a l s e , the rule must be turned o n manually i n the rule set file. HelpLinkUri - optional U R I w i t h an online documentation. Performing the analysis The analyzer needs to provide a code that performs the actual analysis (commonly referred to as action). In order to tell the . N E T C o m p i ler Platform w h e n the action needs to be invoked, the end-analyzer must register the action by overriding the I n i t i a l i z e () method. This method represents the entry point of the analyzer a n d is invoked exactly once per session6 . The method accepts one argument, of type AnalysisContext, exposing number of methods for registering actions. D e p e n d i n g o n w h e n the action should be invoked, a callback w i t h analysis logic is passed to one of these methods [17]: RegisterSyntaxNodeAction - the action w i l l be invoked o n every syntax node encountered by the compiler if the kind of the node matches one of the kinds provided u p o n registration. RegisterSymbolAction - the action w i l l be invoked o n complete semantic processing of a symbol that matches one of symbol kinds that the action was registered with. RegisterSyntaxTreeAction - the action w i l l be invoked as soon as the whole document is parsed. RegisterSemanticModelAction - the action w i l l be invoked after the semantic analysis of the document is finished. Regist er Compi lat i onStart Act i on - the action w i l l be invoked w h e n the compilation starts (before any other actions). The context can be then used to register other actions within the compilation as well as the corresponding RegisterCompilationEndAction. RegisterCodeBlockStartAction - the action w i l l be invoked before any of actions applicable w i t h i n the code block are invoked. The matching code block end action can be registered. RegisterCompilationAction, RegisterCodeBlockAction-actions to be invoked once per compilation or code block, respectively. 6. For batch compilations the session is a single run of the compiler. For hosted environments, where the analysis runs on the background thread, the session can last as long as the IDE is open. For more information see [17]. 2 9 4- T H E . N E T COMPILER PLATFORM The compilation or code block start actions are usually registered for so called stateful analyzers. These report the diagnostics about a specific code unit, like a syntax node or a symbol (same as stateless analyzers), but within the enclosing unit of the code block or the compilation. They have to be designed carefully to perform effectively and not cause any memory leaks. For both categories of analyzers it is vital that they are written defensively and do not to throw any exceptions. Otherwise, Visual Studio might disable them. 4.5.2 Code Fix Provider A code fix is a quick action suggesting a possible solution to a diagnosed code issue f o u n d by the analyzer. It can be applied by invoking the quick actions (Ctrl+.) integrated into Visual Studio light bulb. Before application, the developer can view a live preview of the code fix action, as depicted i n Figure 4.4. © T var numberQfcookies = ^e^p^jT5e_^Cooj J (?~ Analyzers J BugHunter.Analyzers jff BH0000: 'LuceneSearchDocurnent' should not be used. J? BH1000: 'Session.Sessionld' should not be used. Use 'Se BH1001: 'Session[]' should not be used. Use 'SessionHel | J Default Set Ru 1 e Set Severity • Error Warning Info Hidden None View Help,,, Properties Alt+Enter Error Warning Info Hidden None st.Url' should not be used, Use 'Request st.Erowser' should not be used. Use 'Br< nse.RedirectQ' should not be used, String!]' should not be used. Use 'Querj Figure C . l : Setting the analyzer's severity for project 3. http://semver.org 4. https://github.com/dotnet/roslyn/blob/master/docs/compiler s/Rule°/0 20Set°/.20Format .md 6 8 C . INSTALLATION A N D CONFIGURATION C.2.2 Disabling Analyzers in Code One of the big advantages of Roslyn analyzers is that they can be disabled directly i n the code, w h i c h allows for granular and transparent configuration. There are two options for suppressing the analyzers' warnings for a piece of code5 : Pragma warning 6 - preprocessor directive for temporary disabling and later enabling the analysis for any line(s) of code: // Enable use of 'Request.Browser' f o r t h i s f i l e #pragma warning d i s a b l e BH1007 p u b l i c s t a t i c c l a s s BrowserHelper { // methods } #pragma warning r e s t o r e BH1007 Suppress message 7 - attribute for disabling a n analyzer over specified unit (member, type, namespace, or module): [System.Diagnostics.CodeAnalysis.SuppressMessage( "CmsApiReplacements", "BH1007:'Request.Browser "', J u s t i f i c a t i o n = "Enable f o r the helper c l a s s . " ) ] p u b l i c s t a t i c c l a s s BrowserHelper { // methods } 5. The examples show the two approaches applied on Kentico's BrowserHelper class having the HttpRequestBrowserAnalyzer (id BH1007) disabled. As per Kentico internal guidelines, this class should be always used instead of directly accessing the Browser property of HttpRequest object. 6. https://docs.microsoft.com/en-us/dotnet/articles/ csharp/language-reference/preprocessor-directives/ preprocessor-pragma-warning 7. https://msdn.microsoft.com/en-us/library/ms244717.aspx 69