6
votes

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?

7
Actually, loading via RTTI happens ALL the time in actual practice. Every time you load a DFM resource, you fetch method addresses by name with RTTI. That's WHY event handlers have published visibility in the first place. The linker doesn't interpret DFM contents; nor does the compiler.Rob Kennedy
I know all about that. Please don't be pedantic. What I meant was referencing via RTTI from elsewhere in your code. (Doing it manually, in other words.) And that very rarely happens, unless you're doing some sort of RPC technique.Mason Wheeler

7 Answers

6
votes

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.
6
votes

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.

3
votes

ModelMaker Code Explorer contains an so-called Event handler view. It also shows event handlers not connected to any component.

2
votes

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.

2
votes

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.
2
votes

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.

2
votes

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 .