views:

400

answers:

6

Finding dead code in Delphi is usually real simple: just compile and then scan for routines missing their blue dots. The smart linker's very good about tracking them down, most of the time.

Problem is, this doesn't work for event handlers because they're published methods, which (theoretically) could be invoked via RTTI somehow, even though this almost never happens in actual practice.

I'm trying to clean up a large VCL form unit that's been bent, folded, spindled and mutilated various times throughout its history. It would sure be nice if I had some way to find event handlers that aren't actually referenced by the form's DFM and delete them. Is there any easy way to do this? A plug-in IDE Expert, for example?

+2  A: 

There is no solution that is guaranteed to give a correct answer in the most general case (based, as you note, on the possibility of calling them via RTTI).

One solution would be to do code coverage tests and look carefully at handlers that were never reached.

MarkusQ
I'm not looking for "the most general case"; I'm looking for the most common case: Check the list of event handlers declared in the top section of the form declaration in the PAS file against the handlers referenced in the DFM, and report any orphans.
Mason Wheeler
+5  A: 

This is a bit ugly (OK, it's a lot ugly), but for one unit it's close to foolproof, and requires no additional tools:

  1. Make sure that the current version of the form is checked into source control!
  2. Go to the top of the interface of the class where the event handlers are. Delete all of the event handler method interfaces.
  3. Look at Code Explorer/Error Insight. The methods which have implementations but no interfaces will be highlighted. Delete the implementations.
  4. Now save the unit. Delphi will, one at a time, complained about the missing event handler for each event that is actually handled. Write these down as the errors come up.
  5. Check out the original version of the form, and remove the event handlers for anything not on your list.
Craig Stuntz
I would add another step to compile and see which ones it complains about missing because they are called via code.
Jim McKeeth
How is it better than using the refactor method? It gets the references in code and in dfm all at once...
François
I find delete easier than refactoring, but it's a matter of personal choice, I guess.
Craig Stuntz
+2  A: 

I'm not aware of a preexisting app or plugin to do this, but it shouldn't be hard to script.

Assuming you're not using RTTI or manually assigning event handlers: (I'm a C++Builder user rather than Delphi, so the following may not be quite correct.)

  1. Make a list of all published methods in your code.
    • The proper way to do this is to read *.pas. Find each text block that starts with a class declaration or a published directive and ends with a end, private, or public. Within each of these text blocks, extract each procedure.
    • The easy way to do this is to make a list of common event handler types and assume they're published.
  2. Once you have this list, print everything from the list that's not found in your DFM file.

I'm most comfortable using Cygwin or Linux tools for scripting. Here's a bash script that works in Cygwin and should do what you want.

#!/bin/bash

for file in `find -name *.pas`; do
    echo $file:

    # Get a list of common event handling procedures.
    # Add more types between the | symbols.
    egrep '^[[:space:]]+procedure.*(Click|FormCreate|FormClose|Change|Execute)\(' $file | 
    awk '{print $2}' | cut -f 1 -d '(' > published.txt

    # Get a list of used event procedures.
    egrep '^[[:space:]]+On.* =' ${file%.pas}.dfm | awk '{print $3}' > used.txt

    # Compare the two.
    # Files listed in the left column are published but not used, so you can delete them.
    # Files in the right column were not by our crude search for published event 
    # handlers, so you can update the first egrep command to find them.
    comm -3 published.txt used.txt

    echo

done

# Clean up.
rm published.txt used.txt

To actually use this, if you're not familiar with Cygwin:

  • Download and install Cygwin. I think the default install should give you all of the tools I used, but I'm not positive.
  • Save the script to your source directory as cleanup.sh.
  • Start a Cygwin command prompt.
  • If your source is in c:\MyApp, then type cd /cygdrive/c/myapp
  • Type ./cleanup.sh and press Enter.
Josh Kelley
+1 for the proper way to handle this: I'll write a short script that can do it. However, the outlined way to find event handlers using egrep is bad. I'd assume that awk should be better equipped to do it, and using python for everything would be a much better fit still.
mghie
Using the --doc option of the compiler and starting with the resulting XML file should also help to get the list of published methods of the form.
mghie
How is this egrep use bad? (I know it's a quick hack, I'm just wondering what particular shortcomings it has.)
Josh Kelley
It's not working in context. You would want to grep only between "class(TForm)" and the first private / protected / public. This should be possible using awk, but not using grep.
mghie
+1  A: 

I dont think this is possible from an automatic point of view. event handlers are activated when a particular event occurs inside an object. That the even is not triggered in a given run doesnt mean that there isnt an execution pathway to lead to it.

also you can assign handlers dynamically at runtime so whats used in one situation is not garuanteed.

e.g.

button.onclick := DefaultClickHandler;

button.onClick := SpecialClickHandler;

Assuming that the click handlers match the onclick event signature, but you wouldnt get a compile if the signature was incorrect.


however, you can probably find all the abandoned handlers by looking for all the methods that find have a (Sender: TObject) method signature and comparing that his of methods to those in the .dfm (make sure you save it as text if you are working with an older version of delphi), antyhing not wired up automatically would be suspect in my book.

--

if you dont want to go down the cygwin path, you can load the src and dfm into two TStirngLists and rip out the name/idents from each and generate a list with a couple of loops and some string manipulations. my guess is about 20 minutes of work to get something you can live with .

MikeJ
Thank you, but as I said to MarkusQ, I'm looking at the most common case here, not the exceptions. I'm well aware that there's no foolproof way to find all dead-code event handlers with no false positives in every case. That doesn't mean a bit of automation can't be useful in narrowing your search.
Mason Wheeler
see my revised note avove
MikeJ
+2  A: 

There's a much easier approach than Craig's.

Go to a suspect event handler. Rename it in a consistent way--I do this by putting an x in front of the name, go down to the implementation and do the same thing. See what the compiler thinks of it.

If it's not happy you just change the names back.

You can use the same approach to eliminate data elements that no longer do anything.

Loren Pechtel
+5  A: 

Use the "Rename Method" refactoring to rename each event handler. Check the "View references before refactoring" checkbox.

Check the Refactoring window. If the event handler is linked to a control, there will be a "VCL Designer Updates" section show which control(s) are linked to the method.

This will also show if the method is called from any other units, or is assigned programmatically.

Note: this is for D2006, may be slightly different in later versions.

Gerry