Curious Case of Code Contracts and Roslyn

CODE CONTRACTS

We have a large visual studio solution that uses Microsoft CodeContracts for run-time verification and static analysis. Code contracts is a great tool that proves there are bugs in your source code. When it is hard to deduce for the static analyzer to prove facts, you can always help by adding contract blocks in your source code. In addition to static analysis, using a contract statement such as

Contract.Requires(count > 0);

will throw an exception at the runtime. Code contracts extension includes an IL weaver engine that rewrites the Contract statements to a statement that performs the runtime check and reports an error that includes the predicate as a string:

__ContractsRuntime.Requires(count > 0, null"count > 0");

There are many other aspects of the Microsoft Code Contracts that your solution can benefit and they have an extensive documentation.

ROSLYN

On the other hand we have Roslyn installed as the default compiler service within Visual Studio 2013. Rest all other pros, navigating within Visual Studio is many times faster now with Roslyn.

THE PROBLEM

… is that when you have a method which has dynamic local variables, code contracts rewriter (ccrewrite.exe) throws an internal exception which leads to an error message similar to:

1> There were errors reported in {0}'s metadata.
1> Unknown custom metadata item kind: 5

Upon investigating one can find out that Code Contracts project is snail-speed with its updates and it takes time to catch the changes. In this case, code contracts is lagging behind Roslyn in terms of PDB (program database) reading and writing.

After searching for the string “Unknown custom metadata item kind:”, we arrive at the method ReadCustomMetadata in PdbFunction class in CCI project which code contracts uses to handle PDB files. Thus we can no longer blame Code Contracts project lagging behind Rosyln, it is Common Compiler Infrastructure: Metadata API which is not up-to-date.

private void ReadCustomMetadata(BitAccess bits) {
  int savedPosition = bits.Position;
  byte version;
  bits.ReadUInt8(out version);
  if (version != 4) {
    throw new PdbDebugException("Unknown custom metadata item version: {0}", version);
  }
  byte kind;
  bits.ReadUInt8(out kind);
  bits.Align(4);
  uint numberOfBytesInItem;
  bits.ReadUInt32(out numberOfBytesInItem);
  switch (kind) {
    case 0: this.ReadUsingInfo(bits); break;
    case 1: this.ReadForwardInfo(bits); break;
    case 2: break; // this.ReadForwardedToModuleInfo(bits); break;
    case 3: this.ReadIteratorLocals(bits); break;
    case 4: this.ReadForwardIterator(bits); break;
    default: throw new PdbDebugException("Unknown custom metadata item kind: {0}", kind);
  }
  bits.Position = savedPosition+(int)numberOfBytesInItem;
}

When an unhandled metadata kind is encountered, the method throws a PdbDebugException which is what we are seeing in the error message of ccrewrite.exe. We can try to handle this code or maybe we can get around just by not throwing the exception at all.

For example code that does not build see https://github.com/krk/CodeContractsRosyln

THE SOLUTION

We chose the road taken in this case, modifying switch statement to ignore unknown metadata kinds. To modify the switch statement, we need an IL editor such as ildasm/ilasm or reflexil. I will skip the modification step for brevity and highlight the removed part of the ReadCustomMetadata method:

private void ReadCustomMetadata(BitAccess bits)
{
	int position = bits.Position;
	byte b;
	bits.ReadUInt8(out b);
	if (b != 4)
	{
		throw new PdbDebugException("Unknown custom metadata item version: {0}", new object[]
		{
			b
		});
	}
	byte b2;
	bits.ReadUInt8(out b2);
	bits.Align(4);
	uint num;
	bits.ReadUInt32(out num);
	switch (b2)
	{
	case 0:
		this.ReadUsingInfo(bits);
		break;
	case 1:
		this.ReadForwardInfo(bits);
		break;
	case 2:
		break;
	case 3:
		this.ReadIteratorLocals(bits);
		break;
	case 4:
		this.ReadForwardIterator(bits);
		break;
	default:
		throw new PdbDebugException("Unknown custom metadata item kind: {0}", new object[]
		{
			b2
		});
	}
	bits.Position = position + (int)num;
}

Now we need to make the same modification in other applications, namely ccdocgen.exe, ccrefgen.exe, ccrewrite.exe files in addition to cccheck.exe. We can see that our modified code contracts can handle Roslyn generated PDB files that have a dynamic local without any visible problems, hence solving the problem.

1>CodeContractRewrite:
1> “C:\Program Files (x86)\Microsoft\Contracts\Bin\ccrewrite.exe” “@{0}ccrewrite.rsp”
1> elapsed time: 199.3108ms
1> Touching “obj\Debug\{0}.rewritten”.