0

A colleague came me to today with an error with code that worked on Windows XP but fails on Windows 7:

User failed for login 'SalesOrdersystem'

My phychic debugging told me he was running a query against a database connection that was closed, or that he forgot to open.

Starting with ADO2.6, in Windows Vista, the default value of PersistSecurityInfo in a connection string is False, rather than True.

Prior to Windows Vista a connection string such as:

Data Source=deathstar;User ID=SalesOrderSystem;Password=password1

would keep the password in the connection string after the connection is opened, which makes it the equivalent of:

Data Source=deathstar;User ID=SalesOrderSystem;
      Password=password1;PersistSecurityInfo=true

Starting with Windows Vista the password is, by default, removed from a connection's ConnectionString property:

Data Source=deathstar;User ID=SalesOrderSystem

which is the equivalent of

Data Source=deathstar;User ID=SalesOrderSystem;
      Password=password1;PersistSecurityInfo=false

i knew my colleague was experiencing this behavior where the password is being removed. And then while the connection is closed he's trying to open a query (i.e. ADOQuery.Open) which imiplicitely tries to open the Connection. But without a password saved in the connection string he gets his original error

The question became, "Why are you using a connection without opening it first?"

We traced it back to (multi-threaded code) where he was using a connection that was later being freed:

pseudo-code:

customer := TCustomer.Create(ADOConnection)
ADOConnection.Free;
customer.RefreshFromDatabase;

rather than

customer := TCustomer.Create(DataModule.ADOConnection);
customer.RefreshFromDatabase;

In jest, i suggested he could mask the error, and leave the potential crash, by changing the connection string to include PersistSecurityInfo=True:

connectionString := ...+
    ';PersistSecurityInfo=True';

Which he did.


We have some library code that internally makes use of an ADOConnection object. i would love to be able to change my code from:

destructor TAsyncFill.Destroy; 
begin
   ...
   FreeAndNil(FADOConnection)
end;

to

destructor TAsyncFill.Destroy; 
begin
   ...
   FADOConnection.Close;
   FADOConnection.ConnectionString := 'This connection object has been freed. Why are you using it?';
   FreeAndNil(FADOConnection);
end;

But i am sure it will introduce errors, where things used to happen to work.

What i am thinking of is some sort of closure, where i can inject an OnConnect handler to the connection object:

destructor Destroy; 
begin
   ...
   FADOConnection.Close;
   FADOConnection.BeforeConnect := { 
       OutputDebugString('You''re using a connection that''s been freed!'); 
       Windows.Beep(1000, 60000) };
   FreeAndNil(FADOConnection);
end;

But Delphi doesn't have anonymous event handlers.

Can anyone think of a way to be able to alert people when they're using an object after it's been freed?


Note: i understand there is no support for what i'm asking. i'm asking for ideas for the best possible hacks - given the limits of reality.

Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • [Hack #6: Checking for valid object instance](http://hallvards.blogspot.com/2004/06/hack-6checking-for-valid-object.html). – NGLN Aug 26 '11 at 17:10
  • @ngln that hack can easily fail with access after free – David Heffernan Aug 26 '11 at 17:13
  • i don't have the ability to march in and change his code (if i did i would just fix the bug). So the `ValidateObject` and other procedures in the linked article can't really help me. – Ian Boyd Aug 26 '11 at 17:24

3 Answers3

2

As you are "FreeAndNil"ing your AdoConnection, I am assuming that you are instantiating it yourself as well. In that case what you could do is derive your own TMyAdoConnection and instantiate that. You do not even have to give it another class name if you go for the "interceptor" approach:

type
  TAdoConnection = class(AdoDb.TAdoConnection)
  end;

Then, override the protected DoConnect method. In spite of its name it isn't "just" a method to trigger the OnConnect event. It actually does open a connection. There is a similar DoDisconnect method as well which actually closes the connection.

In these two overridden methods, plus overrides of Create and Destroy, you can write a simple detection mechanism for when the Opens and Closes do not match the Creates and Destroys.

If you have a single AdoConnection instance you could just keep track of things in a couple of global variables. Otherwise you may have to write a small registry where you keep track of stuff for each instance. Gonna be hard though trying to find "Self" in that registry if the instance has been freed and set to nil. So you may have to forgo the FreeAndNil for the time being and recode any if Assigned(FAdoConnection) to detect that the instance still needs to be created, with something else.

Warning: This is based on TAdoConnection in Delphi 6. I don't have the Ado components installed in Delphi 5 at the moment. So you will have to check that the DoConnect and DoDiscoonect are present and virtual in D5 as well.

Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • That's *exactly* the kind of thing i was hoping for; thank you! – Ian Boyd Aug 26 '11 at 18:00
  • This isn't really a tenable solution for the fully general problem of access after free. It is useful in identifying this one bug, but you already found it! In principle you would need to add a check to every single method in every single class to detect other bugs of this nature. And that still would fail to detect access to fields, or properties backed by fields. The best approach I know of is runtime checking which involves no distracting boilerplate code modifications. I would advise against following the advice given here. – David Heffernan Aug 26 '11 at 18:49
  • @David: Of course it isn't a solution to the general problem. I didn't imply it was. And Ian wasn't looking for one. Yes, he already knew the cause (access after free), but wanted a way to pinpoint the offending code for this specific case. And that is exactly what I gave him. – Marjan Venema Aug 26 '11 at 19:44
  • I guess I don't understand. I thought the bug already was pinpointed. – David Heffernan Aug 26 '11 at 19:49
  • @David Heffernan: But you don't know the other places that might bee doing the same (wrong) thing. – Ian Boyd Aug 26 '11 at 20:53
  • why find just one bug when fastmm can find everyone of this nature – David Heffernan Aug 27 '11 at 13:27
1

FastMM with full debug will notify you when your code accesses an object after it has been freed.

enter image description here

Obviously you can't ship with that setting on, but switch it on when you run your test suite and such bugs will be flushed into open sight.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Yeah, i have it on in my projects. But people aren't going to enable it in their projects - especially if it shows problems. – Ian Boyd Aug 26 '11 at 17:22
  • @ian make your mind up time, do you want to detect these bugs or not. You need to get rid of people that don't want to use tools that locate bugs. – David Heffernan Aug 26 '11 at 17:26
  • i *do* want to detect these bugs. There's no need to fire anyone. – Ian Boyd Aug 26 '11 at 17:59
  • **i** do indeed. Not every other developer, on every other project here, however, does. – Ian Boyd Aug 26 '11 at 20:25
  • 1
    @Ian As the boss of a small team, perhaps I'm just a bit too used to being able to ensure all team members use such important tools. – David Heffernan Aug 26 '11 at 20:34
  • Changing a memory manager can cause problems to appear where none happened before. Introducing *actual* errors, where none existed before, in an attempt to catch *potential* errors is something some people take seriously. Personally, i do not. i am in favor of breaking every application we down, customer's businesses grinding to a halt. It's a small price to pay to ensure that i have better code. – Ian Boyd Aug 26 '11 at 20:49
  • changing memory manager doesn't introduce new errors, it just causes existing errors to manifest – David Heffernan Aug 27 '11 at 04:29
  • But if they never manifest with the old memory manager, are they really errors? (http://technet.microsoft.com/en-us/magazine/ff625273.aspx) – Ian Boyd Aug 27 '11 at 04:56
  • You have to accept that as a result of the way the memory manager works, it just never happens. One example might be that the MM doesn't recycle the memory for at least 10 free requests. And the application only makes 3 frees before it uses the connection again. It *will not* fail. – Ian Boyd Aug 27 '11 at 13:18
0

Why the coder is using a TADOConnection object that may have already been freed? Why not have the thread create it's own connection? That would allow the thread to control the lifecycle of the connection.

Chris Miller
  • 4,809
  • 4
  • 33
  • 50
  • The thread *is* creating it's own connection. He's taking a reference on the thread's connection, and keeping it after the thread (and it's personal connection object) is destroyed. Later he tries to use the freed connection. But that's not important now. – Ian Boyd Aug 26 '11 at 17:22
  • 1
    So the main thread (or another thread) is using the connection created by another thread? You want to avoid that, ADO recordsets need to be created by the same thread that created the connection. The best way to detect this is through code reviews. – Chris Miller Aug 26 '11 at 19:34
  • i know i want to avoid it. We've found the problem. i know it's there. i know how to fix it. – Ian Boyd Aug 26 '11 at 20:23