views:

178

answers:

5

In the process of automatically renaming many variables in a big project I may have created a lot of things like these:

class Foo {
    int Par;
    void Bar(int Par) {
        Par = Par;       // Nonsense
    }
};

Now I need to identify those locations to correct them. E.g. into "this->Par = Par;". Unfortunately the Visual C++ Compiler des not give me any comment about it even with all warnings on. I remember there once was a warning about it. It said "Code has no effect" or something. But it seems to be gone maybe because some people used that practice to avoid "unreferenced parameter" warnings. Is there a way to re-activate that warning? Does GCC warn here? Any Idea?

A: 

I am thinking about writing a script to go over the files and detecting lines containing the pattern

exp=exp;

ignoring all white spaces in the line.

ArunSaha
This can be done with a perl one-liner. See above.
Jason R. Mick
+1  A: 

As Brian said in his comment, this is one really good argument for having a naming convention which differentiates between member variables and function arguments for classes (of which the "m_" prefix is just one example). I'd suggest that approach, lest you need to repeat the search process regularly down the road.

Nick
Well to be honest the problem arised because I decided to remove those prefixes :-). Do not like them any more. Expressions look just too ugly and too lengthy. I thing after being able to run the project again I will not have much of a problem in future. When writing new code I am aware of those things.
kaptnole
Well, for what it's worth, this is one of those situations where (in my opinion) you are sacrificing future maintainability of the code for "prettiness"; if not for yourself, then for other developers. It may be perfectly acceptable in your case (eg: small project, single developer, limited lifetime, etc.), but it's not something I would do.
Nick
+3  A: 

As kaptnole pointed out, the regex I crafted could be used directly in visual studio. Your pattern is:

^[\s\t]*([a-zA-Z_0-9])[\s\t]=[\s\t]\1[\s\t];[\s\t]*$

Follow the directions listed here:
http://msdn.microsoft.com/en-us/library/2k3te2cs%28VS.80%29.aspx
...and happy finding (without ever touching Perl!).


This perl one liner will do it for you:

perl -n -e'/^[\s\t]*([a-zA-Z_0-9]*)[\s\t]*=[\s\t]*\1[\s\t]*;[\s\t]*$/&&print "$. $_"' test_me && echo

I tested it on a file containing the following and it correctly detected all matches:

hi = hi; 
hi= hi ;
  hi=hi   ;

Output....

xxxxx@yyyy% perl -n -e'/[\s\t]*([a-zA-Z_0-9]*)[\s\t]*=[\s\t]*\1[\s\t]*;[\s\t]*$/&&print "$. $_"' test_me && echo
1 hi = hi; 
2 hi= hi ;
3   hi=hi   ;
xxxxx@yyyy%

My first thought was to do it in Awk, but apparently Awk doesn't store its matches! :(

But hey, this Perl script is pretty snazzy itself... it even prints the line numbers of the find!


EDIT 1

And to answer your other question, I compiled a simple test program with such an assignment inside main with the "-pedantic" and "-Wall" flags using gcc and g++ and received no warnings in either... so I guess it doesn't warn you of this kind of redundancy.

Here's my test program:

int main (int argc, char *argv[]) {
  int bob=5;
  bob=bob;
  return 0;
}

EDIT 2

Please note my above perl script does NOT check to see if there's a local variable of an identical name inside a function. In that case the statement might be valid, but poor style (still might be good to warn about).

And as Josh points out, the flag "-Wshadow" WILL warn about this in gcc/g++ in this specialized case.

I would suggest following Josh's advice about using const for static function arguments. In fact, any variable not passed by reference should generally be const

e.g.

void hello_world_print_numbers(int number_1, int number_2, int number_3) {
   ...
}

is a misassignment waiting to happen, so instead use:

void hello_world_print_numbers(const int number_1, const int number_2, const int number_3) {
   ...
}

...likewise in general with pointers, except in the case of passed pointers to arrays (and be careful there to pass in proper array bounds!).

void hello_world_print_numbers(const int * number_1, const int * number_2, const int * number_3) {
   ...
}

EDIT 3

I forgot my ^ at the start of my regex. While seemingly trivial this causes it to improperly tag assignments of type my_class->name=name;. This was wisely pointed out by RC. The regex is now fixed and should no longer have this issue. Thanks RC!

Jason R. Mick
+1: for the quick Perl script. (I thought about the idea, but you actually came up with the script). [BTW, I could not understand how EDIT 2 is helping the answer.]
ArunSaha
Thanks! Edit 2 is explaining how if he switched to using constant variables (in cases when he didn't need to modify the passed in variable), such assignments would give an error -- an compile-time way of detecting this, without resorting to a script like mine. Unless he declared a local variable with the same name, in which case there would still be no error...
Jason R. Mick
I believe that this incorrectly finds this->x = x;
RC
Thank you, maybe it even works with visual studio directly as you can use regular expressions in the search there
kaptnole
@ RC. Whoops! Forgot my "^" Good catch!! Thank you very much. @ kaptnole -- great! I believe visual studio's regex search captures its matches, so you should be good to go, that way.
Jason R. Mick
+5  A: 

A couple of compilers can generate warnings on this:

  • GCC and Clang can warn on code like this if you add the -Wshadow option. (Specifically, while they don't warn about the meaningless assignment, they do warn about the local variable Par shadowing the member variable Par - you may or may not like this.)
  • Embarcadero C++Builder does not warn that Par = Par is useless, but it can warn that Par isn't used after it's assigned to, which should meet your needs.

I suspect a tool like PC-Lint could also identify code like this.

Another solution is to mark your parameters as const:

class Foo {
    int Par;
    void Bar(const int Par) {
        Par = Par;       // Compiler error!
    }
};

const on pass-by-value parameters is not part of the function signature, so you only need to add it to the function definitions within your .cpp file, not your function declarations within your .h file. In other words, it's legal to do this:

// foo.h
class Foo {
    int Par;
    void Bar(int Par);
};

// foo.cpp
void Foo::Bar(const int Par) { ... }
Josh Kelley
If OP knew the places to go and add the `const`, he could as well fix the next line instead.
ArunSaha
@ArunSaha - `const` could be added to all pass-by-value parameters (by a script, for example), and it's arguably a good programming practice anyway (so it won't hurt to add it everywhere), and using it will help prevent bad assignments like this from occurring in the future.
Josh Kelley
@Josh Kelley: Got it now! Good point. Incidentally, I myself actually practice your recommendation. But somehow I could not connect to your suggestion on first reading :(. Your `-Wshadow` suggestion is, IMHO, more direct. +1.
ArunSaha
That -Wshadow option would be veeery nice in my situation as it will also find other problems. Unfortunately I will have to do much more work before I can even think of compiling the project on gcc. But that was on the agenda anyway so now I think I will just postphone the whole variable renaming action. Thank you!
kaptnole
+1  A: 

You can try this, though I would expect that you could find some false positives or some cases where it may miss, but it should find the straight forward ones.## Heading ##

I took your class and put it inside a file Foo.h as:

class Foo {
    int Par;
    void Bar(int Par) {
        Par = Par;       // Nonsense
        Par=Par;       // Nonsense
        Par  =  Par;       // Nonsense
        Par  =  Par   ;       // Nonsense

        this->x = x;  // Do not match this
    }
};

Then I created the following Perl Script called match.pl

#!/usr/bin/perl
use strict;

my $filename = $ARGV[0];
open(my $F, $ARGV[0]) || die("Cannot open file: $filename");
print "Procesing File: $filename\n";

my $lineNum = 0;
while (<$F>)
{
    $lineNum++;

    chomp;
    my $line = $_; 
    if ($line =~ /(?:^|\s+)(\w+?)\s*=\s*\1\s*;/)
    {
        print "\t$filename:$lineNum: $line\n";
    }
}

Then you can run it.

%> ./match.pl Foo.h 
    Procesing File: Foo.h
    Foo.h:4:         Par = Par;       // Nonsense
    Foo.h:5:         Par=Par;       // Nonsense
    Foo.h:6:         Par  =  Par;       // Nonsense
    Foo.h:7:         Par  =  Par   ;       // Nonsense

Then on Linux (I'm sure there is a similar command on Windows) you can do:

%> find *.cpp *.h -exec ./match.pl {} \; 
Procesing File: test.cpp
Procesing File: test2.cpp
Procesing File: test3.cpp
Procesing File: Foo.h
    Foo.h:4:         Par = Par;       // Nonsense
    Foo.h:5:         Par=Par;       // Nonsense
    Foo.h:6:         Par  =  Par;       // Nonsense
    Foo.h:7:         Par  =  Par   ;       // Nonsense
RC
Check out my perl one-liner below -- its much shorter...
Jason R. Mick
Thanks for your effort. I might try with regular expressions directly from visual studio search.
kaptnole