Implementing a custom lint-style check for virtual functions

D's reflection capabilities can be used to perform some checks that the lint tools are needed to perform for the C or C++ code. Here, we'll implement the code to warn the user at compile time whether their class has an unmarked virtual function.

How to do it…

Let's execute the following steps to implement a custom lint-style check for virtual functions:

  1. Define a plain enum called Virtual to use as the annotation to silence the warning.
  2. Find classes in your program to test, either manually or with reflection.
  3. Use __traits(derivedMembers, Class) to get all the members, excluding the base class members.
  4. Use __traits(isVirtualMethod, member) to determine whether it is a virtual function.
  5. Write a helper function that uses __traits(getAttributes, member) in a loop with static if(is(Virtual)) to look for the annotation.
  6. If it is not found, use pragma(msg) to warn the user.
  7. Optionally, return a failure flag from your check function. The user may check this flag with static assert to turn the warning into an error. Alternatively, the user can write enum virtualPassed = virtualCheck!item_of_interest; to get warnings without errors.

The code is as follows:

// the UDA we should put on authorized virtuals
enum Virtual;

// alias helper for looping over members
alias helper(alias T) = T;

// Helper function to test for presence of @Virtual
bool isAuthorizedVirtual(alias member)() {
  foreach(attr; __traits(getAttributes, member))
    if(is(attr == Virtual))
      return true;
  return false;
}

// Loop over all members, looking for classes to drill
// into and virtual functions to test.
bool virtualCheck(alias T)() {
  bool passes = true;
  foreach(memberName; __traits(derivedMembers, T)) {
    static if(memberName.length) {
      alias member = helper!(__traits(getMember, T, memberName));
      // drill down into classes
      static if(is(member == class))
        passes = passes && virtualCheck!member;

      // check overloaded functions (if any) for
      // unmarked virtuals
      foreach(overload; __traits(getOverloads, T, memberName))
        static if(__traits(isVirtualMethod, overload)) {
          static if(!isAuthorizedVirtual!overload) {
            // and warn if we find any
            pragma(msg, T.stringof ~ "." ~ memberName
              ~ " " ~ typeof(overload).stringof
              ~ " is virtual");
            passes = false;
          }
        }
    }
  }
  return passes;
}

class Test {
  @Virtual void foo() {} // specifically marked, ok
  void foo(int) {} // virtual but not marked = problem
  final void f() {} // final, ok
}

// We'll use static assert to run the test and cause a compile
// error if any tests fail.
static assert(virtualCheck!(mixin(__MODULE__))); // test all classes in the module

The following is the compilation result of the preceding code:

Test.foo void(int) is virtual
virt.d(51): Error: static assert  (virtualCheck!(virt)) is false

How it works…

D's reflection gives us access to other information about items beyond their name and type. We can also retrieve other details, such as whether a function is virtual, its calling convention, and other attributes.

The rest of the implementation is a fairly straightforward application of what we already learned. We use compile-time reflection to drill down into all members of the module, check the information we're interested in, and then use pragma(msg, "string") to print the warning, if necessary. Since it is currently impossible to get the exact file and line numbers of a declaration with reflection, we instead print the full name and type so that the user can identify the function from the message. Finally, we return a flag instead of using the static assert statement in the test so that compilation doesn't fail immediately with the first note.

Tip

If you need to perform checks that cannot be fully completed at the compile time, consider calling the check functions in a unit test block so they run at runtime.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.145.82