/// <summary> /// Constructs a sample SARIF log and writes it to the specified location. /// </summary> /// <param name="options">Create verb options.</param> /// <returns>Exit code</returns> static int CreateSarifLogFile(CreateOptions options) { // We'll use this source file for several defect results -- the // SampleSourceFiles folder should be at the same level as the project folder // Because this file can actually be accessed by this app, its // content will be embedded in the log file var fileLocation = new FileLocation { Uri = new Uri($"file://{AppDomain.CurrentDomain.BaseDirectory}/../../../../SampleSourceFiles/AnalysisSample.cs") }; // Create a list of rules that will be enforced during your analysis #region Rules list var rules = new List <Rule>() { new Rule { Id = "CA1819", Name = new Message { Text = "Properties should not return arrays" }, FullDescription = new Message { Text = "Arrays returned by properties are not write-protected, even if the property is read-only. To keep the array tamper-proof, the property must return a copy of the array. Typically, users will not understand the adverse performance implications of calling such a property." } }, new Rule { Id = "CA1820", Name = new Message { Text = "Test for empty strings using string length" }, FullDescription = new Message { Text = "Comparing strings by using the String.Length property or the String.IsNullOrEmpty method is significantly faster than using Equals." } }, new Rule { Id = "CA2105", Name = new Message { Text = "Array fields should not be read only" }, FullDescription = new Message { Text = "When you apply the read-only (ReadOnly in Visual Basic) modifier to a field that contains an array, the field cannot be changed to reference a different array. However, the elements of the array stored in a read-only field can be changed." } }, new Rule { Id = "CA2215", Name = new Message { Text = "Dispose methods should call base class dispose" }, FullDescription = new Message { Text = "If a type inherits from a disposable type, it must call the Dispose method of the base type from its own Dispose method." } }, //new Rule //{ // Id ="CA1816", // Name = new Message { Text = "Call GC.SuppressFinalize correctly" }, // FullDescription = new Message { Text = "A method that is an implementation of Dispose does not call GC.SuppressFinalize, or a method that is not an implementation of Dispose calls GC.SuppressFinalize, or a method calls GC.SuppressFinalize and passes something other than this (Me in Visual Basic)." } //}, //new Rule //{ // Id ="CA2006", // Name = new Message { Text = "Use SafeHandle to encapsulate native resources" }, // FullDescription = new Message { Text = "Use of IntPtr in managed code might indicate a potential security and reliability problem. All uses of IntPtr must be reviewed to determine whether use of a SafeHandle, or similar technology, is required in its place." } //} }; #endregion // Regions will be calculated by your analysis process #region Regions var regions = new List <Region>() { new Region // CA1819 { StartLine = 16, StartColumn = 19, EndLine = 16, EndColumn = 38, Offset = 331, // Offset should account for the BOM, if present in source file Length = 19 }, new Region // CA1820 { StartLine = 23, StartColumn = 21, EndLine = 23, EndColumn = 44, Offset = 507, // Offset should account for the BOM, if present in source file Length = 23 }, new Region // CA2105 { StartLine = 11, StartColumn = 9, EndLine = 11, EndColumn = 50, Offset = 198, // Offset should account for the BOM, if present in source file Length = 41 }, new Region // CA2215 { StartLine = 32, StartColumn = 9, EndLine = 32, EndColumn = 30, Offset = 646, // Offset should account for the BOM, if present in source file Length = 21 } }; #endregion // Sets of fixes corresponding to each rule // Multiple fixes can be provided for the user to choose from #region Fixes IList <Fix[]> fixes = new List <Fix[]> { null, // no suggested fixes for CA1819 new[] { new Fix // CA1820 { Description = new Message { Text = "Replace empty string test with test for zero length." }, FileChanges = new[] { new FileChange { FileLocation = fileLocation, Replacements = new[] { new Replacement { DeletedLength = 6, InsertedBytes = ".Length == 0", Offset = 524 } } } }, } }, null, // no suggested fix for CA2105 new[] { new Fix // CA2215 { Description = new Message { Text = "Call base.Dispose in the derived's class's Dispose method." }, FileChanges = new[] { new FileChange { FileLocation = fileLocation, Replacements = new[] { new Replacement { DeletedLength = 0, InsertedBytes = @"\nbase.Dispose();", Offset = 656 } } } } } } }; #endregion // The SarifLogger will write the JSON-formatted log to this StringBuilder var sb = new StringBuilder(); using (var textWriter = new StringWriter(sb)) { using (var sarifLogger = new SarifLogger( textWriter, analysisTargets: null, loggingOptions: LoggingOptions.PersistTextFileContents | // <-- embed source file content directly in the log file -- great for portability of the log! LoggingOptions.ComputeFileHashes | LoggingOptions.PrettyPrint, // <-- use PrettyPrint to generate readable (multi-line, indented) JSON prereleaseInfo: null, invocationTokensToRedact: null, invocationPropertiesToLog: null)) { // Create one result for each rule for (int i = 0; i < rules.Count; i++) { Rule rule = rules[i]; Region region = regions[i]; var result = new Result() { RuleId = rule.Id, AnalysisTarget = new FileLocation { Uri = new Uri(@"file://d:/src/module/foo.dll") }, // This is the file that was analyzed Locations = new[] { new Location { PhysicalLocation = new PhysicalLocation { FileLocation = fileLocation, Region = region } }, }, Fixes = fixes[i], RelatedLocations = new[] { new Location { PhysicalLocation = new PhysicalLocation { FileLocation = new FileLocation { // Because this file doesn't exist, it will be included in the files list but will only have a path and MIME type // This is the behavior you'll see any time a file can't be located/accessed Uri = new Uri($"file://{AppDomain.CurrentDomain.BaseDirectory}/../../../SampleSourceFiles/SomeOtherSourceFile.cs"), }, Region = new Region { StartLine = 147, StartColumn = 19, EndLine = 147, EndColumn = 40, Offset = 1245, Length = 21 } } } }, Stacks = new[] { new Stack { Frames = new[] { new StackFrame { // The method that contains the defect Location = new Location { PhysicalLocation = new PhysicalLocation { FileLocation = fileLocation, Region = new Region { StartLine = 212 } } } }, new StackFrame { // The method that calls the one above, e.g. ComputeSomeValue() Location = new Location { PhysicalLocation = new PhysicalLocation { FileLocation = fileLocation, Region = new Region { StartLine = 452 // Fake example } } } }, new StackFrame { // The method that calls the one above, e.g. Main() Location = new Location { PhysicalLocation = new PhysicalLocation { FileLocation = fileLocation, Region = new Region { StartLine = 145 } } } } } } } }; // Let's add a CodeFlow for the first defect (properties shouldn't return arrays) // This flow shows where the array is declared, and where it is returned by a property if (i == 0) { var codeFlow = new CodeFlow { // This is what a single-threaded result looks like // TIP: use SarifUtilities.CreateSingleThreadedCodeFlow to reduce repetition // Multi-threaded example coming soon! ThreadFlows = new[] { new ThreadFlow { Locations = new[] { new CodeFlowLocation { // This is the defect statement's location Location = new Location { PhysicalLocation = new PhysicalLocation { FileLocation = fileLocation, Region = region } }, Step = 1, Importance = CodeFlowLocationImportance.Essential }, new CodeFlowLocation { // This is the declaration of the array Location = new Location { PhysicalLocation = new PhysicalLocation { FileLocation = fileLocation, Region = new Region { StartLine = 12 } } }, NestingLevel = 1, Step = 2, Importance = CodeFlowLocationImportance.Important } } } } }; result.CodeFlows = new[] { codeFlow }; } sarifLogger.Log(rule, result); } } } File.WriteAllText(options.OutputFilePath, sb.ToString()); return(0); }
/// <summary> /// Constructs a sample SARIF log and writes it to the specified location. /// </summary> /// <param name="options">Create verb options.</param> /// <returns>Exit code</returns> internal static int CreateSarifLogFile(CreateOptions options) { // We'll use this source file for several defect results -- the // SampleSourceFiles folder should be a child of the project folder, // two levels up from the folder that contains the EXE (e.g., bin\Debug). string scanRootDirectory = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"..\..\SampleSourceFiles\")); var scanRootUri = new Uri(scanRootDirectory, UriKind.Absolute); var artifactLocation = new ArtifactLocation { Uri = new Uri("AnalysisSample.cs", UriKind.Relative), UriBaseId = RepoRootBaseId }; // Create a list of rules that will be enforced during your analysis #region Rules list var rules = new List <ReportingDescriptor>() { new ReportingDescriptor { Id = "CA1819", Name = "Properties should not return arrays", FullDescription = new MultiformatMessageString { Text = "Arrays returned by properties are not write-protected, even if the property is read-only. To keep the array tamper-proof, the property must return a copy of the array. Typically, users will not understand the adverse performance implications of calling such a property." }, MessageStrings = new Dictionary <string, MultiformatMessageString> { { "Default", new MultiformatMessageString { Text = "The property {0} returns an array." } } }, HelpUri = new Uri("https://www.example.com/rules/CA1819") }, new ReportingDescriptor { Id = "CA1820", Name = "Test for empty strings using string length", FullDescription = new MultiformatMessageString { Text = "Comparing strings by using the String.Length property or the String.IsNullOrEmpty method is significantly faster than using Equals." }, MessageStrings = new Dictionary <string, MultiformatMessageString> { { "Default", new MultiformatMessageString { Text = "The test for an empty string is performed by a string comparison rather than by testing String.Length." } } }, HelpUri = new Uri("https://www.example.com/rules/CA1820") }, new ReportingDescriptor { Id = "CA2105", Name = "Array fields should not be read only", FullDescription = new MultiformatMessageString { Text = "When you apply the read-only (ReadOnly in Visual Basic) modifier to a field that contains an array, the field cannot be changed to reference a different array. However, the elements of the array stored in a read-only field can be changed." }, MessageStrings = new Dictionary <string, MultiformatMessageString> { { "Default", new MultiformatMessageString { Text = "The array-valued field {0} is marked readonly." } } }, HelpUri = new Uri("https://www.example.com/rules/CA2105") }, new ReportingDescriptor { Id = "CA2215", Name = "Dispose methods should call base class dispose", FullDescription = new MultiformatMessageString { Text = "If a type inherits from a disposable type, it must call the Dispose method of the base type from its own Dispose method." }, MessageStrings = new Dictionary <string, MultiformatMessageString> { { "Default", new MultiformatMessageString { Text = "The Dispose method does not call the base class Dispose method." } } }, HelpUri = new Uri("https://www.example.com/rules/CA2215") } }; #endregion // Regions will be calculated by your analysis process #region Regions var regions = new List <Region>() { new Region // CA1819 { StartLine = 17, StartColumn = 16, EndColumn = 32 }, new Region // CA1820 { StartLine = 26, StartColumn = 21, EndColumn = 44 }, new Region // CA2105 { StartLine = 14, StartColumn = 17, EndColumn = 25 }, new Region // CA2215 { StartLine = 37, StartColumn = 9, EndColumn = 9 } }; #endregion #region Message arguments string[][] messageArguments = new string[][] { new string[] { "MyIntArray" }, null, new string[] { "_myStringArray" }, null }; #endregion // Sets of fixes corresponding to each rule // Multiple fixes can be provided for the user to choose from #region Fixes IList <Fix[]> fixes = new List <Fix[]> { null, // no suggested fixes for CA1819 new[] { new Fix // CA1820 { Description = new Message { Text = "Replace empty string test with test for zero length." }, ArtifactChanges = new[] { new ArtifactChange { ArtifactLocation = artifactLocation, Replacements = new[] { new Replacement { DeletedRegion = new Region { StartLine = 26, StartColumn = 38, EndColumn = 44 }, InsertedContent = new ArtifactContent { Text = ".Length == 0" } } } } }, } }, null, // no suggested fix for CA2105 new[] { new Fix // CA2215 { Description = new Message { Text = "Call base.Dispose in the derived's class's Dispose method." }, ArtifactChanges = new[] { new ArtifactChange { ArtifactLocation = artifactLocation, Replacements = new[] { new Replacement { DeletedRegion = new Region { StartLine = 37, StartColumn = 1, EndColumn = 1 }, InsertedContent = new ArtifactContent { Text = @" base.Dispose();\n" } } } } } } } }; #endregion string binRootDirectory = @"d:\src\module\"; var binRootUri = new Uri(binRootDirectory, UriKind.Absolute); var run = new Run { OriginalUriBaseIds = new Dictionary <string, ArtifactLocation> { [RepoRootBaseId] = new ArtifactLocation { Uri = scanRootUri }, [BinRootBaseId] = new ArtifactLocation { Uri = binRootUri } }, VersionControlProvenance = new VersionControlDetails[] { new VersionControlDetails { RepositoryUri = new Uri("https://github.com/microsoft/sarif-sdk"), RevisionId = "ee5a1ca8", Branch = "master", MappedTo = new ArtifactLocation { Uri = new Uri(".", UriKind.Relative), UriBaseId = RepoRootBaseId } } } }; // The SarifLogger will write the JSON-formatted log to this StringBuilder var sb = new StringBuilder(); using (var textWriter = new StringWriter(sb)) { using (var sarifLogger = new SarifLogger( textWriter, logFilePersistenceOptions: LogFilePersistenceOptions.PrettyPrint, // Use PrettyPrint to generate readable (multi-line, indented) JSON dataToInsert: OptionallyEmittedData.TextFiles | // Embed source file content directly in the log file -- great for portability of the log! OptionallyEmittedData.Hashes | OptionallyEmittedData.RegionSnippets, tool: null, run: run, analysisTargets: null, invocationTokensToRedact: null, invocationPropertiesToLog: null, defaultFileEncoding: null)) { // Create one result for each rule for (int i = 0; i < rules.Count; i++) { ReportingDescriptor rule = rules[i]; Region region = regions[i]; var result = new Result() { RuleId = rule.Id, AnalysisTarget = new ArtifactLocation { Uri = new Uri("example.dll", UriKind.Relative), // This is the file that was analyzed UriBaseId = BinRootBaseId }, Message = new Message { Id = "Default", Arguments = messageArguments[i] }, Locations = new[] { new Location { PhysicalLocation = new PhysicalLocation { ArtifactLocation = artifactLocation, Region = region } }, }, Fixes = fixes[i], RelatedLocations = new[] { new Location { PhysicalLocation = new PhysicalLocation { ArtifactLocation = new ArtifactLocation { // Because this file doesn't exist, it will be included in the files list but will only have a path and MIME type // This is the behavior you'll see any time a file can't be located/accessed Uri = new Uri("SomeOtherSourceFile.cs", UriKind.Relative), UriBaseId = RepoRootBaseId }, Region = new Region { StartLine = 147, StartColumn = 19, EndLine = 147, EndColumn = 40 } } } }, Stacks = new[] { new Stack { Frames = new[] { new StackFrame { // The method that contains the defect Location = new Location { PhysicalLocation = new PhysicalLocation { ArtifactLocation = artifactLocation, Region = new Region { StartLine = 17 } } } }, new StackFrame { // The method that calls the one above, e.g. ComputeSomeValue() Location = new Location { PhysicalLocation = new PhysicalLocation { ArtifactLocation = artifactLocation, Region = new Region { StartLine = 24 // Fake example } } } }, new StackFrame { // The method that calls the one above, e.g. Main() Location = new Location { PhysicalLocation = new PhysicalLocation { ArtifactLocation = artifactLocation, Region = new Region { StartLine = 26 // Fake example } } } } } } } }; // Let's add a CodeFlow for the first defect (properties shouldn't return arrays) // This flow shows where the array is declared, and where it is returned by a property if (i == 0) { var codeFlow = new CodeFlow { // This is what a single-threaded result looks like // TIP: use SarifUtilities.CreateSingleThreadedCodeFlow to reduce repetition // Multi-threaded example coming soon! ThreadFlows = new[] { new ThreadFlow { Locations = new[] { new ThreadFlowLocation { // This is the defect statement's location Location = new Location { PhysicalLocation = new PhysicalLocation { ArtifactLocation = artifactLocation, Region = region } }, Importance = ThreadFlowLocationImportance.Essential }, new ThreadFlowLocation { // This is the declaration of the array Location = new Location { PhysicalLocation = new PhysicalLocation { ArtifactLocation = artifactLocation, Region = new Region { StartLine = 12 } } }, NestingLevel = 1, Importance = ThreadFlowLocationImportance.Important } } } } }; result.CodeFlows = new[] { codeFlow }; } sarifLogger.Log(rule, result); } } } File.WriteAllText(options.OutputFilePath, sb.ToString()); return(0); }