Understanding the Internals of Code Contracts

Introduction

.NET Framework 4.0 made code contracts first class citizens by introducing System.Diagnostics.Contracts as part of base class libraries. Code contracts offer a way to express state assumptions in applications and take forms of pre-conditions, post-conditions and object invariants. In this article we will see the underlying internals of how code contracts work.

Sample Application Using Code Contracts

Here is the sample code we will be using.

  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Diagnostics.Contracts;
  
  namespace RewriteThrow
  {
      class Program
      {
          static void Main(string[] args)
          {
              Contract.Assert(false);
          }
      }
  }

Tools Required

  1. Microsoft Visual Studio 2010 Ultimate/Professional
  2. Code contracts tools download

Let us start by creating a solution titled SampleContracts with a Console C# project titled NoRewriting which has the code listed above. Do not change the project properties for this.

Create another C# console project titled RewriteAssert having the same code as above. However for this project, Go to the project properties and on the Code Contracts tab, check the "Perform Runtime Contract checking" and also check that the "Assert on Contract Failure".

check that the
Figure 1

Now, create a third C# console project titled RewriteThrow, again with the same code as above. However, for this project, go to the project properties and on the Code Contracts tab, check the "Perform Runtime Contract checking" and also double-check that the "Assert on Contract Failure" is not checked.

check that the
Figure 1

Now that we have the project setup, build the solution and let us look under the covers.

Diving In

Fire up ildasm.exe from Microsoft Visual Studio Tools directory (Start -> Microsoft Visual Studio 2010 > Visual Studio Tools> Visual Studio Command prompt and typing ildasm.exe at the console) Open the NoRewriting.exe and check out the contents of the binary. It should look like the sample below:

the contents of the binary
Figure 1

The RewriteAssert Assembly looks like the sample below:

The RewriteAssert Assembly
Figure 1

And the RewriteThrow assembly looks like the sample below:

the RewriteThrow assembly
Figure 1

Even though the exact same code is compiled into these three binaries, however the ending composition of these binaries is very different from each other. Let us try to understand the reason for that.



Understanding the Internals of Code Contracts

Lets delve into the IL code generated in each of the three cases.

For the NoWriting project, since we did not opt for contracts to be rewritten, the application is as simple as it can be, having only the implicit constructor and the Main method. Here is the IL for the Main method:

  .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size       9 (0x9)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldc.i4.0
    IL_0002:  call       void [mscorlib]System.Diagnostics.Contracts.Contract::Assert(bool)
    IL_0007:  nop
    IL_0008:  ret
  } // end of method Program::Main

At line IL_0002, there is a call to Contract.Assert from mscorlib.dll.

For the RewriteAssert assembly, we see that the contract rewriting process did a few things:

  1. It generates two classes into the assembly, namely, System.Diagnostics.Contracts.RuntimeContractsAttribute and System.Diagnostics.Contracts.__ContractsRuntime RuntimeContractsFlags and also included an enum System.Diagnostics.Contracts.RUntimeContractsFlags.
  2. The IL code in Main got changed to load a string in instruction IL_0003 and calls the ContractsRuntime::Assert method on the class which is generated in step (a) above. Note that the Assert call is different since the call is not to the Assert method in mscorlib but into the newly generated class which resides in RewriteAssert.exe:

      .method private hidebysig static void  Main(string[] args) cil managed
      {
        .entrypoint
        // Code size       15 (0xf)
        .maxstack  8
        IL_0000:  nop
        IL_0001:  ldc.i4.0
        IL_0002:  ldnull
        IL_0003:  ldstr      "false"
        IL_0008:  call       void System.Diagnostics.Contracts.__ContractsRuntime::Assert(bool,
                                                                                          string,
                                                                                          string)
        IL_000d:  nop
        IL_000e:  ret
      } // end of method Program::Main
    

  3. The newly generated class ContractsRuntime's Assert method calls the ReportFailure method on the same class, which in turns calls mscorlib's System.Diagnostics.Contracts.Internal.ContractHelper::TriggerFailure method which has the implementation of showing an Assert dialog in case of a failing contract:

      .method assembly static void  ReportFailure(valuetype [mscorlib]System.Diagnostics.Contracts.ContractFailureKind kind,
                                                  string message,
                                                  string conditionText,
                                                  class [mscorlib]System.Exception inner) cil managed
      {
        // Code size       27 (0x1b)
        .maxstack  6
        .locals init ([0] string V_0)
        IL_0000:  ldarg.0
        IL_0001:  ldarg.1
        IL_0002:  ldarg.2
        IL_0003:  ldarg.3
        IL_0004:  call       string [mscorlib]System.Diagnostics.Contracts.Internal.ContractHelper::RaiseContractFailedEvent(valuetype [mscorlib]System.Diagnostics.Contracts.ContractFailureKind,
                                                                                                                             string,
                                                                                                                             string,
                                                                                                                             class [mscorlib]System.Exception)
        IL_0009:  stloc.0
        IL_000a:  ldloc.0
        IL_000b:  brfalse    IL_001a
        IL_0010:  ldarg.0
        IL_0011:  ldloc.0
        IL_0012:  ldarg.1
        IL_0013:  ldarg.2
        IL_0014:  ldarg.3
        IL_0015:  call       void [mscorlib]System.Diagnostics.Contracts.Internal.ContractHelper::TriggerFailure(valuetype [mscorlib]System.Diagnostics.Contracts.ContractFailureKind,
                                                                                                                 string,
                                                                                                                 string,
                                                                                                                 string,
                                                                                                                 class [mscorlib]System.Exception)
        IL_001a:  ret
      } // end of method __ContractsRuntime::ReportFailure
    

On the other hand, for the RewriteThrow assembly, we see that the contract rewriting process changed a bit from the RewriteAssert assembly:

  1. There is an explicit ContractException class created insides the newly generated classes System.Diagnostics.Contracts.RuntimeContractsAttribute and System.Diagnostics.Contracts.__ContractsRuntime RuntimeContractsFlags and the assembly also includes an enum System.Diagnostics.Contracts.RUntimeContractsFlags.
  2. The IL code in Main is the same as for RewriteAssert.exe
  3. The newly generated class ContractsRuntime's Assert method calls the ReportFailure method on the same class, which in turns calls System.Diagnostics.Contracts.Internal.ContractHelper::TriggerFailure method (on the same assembly and not on mscorlib) which has the implementation of throwing a ContractException in case of a failing contract.

      .method assembly static void  TriggerFailure(valuetype [mscorlib]System.Diagnostics.Contracts.ContractFailureKind kind,
                                                   string message,
                                                   string userMessage,
                                                   string conditionText,
                                                   class [mscorlib]System.Exception inner) cil managed
      {
        // Code size       12 (0xc)
        .maxstack  8
        IL_0000:  ldarg.0
        IL_0001:  ldarg.1
        IL_0002:  ldarg.2
        IL_0003:  ldarg.3
        IL_0004:  ldarg.s    inner
        IL_0006:  newobj     instance void System.Diagnostics.Contracts.__ContractsRuntime/ContractException::'.ctor$PST06000007'(valuetype [mscorlib]System.Diagnostics.Contracts.ContractFailureKind,                                                                                                                            string, string, string, class [mscorlib]System.Exception)
        IL_000b:  throw
      } // end of method __ContractsRuntime::TriggerFailure
    

Conclusion

As we can see from the code above, though the assembly has the same business code inside it, rewriting it using contracts changes the actual code tremendously. Hopefully, this knowledge will help you understand the mechanism of how code contracts works and will help you leverage the tools in the best possible way that fits your needs.

Related Articles





About the Author

Vipul Vipul Patel

Vipul Patel is a Software Engineer currently working at Microsoft Corporation, working in the Office Communications Group and has worked in the .NET team earlier in the Base Class libraries and the Debugging and Profiling team. He can be reached at vipul_d_patel@hotmail.com

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: August 14, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Data protection has long been considered "overhead" by many organizations in the past, many chalking it up to an insurance policy or an extended warranty you may never use. The realities of today makes data protection a must-have, as we live in a data-driven society -- the digital assets we create, share, and collaborate with others on must be managed and protected for many purposes. Check out this upcoming eSeminar and join Seagate Cloud …

  • Cisco and Intel have harnessed flash memory technology and truly innovative system software to blast through the boundaries of today's I/O-bound server/storage architectures. See how they are bringing real-time responsiveness to data-intensive applications—for unmatched business advantage. Sponsored by Cisco and Intel® Partnering in Innovation

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds