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?
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:
Make sure that the current version of the form is checked into source control!
Go to the top of the interface of the class where the event handlers are. Delete all of the event handler method interfaces.
Look at Code Explorer/Error Insight. The methods which have implementations but no interfaces will be highlighted. Delete the implementations.
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.
Check out the original version of the form, and remove the event handlers for anything not on your list.
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.
ModelMaker Code Explorer contains an so-called Event handler view. It also shows event handlers not connected to any component.
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 .
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.
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.)
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.
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.
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.
Related
Context
So I finally give a try to Fish, and as one would expect I encounter some frictions due to differences with my usual routines.
The most astonishing for me, as for many other, was the absence of the bang operator. I'm fine with the lose of sudo !!, as the suggested function replacement seems even better to me, I named it gar which means "To make, compel (someone to do something); to cause (something to be done." However I'll need a replacement for !<abc><enter> which grab the last history line starting with <abc> and run it without further ado, suggestions are welcome.
Now, for the more personal things:
- I use a Typematrix 2030 keyboard
- I use a bépo layout
- I like to configure default finger position keys with the most used actions
Aims
As on my keybord <enter> is well positioned and is semantically relevant for that, ideally I would like to achieve the following key binding:
ctrl-enter: accept the whole suggestion and run it without further confirmation
ctrl-tab: accept the whole suggestion and wait for further edit
alt-enter: redo the last command without further confirmation
But according to xev it appears that, at least with Gnome-terminal, this combinations are not recognized. Are they terminal that supports it? For now I remapped these three to <ctrl>-i, <alt>-i and <alt>-I respectively:
bind --preset \ci forward-char execute
bind --preset \ei forward-char
bind --preset \eI forward-word
This works as expected, but it seems that now the tab key will also map to the first item. I guess that tab map to <alt>-i at some point in the shell stack. I wasn't aware of that, so I don't know yet if it will be possible for Fish to separate each of them.
To manage jobs, I also came with
bind --preset \es fg
bind --preset \eS bg
The first works as expected, but the second one doesn't. With application like vim, the binding should be operated in the application configuration itself of course. But for things as trivial as yes, <alt>-S won't work as expected while <crl>-z continue to operate normally.
I also would like to bind some commands like ls -alh and git status --short to a directly executed command, showing the result bellow the currently edited line, allowing to further type seamlessly, but didn't find the way to do it yet.
Summary of remaining question
So here are my more precise questions summarised:
how do I bind the sleep signal to <alt>-S?
is there a terminal I can use where <alt>-<enter> and <ctrl>-<enter> works?
how to seamlessly run command while maintaining the current line edition in place?
can you bind something to <alt>-i without altering <tab>?
how do I bind the sleep signal to -S?
What you are doing with bind \es fg is to alter a binding inside the shell.
But when you execute yes, the shell isn't currently in the foreground, so shell bindings don't apply.
What you'd have to do instead is change the terminal settings via stty susp \cs,
but fish resets the terminal settings when executing commands (so you can't accidentally break them and end up in an unusable environment), so there currently is no way to do this in fish.
can you bind something to <alt>-i without altering <tab>?
Sure. You bind \ei. Which is escape+i, which is alt-i (because in a terminal alt is escape).
Your problem is with ctrl-i, which in the way terminals encode control+character is tab. The application receives an actual tab character, and at that point the information has been lost.
is there a terminal I can use where - and - works?
Most terminals should send \e\r for alt-enter. ctrl-enter again is unencodable with the usual code (because \r is ctrl-m), just like ctrl-tab is.
Any fix to this requires the terminal to encode these combination differently.
how to seamlessly run command while maintaining the current line edition in place?
I don't know what you mean by this. I'm guessing you want fish to remain open and editable while a command also runs in the foreground. That can't work. There's no way to synchronize output from two commands to a terminal, not with cursor movement being what it is.
My program accepts input file names either as command line parameters or in a drag and drop operation or in Explorer by clicking on filenames with an extension that is associated with my program.
The command line and drag and drop work fine, but it is clicking on the filenames in Explorer that causes problems when the filepaths of the files clicked on have spaces in them, e.g.:
c:\temp\file one.txt
c:\my directory\filetwo.txt
c:\my directory\file three.txt
then, the ParamStr function gives me back:
ParamStr(1): c:\temp\file
ParamStr(2): one.txt
ParamStr(3): c:\my
ParamStr(4): directory\filetwo.txt
ParamStr(5): c:\my
ParamStr(6): directory\file
ParamStr(7): three.txt
How can I best reconstitute these back into the three filenames that I need?
It might be your shell file association that does not include the pair of "".
Like these ones for opening:
"C:\Program Files\WinRAR\WinRAR.exe" "%1"
or with DDE message:
[open("%1")]
Command-line parameters with spaces in them, such as filenames, should be quoted. This makes the param parser realize that it's supposed to keep them together. If the user's not quoting the filename, it's operator error.
If a drag-and-drop system is doing this, on the other hand, then you've got a bug in your drag-and-drop library and you need to talk to whoever created it. I'm a bit confused, though, as to why drag-and-drop operations are messing with ParamStr. That should only be set by the params passed to your program at the moment it's invoked, not once it's up and running. Maybe I'm missing something?
i use the CmdLineHelper unit, from here.
I'm trying to get rid of some hints(*) the Delphi compiler emits. Browsing through the ToolsAPI I see a IOTAToolsFilter that looks like it might help me accomplish this through it's Notifier, but I'm not sure how to invoke this (through what xxxServices I can access the filter).
Can anyone tell me if I´m on the right track here? Thanks!
(*) In particular, H2365 about overridden methods not matching the case of the parent. Not so nice when you have about 5 million lines of active code with a slightly different code convention than Embarcadero's. We've been working without hints for months now, and we kinda miss 'm. :-)
Even if you could query BorlandIDEServices for IOTAToolsFilter, that interface isn't going to help you do what you're asking. That interface was introduced as part of a mechanism for adding additional build tools (compilers, etc.) to the IDE (before the IDE used MSBuild). It allowed you to write a custom "filter" to handle output from a particular build tool, but it would not let you apply a filter to one of the built-in tools (like the delphi compiler).
The reason the Supports(BorlandIDEServices, IOTAToolsFilter, OTAToolsFilter) call fails in Delphi2010 is that once MSBuild support was added to the IDE, the old way of adding build tools to the IDE was disabled, and the BorlandIDEServices interface no longer supported IOTAToolsFilter.
The declaration of IOTAToolsFilter should probably have been marked deprecated in ToolsAPI.pas (or least it should have been mentioned in the source code comment that it is no longer supported).
As far as your desire to filter a particular hint, I'm not aware of a way to do that via the ToolsAPI. It seems like a reasonable thing that can be added to IOTAMessageServices (the ability to enumerate, filter, and possibly change the messages in the IDE's Message View). I would enter a request in QualityCentral for that.
Also, please vote for QC #35774 (http://qc.embarcadero.com/wc/qcmain.aspx?d=35774), as if that were implemented, you would not need to use the ToolsAPI for this sort of thing.
According to http://docwiki.embarcadero.com/RADStudio/en/Obtaining_Tools_API_Services it should be possible to access it directly using BorlandIDEServices, eg:
var
OTAToolsFilter: IOTAToolsFilter;
begin
if Supports(BorlandIDEServices, IOTAToolsFilter, OTAToolsFilter) then
ShowMessage('supports IOTAToolsFilter')
else
ShowMessage('IOTAToolsFilter NOT supported');
end;
However this doesn't return the desired interface in Delphi 2010 (you'll get the not supported message), so there's either an error in the documentation, or an error in BorlandIDEServices not returning the correct interface.
I just caught myself doing something I do a lot, and wanted to generalize it, express it, share it and see who else is following this general practice, to find some other example situations where it might be relevant.
The general practice is getting something wrong first, on purpose, to establish that everything else is right before undertaking the current task.
What I was trying to do, specifically, was to find examples in our code base where the dojo TextArea widget was used. I knew (because I had it in front of me - existence proof) that the TextBox widget was present in at least one file. So I looked first for what I knew was there:
grep -r digit.form.TextBox | grep -v
svn
This wasn't right - I had made a common (for me) mistake of leaving off the star, so I fixed that:
grep -r digit.form.TextBox * | grep
-v svn
which found no results! Quick comparison with the file I was looking at showed me I had misspelled "dijit":
grep -r dijit.form.TextBox * | grep
-v svn
And now I got results. Cool; doing it wrong first on purpose meant my query was correct except for looking for the wrong thing, so now I could construct the right query:
grep -r dijit.form.TextArea * | grep
-v svn
and be confident that when it gave me no results, it was because there are no such files, and not because I had malformed the query.
I'll add three other examples as answers; please add any others you're aware of.
TDD
The red-green-refactor cycle of test-driven development may be the archetype of this practice. With red, demonstrate that the functionality doesn't exist; then make it exist and demonstrate that you've done so by witnessing the green bar.
http://support.microsoft.com/kb/275085
This VBA routine turns off the "subdatasheets" property for every table in your MS Access database. The user is instructed to make sure error-handling is set to "Break only on unhandled errors." The routine identifies tables needing the fix by the error that is thrown. I'm not sure this precisely fits your question, but it's always interesting to me that the error is being used in a non-error way.
Here's an example from VBA:
I also use camel case when I Dim my variables. ThisIsAnExampleOfCamelCase. As soon as I exit the VBA code line if Access doesn't change the lower case variable to camel case then I know I've got a typo. [OR, Option Explicit isn't set, which is the post topic.]
I also use this trick, several times an hour at least.
arrange - assert - act - assert
I sometimes like, in my tests, to add a counter-assertion before the action to show that the action is actually responsible for producing the desired outcome demonstrated by the concluding assertion.
When in doubt of my spelling, and of my editor's spell-checking
We use many editors. Many of them highlight misspelled words as I type them - some do not. I rely on automatic spell checking, but I can't always remember whether the editor of the moment has that feature. So I'll enter, say, "circuitx" and hit space. If it highlights, I'll back up over the space and the "x" and type another space - and learn that I spelled circuit correctly - but if it doesn't, I'll copy the word and paste it into a known spell-checker to see whether I did.
I'm not sure it's the best way to act, as it does not prevent you from mispelling the final command, for example typing "TestArea" or something like that instead of "TextArea" (your finger just have to slip a little for such a mistake).
IMHO the best way is to run your "final" command, but on two sample files first : one containing the requested text, another that doesn't.
In other words, instead of running a "similar" command, run the real one, but over "similar" data.
(Not sure if this would be a good idea to try for real!)
For example, you might give the system to the users for testing and tell them the password to get started is "Apple".
You know the users are fully up and ready to test (everything is installed and connections to databases working) when they contact you and say the password doesn't work (it's actually "Orange").
I often accidently step into code that I'm not interested in while debugging in Delphi.
Let's start by saying that I know that you can step over with F8, and that you can run to a certain line with f4.
Example:
function TMyClass.DoStuff():Integer;
begin
// do some stuff
bla();
end;
procedure TMyClass.Foo()
begin
if DoStuff()=0 then // press F7 when entering this line
beep;
end;
Example: I want to step into method DoStuff() by pressing F7, but instead of going there, I first end up in FastMM4.FastGetMem(), which is a massive blob of assembly code that obviously I'm not interested in at the moment.
There are several ways to go about this, and I don't like any of them:
Add a breakpoint on "bla" (almost useless if you only want to step into DoStuff on special occasions, like iteration 23498938);
Instead of pressing F7, manually move the cursor to "bla", and press F4 (Works for this simple example. In practice, it doesn't);
In case of FastMM: temporarily disable fastmm;
Is there any way to hint the IDE that I'm never interested into stepping into a certain block of code, or do I always have to set extra breakpoints or use F4 to try to avoid this?
I'm hoping for some magic compiler directive like {$NODEBUG BEGIN/END} or something like that.
In most cases being able to exclude entire units would be fine-grained enough for me, but being able to avoid certain methods or even lines of code would be even better.
Update: Maybe codegear should introduce something like skip-points (as opposed to break-points) :-)
There is a "magic nodebug switch". {$D-} will disable the generation of debug code. Place that at the top of your FastMM unit and you won't end up tracing into it. And if you do end up in a function you don't want to be in, SHIFT-F8 will get you out very quickly. (WARNING: Don't use SHIFT-F8 from inside an assembly-code routine that plays around with the stack. Unpredictable behavior can result. F4 to the bottom of it instead.)
If you're jumping into FastMM code, then there are memory operations occurring. The code you've shown doesn't have any memory operations, so your question is incomplete. I'll try to guess at what you meant.
When a subroutine has local variables of compiler-managed types (such as strings, interfaces, or dynamic arrays), the function prologue has non-trivial work to do. The prologue is also where reference counts of input parameters are adjusted. The debugger represents the prologue in the begin line of the function. If the current execution point is that line, and you "step into" it, you'll be taken to the RTL code for managing the special types. (I wouldn't expect FastMM to be involved there, either, but maybe things have changed from what I'm used to.) One easy thing to do in that situation is to "step over" the begin line instead of into it; use F8.
If you're really pressing F7 when entering your highlighted line, then you're doing it wrong. That's stepping into the begin line, not the line where DoStuff is called. So whether you get taken to the FastMM code has nothing to do with the implementation of DoStuff. To debug the call to DoStuff, the current execution point should already be the line with the call on it.
If you only want to debug DoStuff on iteration 23498938, then you can set a conditional breakpoint in that function. Click in the gutter to make a normal breakpoint, and then right-click it to display its properties. There you can define a condition that will be evaluated every time execution reaches that point. The debugger will only stop there when the condition is true. Press F8 to "step over" the DoStuff call, and if the condition is true, the debugger will stop there as though you'd pressed F7 instead.
You can toggle the "use debug DCUs" option to avoid stepping into most RTL and VCL units. I don't know whether FastMM is included in that set. The key difference is whether the DCUs you've linked to were compiled with debug information. The setting alters the library path to include or exclude the subdirectory where the debug DCUs are. I think you can configure the set of included or excluded debug directories so that a custom set of directories is added or removed based on the "debug DCUs" setting.
Back to breakpoints. You can set up breakpoint groups by assigning names to your breakpoints. You can use an advanced breakpoint to enable or disable a named group of breakpoints when you pass it. (Breakpoint groups can have just one breakpoint, if you want.) So, for example, if you only want to break at location X if you've also passed some other location Y in your program, you could set a disabled breakpoint at X and a non-breaking breakpoint at Y. Set the "enable groups" setting at Y to enable group X.
You can also take advantage of disabled breakpoints without automatic enabling and disabling. Your breakpoints appear in the "breakpoints" debugger window. If you're stepping through DoStuff and you decide you want to inspect bla this time, go to the breakpoint window and enable the breakpoint at bla. No need to navigate to bla's implementation to set the breakpoint there.
For more about advanced breakpoints, see Using Non-Breaking Breakpoints in Delphi, and article by Cary Jensen from a few years ago.
I may have missed something with your post, but with FastMM4 you can edit the FastMM4Options.Inc include file and remove the '.' from the following define:
From FastMM4Options.inc ****
{Enable this option to suppress the generation of debug info for the
FastMM4.pas unit. This will prevent the integrated debugger from stepping into
the memory manager code.}
{$.define NoDebugInfo}
When recompiling (might need building) the debugger will (should) no longer debug the FastMM code.
Use a precompiled non-debug DCU of FasmMM
In the project dpr file, I use
uses
{$IFNDEF DEBUG} FastMM4, {$ENDIF}
... // other units
to exclude FastMM4 during debug mode. Requires no change in FastMM4 so I don't have to remember to add {$D-} in FastMM when I change to a different version.
AFAIK, the debugger is only aware of the files in Browsing Path that you can modify in Options. So if you exclude the paths of modules you're not interested in debugging that will give the effect of what you want to do.
One caveat: code completion also relies on Browsing Path so you might run into occasions that code completion falls short when needed.
Although it isn't a direct answer to your question, you could modify your first suggested solution by putting breakpoint at bla that is only enabled when a breakpoint at Foo is passed (or some other condition of your choose, such as iteration count). Then it will only break when you want it to.
As an aside, I am finding more and more that I am not halting execution at break points, but rather dumping variable values or stack dumps to the message log. This allows more careful analysis than on-the-fly inspection of variables, etc. FWIW.
No. I don't believe there is a way to tell the debugger to never stop in a certain section of code. There is no magic directive.
The best you can do when you get into a routine you don't want to be in is to use Shift+F8 which will Run until the Return. Then do a F7 or F8 to exit the procedure.
Hmmm. Now I see Mason's answer. Learned something. Thanks, Mason. +1