Home delphi Check whether function result value is used
Reply: 3

delphi Check whether function result value is used

qGold
1#
qGold Published in 2017-11-12 23:16:20Z

My question is the same as this one here except for the language. I am using Delphi.

Following the link you'll see in Java this seems to be impossible, so I would assume this applies to Delphi as well, but I am not entirely sure

I want to know, whether a function can check, if the calling procedure/function is making use of the return value the function provides.

I've tried this, but it didn't work: (I guess if a solution exists it would behave similar to this)

  if Assigned(Result) then begin
    showmessage('return value is assigned');
  end else begin
    showmessage('return value is NOT assigned');
  end; 

It may seem to be useless to check, but my function produces an encrypted file, for which the return value is beeing needed to decrypt this file, so it would be nice to notify the user instead of just producing an useless file and therefore I would like to check the usage of the return value.

Possible workaround:

I've got an Idea, but either don't know whether this is possible nor how to do that: The function could scan the part of RAM, which is occupied by my application and check for the return value (String) to exist after the command Result := 'something like F8975BE8AC0192#2983FE4A#B9DFE25' has been fired. It is soooo unlikeley that the String exists twice, that this simple check would completely fit my purpose. In general this may not work, because the Result could exist twice, but in my case this is somehow impossible.

So to do this workaround I expand my original question: How can I search for a String in the part of RAM occupied by the application containing the function, which wants to check this? As far as I know it would be much harder to check this for an external application, which is not needed here.

Chris Reynolds
2#
Chris Reynolds Reply to 2017-11-12 23:50:33Z

So I think what you are asking is whether you can return two pieces of data from a function, the encrypted string and the decryption key. You can either declare a class with the two items and return an instance of that or you can pass them both back as part of the parameter interface.

procedure cruncher(incoming:string;var encrypted:string;var decryptkey:string)

As a matter of personal style, I would be against passing back half the answer through a function result.

Craig Young
3#
Craig Young Reply to 2017-11-13 13:32:44Z

What you're trying to do is not possible in Delphi. And it's not a "language issue". But it's also not a problem.

You're really thinking about this back-to-front. I'll try to explain the why; and hopefully you'll abandon this folly.

Summary

  • Functions should do one thing only. The more things a function tries to do: the more difficult it is to implement correctly, and the more difficult it is for others to reuse.
  • Any hack you may conceive can easily be bypassed. So you end up with more complexity and the same non- problem.
  • It's not your responsibility to ensure that callers use your function correctly. That way lies madness and an ever-growing rabbit-warren.
  • You should only implement your function according to a "contract" and it's the caller's responsibility to use it correctly. If caller doesn't use it correctly, caller has a bug until the calling code is fixed. Simple as that.

Do only one thing

When functions do more than one thing, it becomes impossible (without careful refactoring) to use one feature and not the other. The function is more difficult to test because: more things change during the call, more setup is required, more things should be checked, there are likely more permutations that need to be considered.

The scenario in the Javascript question is even worse because the 2 things have a fundamental difference:

  • The function-style version can guarantee not to change state.
  • Whereas the other style is intended to modify state.

This is a concept of mutability vs immutability. And it's extremely beneficial to know when immutability is guaranteed. Bundling both into a single function means you no longer have immutable version.

Hacking a "solution" is pointless

Let's suppose you find some hack to interrogate the call-stack outside the function itself in order to confirm the caller stores the function result to a variable for later use.

Let's also put aside concerns of different OS, CPU architecture, compiler optimisations etc.

Nothing prevents the caller doing the following:

S := HybridFunction(...);
if (S <> '') then S := '';

Yes caller stored the variable, but quite obviously didn't use it. But caller doesn't even have to be so blatant about it. Storing the variable in a class field but never referencing it again has the same effect.

Do not double-check your callers - it is NOT your responsibility

You have enough to worry about ensuring your code behaves correctly without having to worry about what your callers do. If you try to compensate for callers then:

  • If caller is correct, your special code is pointless.
  • If caller is wrong, you can't "fix" anything. You cannot know what every possible caller should do.

The effort is better spent working on the caller code and ensuring it is correct!

Also, if you worry about your callers; what about your caller's callers? Or your caller's caller's callers? ... It never ends.

Implement to a contract and be done

You can force your caller to receive a result by using an out parameter. But that still doesn't prevent the caller from not using the out value after the call. So you'd sacrifice clean function-style calling convention for zero benefit.

I assume you have a function along the following lines:

function EncryptFile(): TKey; { returns the decryption key }

There's already a problem here because your function is obviously doing 2 things. Split it into 2 functions and your caller will also be easier to write.

function GetKey(): TKey;
procedure EncryptFile(AKey: TKey);

Then your caller can validate before trying to Encrypt:

LKey := GetKey();
if Assigned(LKey) then 
  EncryptFile(LKey)
else { report the error as you choose }

And this is why I say you're going about this back-to-front.

nil
4#
nil Reply to 2017-11-14 12:03:45Z

I believe your question might stem from either the desire to implement something absolutely bullet-proof, or a design choice that makes things harder than they are.

Bullet proof implementation

If you have done a good job as a developer, function naming and signature will indicate the intended use to a caller. Whether he understands that and uses it correctly in the end, is likely out of your control, as comments have indicated: where do you stop? There might always be a way to defeat your implementation.

Can and should anything be done?

But thinking about the technical aspect of your question and the inability to check if something is done with a function result - as the functions code is already exited - my idea would have been to make the result itself 'smarter'.

You mentioned your encryption key is a string you would call 'unique'. So it could be possible to use Delphi's string reference counting - at least for strings other than WideString - to make sure it is still referenced somewhere. Coupled with an IInterfacedObject that is also reference counted, the TInterfaceObject could check if it outlives the monitored string.

Interface

  IRefCheck = interface
  ['{E6A54A33-E4DB-4486-935A-00549E6793AC}']
    function Value: string;
  end;

Implemented by a TInterfacedObject

  TRefCheck = class(TInterfacedObject, IRefCheck)
  strict private
    FString: string;
  public
    constructor Create(const S: string);
    destructor Destroy; override;
    function Value: string;
  end;

Implemented like

constructor TRefCheck.Create(const S: string);
begin
  FString := S;
end;

destructor TRefCheck.Destroy;
begin
  Assert(StringRefCount(FString) > 1, 'Reference check failed for ' + FString);
  inherited Destroy;
end;

function TRefCheck.Value: string;
begin
  Result := FString;
end;

So whenever TRefCheck is destroyed and holds the only reference to its string, the Assert will fail. I opted for the interface as result because when it was a string, the caller didn't need to control the life-time of the result and doesn't need now. Your function will now look similar to this:

function GetEncryptedFileKey(Key: string): IRefCheck;
begin
  Result := TRefCheck.Create(Key);
end;

And is used like this:

procedure Test;
var
  S: string;
begin
  S := GetEncryptedFileKey('Test Value 1').Value;
  GetEncryptedFileKey('Test Value 2');
end;

A small test program calling Test will fail with

EAssertionFailed: Reference check failed for Test Value 2

As this requires the knowledge of the IRefCheck interface for the caller to access the Value and implicit operators are not available for classes (or interfaces) I tried it with an record, so it can be used like your string function before:

  TRefCheckRec = record
  private
    RefCheck: IRefCheck;
    function AsString: string;
  public
    class operator Implicit(Value: TRefCheckRec): string;
    class function CreateNew(S: string): TRefCheckRec; static;
  end;

function TRefCheckRec.AsString: string;
begin
  RefCheck.Value;
end;

class function TRefCheckRec.CreateNew(S: string): TRefCheckRec;
begin
  Result.RefCheck := TRefCheck.Create(S);
end;

class operator TRefCheckRec.Implicit(Value: TRefCheckRec): string;
begin
  Result := Value.AsString;
end;

Your function and the test code then look like this:

function GetEncryptedFileKey(Key: string): TRefCheckRec;
begin
  Result := TRefCheckRec.CreateNew(Key);
end;
procedure Test2;
var
  S: string;
begin
  S := GetEncryptedFileKey('Test Value Record');
  GetEncryptedFileKey('Test Value Record 2');
end;

At the end even with all this, defeating is as easy as never really 'using' S, in fact the test code does exactly that. All fluff and no advantage over

function GetEncryptedFileKey: string;

or

procedure EncryptFile(var EncryptionKey: string);

Design choice

Reading your comments and the question what I believe I would have done is encapsulate your logic into a class, and let your function return an instance of it.

  TEncryptedFile = class
  private
    FEncryptionKey: string;
    FKeyPersisted: Boolean;
  public
    constructor Create;
    destructor Destroy; override;
    function GetEncryptionKey: string;
    procedure SaveEncryptionKeyToFile;
  end;

constructor TEncryptedFile.Create;
begin
  FKeyPersisted := False;
  FEncryptionKey := ...
end;

destructor TEncryptedFile.Destroy;
begin
  if not FKeyPersisted then
    SaveEncryptionKeyToFile;
  inherited Destroy;
end;

function TEncryptedFile.GetEncryptionKey: string;
begin
  Result := FEncryptionKey;
  FKeyPersisted := True;
end;

procedure TEncryptedFile.SaveEncryptionKeyToFile;
begin
  FKeyPersisted := True;
  // Save to file
  ...
end;

Your function now returns an instance of it, caller is responsible for clean-up, as indicated by function name.

  function CreateEncryptedFile: TEncryptedFile;
  begin
    Result := TEncryptedFile.Create;
  end;

Whether he decides to access your key directly or save it to a file is up to him. If he cleans up the instance and none of both is done already, the key is automatically saved.

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.339108 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO