views:

190

answers:

6

I have a macro that looks like this:

#define coutError    if (VERBOSITY_SETTING >= VERBOSITY_ERROR)    ods()

where ods() is a class that behaves similarly to cout, and VERBOSITY_SETTING is a global variable. There are a few of these for different verbosity settings, and it allows the code to look something like this:

if (someErrorCondition)
{
    // ... do things relating to the error condition ...

    coutError << "Error condition occurred";
}

And there is functionality in this framework to set the verbosity, etc. However, the obvious pattern breaks when not using braces in something like this:

void LightSwitch::TurnOn()
{
    if (!PowerToSwitch)
        coutError << "No power!";
    else
        SwitchOn = true;
}

because of the macro, will turn into this:

void LightSwitch::TurnOn()
{
    if (!PowerToSwitch)
        if (VERBOSITY_SETTING >= VERBOSITY_ERROR)
            ods() << "No power!";
        else
            SwitchOn = true;
}

Which is not the intended functionality of the if statement.

Now, I understand a way to fix this macro properly so it doesn't cause this problem, but I'd like to run an audit on the code and find any place that has this pattern of "if (...) coutError << ...; else" to find out if there are any other cases where this happens to make sure that when fixing the macro, it will indeed be correct functionality.

I can use any language/tool to find this, I just want to know the best way of doing that.

A: 

I think the real problem is the use of the macro - not the code that gets preprocessed. But I don't think this is the answer you are looking for.

I would find a way to do it without using macros at all - and if you do use conditional compiles you can do so in the call to ods() - depending on some #define it can use whatever functionality you want.

just my $.02

Tim
well the way I was going to fix it was to use a template, so: typedef ods<VERBOSITY_ERROR> coutError;
FryGuy
A: 

regular expression search?

Jim Buck
well, the problem is that the stuff in the if could be complicated, and the statements after the coutError could be complicated as well. What regular expression could be used?
FryGuy
+2  A: 

You could try - temporarily - modifying the macro to something like this, and see what doesn't compile...

#define coutError {} if (VERBOSITY_SETTING >= VERBOSITY_ERROR) ods()

The 'else' clauses should now give errors.

Roddy
good, but I think he also might have issues with ifs that do not have elses? Not sure though.
Tim
It's a good idea, but I don't think your line will cause the right problems. the else will still attach to the #defined if statement.
Douglas Leeder
I think this is what's needed:#define coutError {} ods()
FryGuy
+1  A: 

I think all good usages of the macro are preceeded with '{' or ';'.

So try this regular expression:

[^{;]\s*coutError

You'll need to turn on multi-line matching and search against entire files.

You might need to grab more stuff so that you can locate the line in question :-)

Alternatively changing the macro is a neat idea, if we can work out something that will fail correctly. Maybe a block followed by the conditional operator:

#define coutError {} (VERBOSITY_SETTING >= VERBOSITY_ERROR)?(ods()):(nullstream())

(But does require implementing a nullstream() operator.)

(Alternatively get rid of the conditional entirely temporarily - as you suggest is a comment to another answer @Roddy (currently the selected answer)).

p.s. I know you didn't ask, but an easy way to wrap the macro to make it safe is with a do {} while(false) loop.

Douglas Leeder
I don't think you can wrap this particular macro in anything and make it not suffer this problem.That will also catch some false positives, in that "if (...) coutError << "..."; someotherstatement;" isn't affected by the macro, but this regex will catch it.
FryGuy
Regex find = new Regex(@"if[^{};]+coutAgent[^{};]+;[^{};]+else", RegexOptions.Singleline);seems to do the trick
FryGuy
+1  A: 

I see in your comments above that you're thinking of using a template in the macro, and I can't comment yet (I'm 9 points short of it), so...

What stops you from doing

#define CoutError(s) { if (VERBOSITY_SETTING >= VERBOSITY_ERROR){ ods(s); } }

And then

void LightSwitch::TurnOn()
{
    if (!PowerToSwitch)
        CoutError("No power!");
    else
        SwitchOn = true;
}

And redefine ods to admit a string, or alternatively if you can't, then just define an OdsHelper function which takes a string and whose body is simply ods << inString?

I wouldn't play with macros trying to mimic the << syntax if there is no clear gain. At least with macros simulating the function syntax we're more used and we know we have to write the blocks to prevent strange issues.

Do you really need the << syntax?

And do you really need to introduce a template for this simple behaviour?

Oh and one last thing - don't use macros.

Daniel Daranas
Well the problem is more that it's mimicing cout. For instance: coutError << "Failed reading power: " << powerSetting << endl; And the code isn't mine, it's just part of the framework that we use where I work. I agree that macros are evil. In fact, this is the one I cite when I argue it :)
FryGuy
Great, now I can comment :)You should try to refactor the framework, deleting at least its more problematic macros and trying to establish standards for them.A common standard is that macros are in blocks. Tell that to your team and try to convince them. If you can't, then you're stuck :)
Daniel Daranas
+2  A: 

Don't bother trying to find all of the locations in your code where a logic error is occurring -- fix the problem at its source! Change the macro so that there's no possibility of error:

#define coutError if(VERBOSITY_SETTING < VERBOSITY_ERROR); else ods()

Note that what I've done here is inverted the test, added an empty statement for the then clause, and put the output object in the else clause. This still allows you to use << foo << bar after the macro, and if you have a trailing else clause belonging to a different if statement, it will get matched up properly, since it expands like so:

if(foo)
    coutError << bar;
else
    baz();

becomes

if(foo)
    if(VERBOSITY_SETTING < VERBOSITY_ERROR)
        ;
    else
        ods() << bar;
else
    baz();
Adam Rosenfield
This is an excellent fix; even simpler than the one I was going to do. However, the main point of the question was to find out if any bugs will surface by fixing the problem.
FryGuy