Benchmarkdotnet: DisassemblyDiagnoser index outside array bounds

Created on 5 Dec 2019  路  4Comments  路  Source: dotnet/BenchmarkDotNet

Hello, thank you for your great tool.
I want to report a error that I'm experiencing with the DisassemblyDiagnoser.

I'm getting a index outside array bounds exception when running this bench:

[Config(typeof(CustomConfig))]
public class SqlDataReaderTest
{
    [Params(100, 1000, 10000)]
    public int LineCount { get; set; }

    public IDataReader InputReader { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        InputReader = new MockSqlReaderBuilder().CreateNewTable().AddStandardData(LineCount).ToDataReader();
    }


    [Benchmark]
    public List<ResultClass> SyntacticalSelection()
    {
        var result = new List<ResultClass>();
        while (InputReader.Read())
        {
            result.Add(new ResultClass
            {
                IntValue = (int)InputReader["intField"],
                IntValueNullable = InputReader["intFieldNullable"] as int? ?? 0,
                IntValueNull = InputReader["intFieldAlwaysNull"] as int?,

                BoolValue = (bool)InputReader["boolField"],
                BoolValueNullable = InputReader["boolFieldNullable"] as bool? ?? false,
                BoolValueNull = InputReader["boolFieldAlwaysNull"] as bool? ?? false,

                StringValue = InputReader["stringField"] as string,
                StringValueNullable = InputReader["stringFieldNullable"] as string,
                StringValueNull = InputReader["stringFieldAlwaysNull"] as string,
            });
        }
        return result;
    }

    [Benchmark(Baseline = true)]
    public List<ResultClass> ExtensionSelection()
    {
        var result = new List<ResultClass>();
        while (InputReader.Read())
        {
            result.Add(new ResultClass
            {
                IntValue = InputReader["intField"].ValueAsInt(),
                IntValueNullable = InputReader["intFieldNullable"].ValueAsInt(),
                IntValueNull = InputReader["intFieldAlwaysNull"].ValueAsIntOrNull(),

                BoolValue = InputReader["boolField"].ValueAsBool(),
                BoolValueNullable = InputReader["boolFieldNullable"].ValueAsBool(),
                BoolValueNull = InputReader["boolFieldAlwaysNull"].ValueAsBool(),

                StringValue = InputReader["stringField"].ValueAsString(),
                StringValueNullable = InputReader["stringFieldNullable"].ValueAsString(),
                StringValueNull = InputReader["stringFieldAlwaysNull"].ValueAsString(),
            });
        }
        return result;
    }
}

public class ResultClass
{
    public int IntValue { get; set; }
    public int? IntValueNullable { get; set; }
    public int? IntValueNull { get; set; }

    public bool BoolValue { get; set; }
    public bool? BoolValueNullable { get; set; }
    public bool? BoolValueNull { get; set; }

    public string StringValue { get; set; }
    public string StringValueNullable { get; set; }
    public string StringValueNull { get; set; }
}

public static class Extensions
{
    public static bool ValueAsBool(this object value)
    {
        try
        {
            if (value == null) return false;
            if (value == DBNull.Value) return false;

            if (bool.TryParse(value.ToString(), out var result)) return result;
            if (int.TryParse(value.ToString(), out var intTest)) return intTest == 1 ? true : false;
            return false;
        }
        catch (Exception)
        {
            return false;
        }
    }

    public static int ValueAsInt(this object value)
    {
        try
        {
            if (value == null) return 0;
            if (value == DBNull.Value) return 0;
            return Int32.Parse(value.ToString());
        }
        catch (Exception)
        {
            return 0;
        }
    }

    public static int? ValueAsIntOrNull(this object value)
    {
        try
        {
            if (value == null) return null;
            if (value == DBNull.Value) return null;
            return Int32.Parse(value.ToString());
        }
        catch (Exception)
        {
            return null;
        }
    }

    public static string ValueAsString(this object value)
    {
        try
        {
            if (value == null) return string.Empty;
            if (value == DBNull.Value) return String.Empty;
            return value.ToString();
        }
        catch (Exception e)
        {
            return string.Empty;
        }
    }

}

public class MockSqlReaderBuilder
{
    private DataTable _dataTable;

    public MockSqlReaderBuilder CreateNewTable()
    {
        this._dataTable = new DataTable("CustomerPersonalDetails");
        _dataTable.Columns.AddRange(new[]
        {
            new DataColumn("intField", typeof(int)),
            new DataColumn("intFieldNullable", typeof(int)),
            new DataColumn("intFieldAlwaysNull", typeof(int)),
            new DataColumn("boolField", typeof(bool)),
            new DataColumn("boolFieldNullable", typeof(bool)),
            new DataColumn("boolFieldAlwaysNull", typeof(bool)),
            new DataColumn("stringField", typeof(string)),
            new DataColumn("stringFieldNullable", typeof(string)),
            new DataColumn("stringFieldAlwaysNull", typeof(string))
        });
        return this;
    }

    public MockSqlReaderBuilder AddStandardData(int numberOfRows)
    {
        var rnd = new Random();
        for (int i = 0 ; i < numberOfRows; i++)
        {
            var someValue = rnd.Next(0, 11);

            this._dataTable.Rows.Add(
                i, //intField
                (someValue > 3 ? someValue : (int?)null), //intFieldNullable
                null, //intFieldAlwaysNull
                someValue <= 5, //boolField
                someValue % 2 == 0 ? true : someValue % 3 == 0 ? false : (bool?)null, //boolFieldNullable
                null, //boolFieldAlwaysNull
                Guid.NewGuid().ToString(), //stringField
                someValue % 4 == 0 ? Guid.NewGuid().ToString() : (string)null, //stringFieldNullable
                null //stringFieldAlwaysNull
            );
        }
        return this;
    }

    public IDataReader ToDataReader() =>
        this._dataTable.CreateDataReader();

}
Diagnosers bug

Most helpful comment

@Vermistuk thanks for the repro, fixed. The fix will be available in the next official version of BenchmarkDotNet. Currently, you can use our nightly channel, the fix will be available as soon as this build is finished.

All 4 comments

Hello @Vermistuk, thanks for the report. I tried to reproduce the issue on my local machine, but everything works fine. We need some additional details for investigation:

  1. In your code snippet, you apply CustomConfig to your class, but there is no the definition of CustomConfig. Could you share it as well?
  2. Could you share your environment info (the version of BenchmarkDotNet, OS, versions of used runtime, etc.). BenchmarkDotNet prints it above the summary table; you can temporary disable the DisassemblyDiagnoser and get this information.
  3. Could you share the full text of the exception?

Sorry @AndreyAkinshin ,
the Config:

public class CustomConfig : ManualConfig
    {
        public CustomConfig()
        {
            Add(AsciiDocExporter.Default, CsvExporter.Default, CsvMeasurementsExporter.Default,
                PlainExporter.Default, RPlotExporter.Default,
                JsonExporter.Full,
                XmlExporter.Full,
                new CustomHtmlExporter());

            SummaryStyle = new SummaryStyle
            (
                printUnitsInHeader: true,
                sizeUnit: SizeUnit.B,
                timeUnit: TimeUnit.Microsecond,
                printUnitsInContent: true,
                printZeroValuesInContent: false,
                maxParameterColumnWidth: 20);

            Options = ConfigOptions.KeepBenchmarkFiles;

            Add(
                Job.ShortRun
                    .With(Jit.RyuJit)
                    .With(Platform.X64)
                    .With(new[] { new MsBuildArgument(""), })
                    .WithGcServer(true)
                    .WithGcForce(true)
                    .WithId("ServerForce"));

            Add(new[] { RankColumn.Arabic });

            Add(
                DisassemblyDiagnoser.Create(new DisassemblyDiagnoserConfig(
                    printAsm: true,
                    printSource: true,
                    recursiveDepth: 3)
                )
            );

            Add(MemoryDiagnoser.Default);

            ArtifactsPath = $@"C:\temp\Benchmarks\{DateTime.Now:yyyy-MM-dd_HH-mm-ss}";
        }
    }

A custom HtmlExporter which only adds the legends (code just copied from the logger):

public class CustomHtmlExporter : ExporterBase
    {
        public static readonly IExporter Default = (IExporter)new CustomHtmlExporter();
        internal const string CssDefinition = "\r\n<style type=\"text/css\">\r\n\ttable { border-collapse: collapse; display: block; width: 100%; overflow: auto; }\r\n\ttd, th { padding: 6px 13px; border: 1px solid #ddd; text-align: right; }\r\n\ttr { background-color: #fff; border-top: 1px solid #ccc; }\r\n\ttr:nth-child(even) { background: #f8f8f8; }\r\n</style>";

        protected override string FileExtension
        {
            get
            {
                return "html";
            }
        }

        public override void ExportToLog(Summary summary, ILogger logger)
        {
            logger.WriteLine("<!DOCTYPE html>");
            logger.WriteLine("<html lang='en'>");
            logger.WriteLine("<head>");
            logger.WriteLine("<meta charset='utf-8' />");
            logger.WriteLine("<title>" + summary.Title + "</title>");
            logger.WriteLine("\r\n<style type=\"text/css\">\r\n\ttable { border-collapse: collapse; display: block; width: 100%; overflow: auto; }\r\n\ttd, th { padding: 6px 13px; border: 1px solid #ddd; text-align: right; }\r\n\ttr { background-color: #fff; border-top: 1px solid #ccc; }\r\n\ttr:nth-child(even) { background: #f8f8f8; }\r\n</style>");
            logger.WriteLine("</head>");
            logger.WriteLine("<body>");
            logger.Write("<pre><code>");
            logger.WriteLine();
            foreach (string text in summary.HostEnvironmentInfo.ToFormattedString())
                logger.WriteLine(text);
            logger.WriteLine(summary.AllRuntimes);
            logger.Write("</code></pre>");
            logger.WriteLine();
            CustomHtmlExporter.PrintTable(summary.Table, logger);
            CustomHtmlExporter.PrintLegend(summary, logger);
            logger.WriteLine("</body>");
            logger.WriteLine("</html>");

        }

        private static void PrintLegend(Summary summary, ILogger logger)
        {
            List<IColumn> list = ((IEnumerable<SummaryTable.SummaryTableColumn>)summary.Table.Columns).Select<SummaryTable.SummaryTableColumn, IColumn>((Func<SummaryTable.SummaryTableColumn, IColumn>)(c => c.OriginalColumn)).Where<IColumn>((Func<IColumn, bool>)(c => !string.IsNullOrEmpty(c.Legend))).ToList<IColumn>();
            TimeUnit timeUnit = summary.Table.EffectiveSummaryStyle.TimeUnit;
            if (list.Any<IColumn>() || timeUnit != (TimeUnit)null)
            {
                logger.WriteLine();
                logger.Write("<pre><code>");
                logger.WriteLine(" * Legends *");
                int num = 0;
                if (list.Any<IColumn>())
                    num = Math.Max(num, list.Select<IColumn, int>((Func<IColumn, int>)(c => c.ColumnName.Length)).Max());
                if (timeUnit != (TimeUnit)null)
                    num = Math.Max(num, timeUnit.Name.ToString(Encoding.Unicode).Length + 2);
                foreach (IColumn column in list)
                    logger.WriteLine("  " + column.ColumnName.PadRight(num, ' ') + " : " + column.Legend);
                if (timeUnit != (TimeUnit)null)
                    logger.WriteLine("  " + ("1 " + timeUnit.Name?.ToString()).PadRight(num, ' ') + " : 1 " + timeUnit.Description + " (" + TimeUnit.Convert(1.0, timeUnit, TimeUnit.Second).ToString("0.#########") + " sec)");

                logger.Write("<pre><code>");
            }
        }

        private static void PrintTable(SummaryTable table, ILogger logger)
        {
            if (table.FullContent.Length == 0)
            {
                logger.WriteLineError("<pre>There are no benchmarks found</pre>");
            }
            else
            {
                logger.Write("<pre><code>");
                table.PrintCommonColumns(logger);
                logger.WriteLine("</code></pre>");
                logger.WriteLine();
                logger.WriteLine("<table>");
                logger.Write("<thead>");
                logger.Write("<tr>");
                table.PrintLine(table.FullHeader, logger, "<th>", "</th>");
                logger.WriteLine("</tr>");
                logger.Write("</thead>");
                logger.Write("<tbody>");
                foreach (string[] line in table.FullContent)
                {
                    logger.Write("<tr>");
                    CustomHtmlExporter.PrintLine(table, line, logger, "<td>", "</td>");
                    logger.Write("</tr>");
                }
                logger.Write("</tbody>");
                logger.WriteLine("</table>");
            }
        }

        private static void PrintLine(
          SummaryTable table,
          string[] line,
          ILogger logger,
          string leftDel,
          string rightDel)
        {
            for (int index = 0; index < table.ColumnCount; ++index)
            {
                if (table.Columns[index].NeedToShow)
                    logger.WriteStatistic(leftDel + HtmlEncode(line[index]) + rightDel);
            }
            logger.WriteLine();
        }
        internal static string HtmlEncode(string s)
        {
            if (s == null)
                return (string)null;
            StringBuilder stringBuilder = new StringBuilder(s.Length);
            foreach (char ch in s)
            {
                switch (ch)
                {
                    case '"':
                        stringBuilder.Append("&quot;");
                        break;
                    case '&':
                        stringBuilder.Append("&amp;");
                        break;
                    case '\'':
                        stringBuilder.Append("&#39;");
                        break;
                    case '<':
                        stringBuilder.Append("&lt;");
                        break;
                    case '>':
                        stringBuilder.Append("&gt;");
                        break;
                    default:
                        stringBuilder.Append(ch);
                        break;
                }
            }
            return stringBuilder.ToString();
        }
    }

The program.cs only contains

var summary = Benchmarker.Run<MyClass>();

The exception:

// AfterAll
Failed to disassemble with following exception:
Der Index war au脽erhalb des Arraybereichs.    [Index outside arraybounds]
   bei BenchmarkDotNet.Disassembler.Program.GetSmartPrefix(String sourceLine, Int32 length)
   bei BenchmarkDotNet.Disassembler.Program.<GetSource>d__9.MoveNext()
   bei System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
   bei BenchmarkDotNet.Disassembler.Program.DisassembleMethod(MethodInfo methodInfo, State state, Settings settings)
   bei BenchmarkDotNet.Disassembler.Program.Disassemble(Settings settings, ClrRuntime runtime, State state)
   bei BenchmarkDotNet.Disassembler.Program.Handle(Settings settings)
   bei BenchmarkDotNet.Disassembler.Program.Main(String[] args)

[Unhandled Exception ... Error in XML-Document]
Unbehandelte Ausnahme: System.InvalidOperationException: Fehler im XML-Dokument (0,0). ---> System.Xml.XmlException: Das Stammelement ist nicht vorhanden.
   bei System.Xml.XmlTextReaderImpl.Throw(Exception e)
   bei System.Xml.XmlTextReaderImpl.ParseDocumentContent()
   bei System.Xml.XmlReader.MoveToContent()
   bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderDisassemblyResult.Read9_DisassemblyResult()
   --- Ende der internen Ausnahmestapel眉berwachung ---
   bei System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   bei BenchmarkDotNet.Diagnosers.WindowsDisassembler.Disassemble(DiagnoserActionParameters parameters)
   bei BenchmarkDotNet.Diagnosers.DisassemblyDiagnoser.Handle(HostSignal signal, DiagnoserActionParameters parameters)
   bei BenchmarkDotNet.Diagnosers.CompositeDiagnoser.Handle(HostSignal signal, DiagnoserActionParameters parameters)
   bei BenchmarkDotNet.Loggers.SynchronousProcessOutputLoggerWithDiagnoser.ProcessInput()
   bei BenchmarkDotNet.Toolchains.Executor.Execute(Process process, BenchmarkCase benchmarkCase, SynchronousProcessOutputLoggerWithDiagnoser loggerWithDiagnoser, ILogger logger)
   bei BenchmarkDotNet.Toolchains.Executor.Execute(BenchmarkCase benchmarkCase, BenchmarkId benchmarkId, ILogger logger, String exePath, String workingDirectory, String args, IDiagnoser diagnoser, IResolver resolver)
   bei BenchmarkDotNet.Toolchains.Executor.Execute(ExecuteParameters executeParameters)
   bei BenchmarkDotNet.Running.BenchmarkRunnerClean.Execute(ILogger logger, BenchmarkCase benchmarkCase, BenchmarkId benchmarkId, IToolchain toolchain, BuildResult buildResult, IResolver resolver)
   bei BenchmarkDotNet.Running.BenchmarkRunnerClean.RunCore(BenchmarkCase benchmarkCase, BenchmarkId benchmarkId, ILogger logger, IResolver resolver, BuildResult buildResult)
   bei BenchmarkDotNet.Running.BenchmarkRunnerClean.Run(BenchmarkRunInfo benchmarkRunInfo, Dictionary`2 buildResults, IResolver resolver, ILogger logger, List`1 artifactsToCleanup, String resultsFolderPath, String logFilePath, StartedClock& runChronometer)
   bei BenchmarkDotNet.Running.BenchmarkRunnerClean.Run(BenchmarkRunInfo[] benchmarkRunInfos)
   bei BenchmarkDotNet.Running.BenchmarkRunner.RunWithDirtyAssemblyResolveHelper(Type type, IConfig config)
   bei BenchmarkDotNet.Running.BenchmarkRunner.RunWithExceptionHandling(Func`1 run)
   bei BenchmarkDotNet.Running.BenchmarkRunner.Run[T](IConfig config)
   bei Benchmarker.Program.Main(String[] args) in C:\Users\username\Documents\VisualStudioProjects\Benchmarker\Benchmarker\Program.cs:Zeile 68.

Environment info:

// * Summary *

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-5500U CPU 2.40GHz (Broadwell), 1 CPU, 4 logical and 2 physical cores
  [Host]      : .NET Framework 4.8 (4.8.4018.0), X64 RyuJIT
  ServerForce : .NET Framework 4.8 (4.8.4018.0), X64 RyuJIT

Job=ServerForce  Jit=RyuJit  Platform=X64
Force=True  Server=True  Arguments=Empty
IterationCount=3  LaunchCount=1  WarmupCount=3

@Vermistuk thanks, reproduced.

@Vermistuk thanks for the repro, fixed. The fix will be available in the next official version of BenchmarkDotNet. Currently, you can use our nightly channel, the fix will be available as soon as this build is finished.

Was this page helpful?
0 / 5 - 0 ratings