What the what?

What the what?

Why would a function retain the value of a previous call to the function?

I finally got some old D5 code that operates on Tstringlist members to work in Berlin/Unicode but...

It is a non-sorted list. In a for loop, I'm replacing the original string with the result of the function. In debugging when I step into my function the result displays the operated string correctly.

After the first element is passed into the function the resulting first stringlist element is an empty string. The next pass into the function shows (before any code execution) show a result already of the previous returned value.

After code execution on the second element when attempting to assign the new result I'm getting an AV (read of address)

If it makes any difference the function result is AnsiString.

Comments

  1. It sounds like that the function is returning a string. If that is the case, I believe (so I could be wrong) that functions returning strings (or any managed type) are changed (within the compiler) from:

    function myFunc() : string;

    to:

    procedure myFunc(; var Result : string);

    Information from: http://stackoverflow.com/questions/3250827/initialise-string-function-result

    Now, how does that help you, I am not sure but it might help you to understand what is happening behind the scene.

    ReplyDelete
  2. Is function parameter declared as AnsiString?

    ReplyDelete
  3. This is my function declaration:

    function NewDecryptString(StrToDecrypt, Code: AnsiString): AnsiString;

    ReplyDelete
  4. Patrick Hughes
    I suppose there are some unicode-related problems in a function.
    For example using char or pchar instead of ansichar or pansichar.
    For example, pchar variable which is set to ansistring and incremented length(s) times, so memory is corrupted.

    ReplyDelete
  5. That's the thing though, I just got the function "working" replacing variables into ansichar types.

    ReplyDelete
  6. Original D5 code used strings, and array of chars, I've changed them to ansistrings and array of ansichars

    ReplyDelete
  7. Nicholas Ring​ is correct, and the reason why you see the same value is because the same temporary variable is passed to the function call.

    If it's any consolation this bit me as well at some point.

    Now, this in itself should not lesd to an AV, so that must be something else.

    ReplyDelete
  8. I've just now modified to a procedure like Nicholas Ring suggested and it is not producing the AV on multiple calls. My code is generating the correct result within the procedure but the returned result is an empty string. WTF

    ReplyDelete
  9. Patrick Hughes I didn't mean for you to change it - I was just saying what the compiler does behind the scenes, which would explain why the result of the function had the previous result value.

    Sorry for the confusion.

    ReplyDelete
  10. I'm just struggling with the transition from D5 to Berlin and not understanding why the result is not "cleared" after the first call, then causing the AV on subsequent calls. Makes no sense to me.

    ReplyDelete
  11. The SO post you linked talks about (in that case) that the result needs to be initialize. If I do that then when I try to assign the calculated result I get an AV.

    ReplyDelete
  12. Let me see if I can get it back to a state that almost worked. I've got some rather profane variable names in it right now. And it broke differently now - it's pretty twisted around.

    ReplyDelete
  13. My calling procedure:

    procedure LoadAppAnalytics(ThisApp: string);
    var
      I, J: integer;
      Document: string;
      ThisStr: string;
      AppAnalyticsStrings: TStringList;
      Stream: TFileStream;
    begin
      Document := SessionLogLocation + '\Analytics\AppAnalytics.dat';

      AppAnalyticsStrings := TStringList.Create;

      try

        Stream := TFileStream.Create(Document, fmOpenRead or  fmShareDenyNone);


        try
          AppAnalyticsStrings.LoadFromStream(Stream);
          AppAnalyticsStrings.Sorted := false;
          for I := 0 to AppAnalyticsStrings.Count - 1 do
            begin
    //// this after modifying to procedure 
              NewDecryptString(AppAnalyticsStrings[I], PrivacyCode, ThisStr);
              AppAnalyticsStrings[I] := string(ThisStr);
              WriteToErrorLog(ErrorLogFile, '->>>>>>>>>>>>>  ' +           AppAnalyticsStrings[I]);
            end;
        finally
          Stream.Free;
        end;
      finally
        AppAnalyticsStrings.Free;
      end;
    end;

    ReplyDelete
  14. My called procedure:

    procedure NewDecryptString(StrToDecrypt, Code: AnsiString; var result: string);
    var
      X, I: Integer;
      MyPw: ansistring;
      MyPwHash: ansiString;
      HashArray: array of ansichar;
      StringArray: array of ansichar;
      ThisStr: ansistring;
    begin

      MyPwHash := Copy(StrToDecrypt, Length(StrToDecrypt) - 9, 10);
      StrToDecrypt := Copy(StrToDecrypt, 0, Length(StrToDecrypt) - 10);
      SetLength(HashArray, Length(MyPwHash));
      for I := Low(HashArray) to High(HashArray) do
        HashArray[I] := MyPwHash[I + 1];
      X := 0; // initialize count
      {$R-}
      for I := Low(HashArray) to High(HashArray) do
        begin
          HashArray[I]:= AnsiChar(Ord(HashArray[I]) - Ord(Code[X]));
          Inc(X);
          if X = Length(Code) - 1 then
            X := 0;
        end;
      {$R+}
      MyPw := ansiString(HashArray);    //'0507254092'  '0507254092'  <<-- 10 characters, unique each for string
      SetLength(StringArray, Length(StrToDecrypt));
      for I := Low(StringArray) to High(StringArray) do
        StringArray[I] := StrToDecrypt[I + 1];
      X := 0; // initialize count
      {$R-}
      for I := Low(StringArray) to High(StringArray) do
        begin
          StringArray[I]:= AnsiChar(Ord(StringArray[I]) - Ord(MyPW[X]));
          Inc(X);
          if X = Length(MyPW) - 1 then
            X := 0;
        end;
      {$R+}
      ThisStr := ansistring(StringArray);
      Result := ThisStr;
      //Result := ansistring(StringArray);
    end;

    when called ThisStr has a value but no matter what I try (even assigning it to a global variable the end result is an empty string.

    ReplyDelete
  15. So if you want to give it a go one encrypted string is:

    '8inbmifw„4'#$008F'ecv‚–¨¢i“ P'#$7F'§Å“Å“•sbeak_feme¨¥Å“¦¤Å¡¦^e¨Å¡Xnbff\1ameg[©•£k'#$009D'Å“¢e—'#$00AD'™X4df\hekkYC‘™„œŸ¥£^e¨Å¡Xjbl`d6`^s˜–‰™'#$009D'pŸ‹™Å“©c™¨eXhihjajf1`^™Å“ª¥ Å¸r•c•¯—]ec6eabpkh]£i”Å¡’˜¤c™¨eXgan^kgb)‘˜‘›`Å¡¬•(gihgbaec6gga`–¬¡^e¨Å¡Xkgaec0Y¨•¥–™¡ .•'#$00AD'•_kf`a0b^|„‡c™¨eXffj^hf`)q˜r©¡¬§•rx¤£«`Å¡¬•(ajco^hjf5Yy•£›«™¢y„§‘°`Å¡¬•(ef\jej]qc¢¤‚›egb•x•]agjf`a2`iY€ «™Å¾tŸ§† —¬b•x•]bihj`b3fiY{†x„•x•c•¯—]ee6\gho[¢¡“.•'#$00AD'•_dif`,iidn[~¢¦ež©Å¸©`Å¡¬•(fedp^mja0Yy§Å¾y§'#$009D'” XhYe—'#$00AD'™X3he\kkg]tw—|¢ –U\b)^Å¡¨Å“Zgkf,ceg`v¬›wr™™^œªš\a5baaog^ª a¤˜˜e—'#$00AD'™X6\gg`v—¦va“©Å¸©«Ë†™¤u c•¯—]f`,dkY{t‡‰ d‘©•e—'#$00AD'™X0\nYž¤Å¡¤‡ižc•¯—]fh7\hfh[0mipdk4hrk'

    and the PrivacyCode is '8992671'

    ReplyDelete
  16. And with that I'm done for the evening.

    ReplyDelete
  17. {$R-} simply asks for trouble. What possessed you. The issue is exactly as described in the very first comment. You have other huge problems caused by treating binary and text as interchangeable. When you encrypt you have binary data. Don't pretend that it is text.

    ReplyDelete
  18. Patrick Hughes which encoding is that string? If you don't know, then you're in trouble. Deep trouble.

    ReplyDelete
  19. At least a part

    X := 0; // initialize count
      {$R-}
      for I := Low(HashArray) to High(HashArray) do
        begin
          HashArray[I]:= AnsiChar(Ord(HashArray[I]) - Ord(Code[X]));

    looks strange,
    On first pass x=0, so it will be Code[0]

    ReplyDelete
  20. Presumably there was a runtime bounds error which Patrick suppressed with {$R-} .......

    ReplyDelete
  21. If your function returns a managed type (string, interface, record, etc.), your variable Result does not get initialized. Calling that method multiple times results in what you see: at the beginning Results points to the result of the last execution.
    That is the same reason why
    function foo: string;
    begin
    end;
    dont lead to "W1035 Return value of function 'foo' might be undefined", because the return value is defined. Changing the result type from string to Integer and the compiler shows you W1035.

    When you are mixing binary operations like crypting with strings you get a lot of problems with encoding. In D5 this worked because the types are binary compatible, but since D2010 (i think) this wont work any longer. Maybe you have luck and you could try "RawByteString", but that is a dirty adhesive plaster and not a solution.

    ReplyDelete
  22. The range check suppression was added because at the time I began using the code I wasn't too well versed in debugging - I threw it in and it worked. Yeah, dumb, I know.

    The encryption is done by another function that is basically an inversion of the decryption function. Re the encoding: this is internet coding at its finest.

    Lots for this feeble mind to digest. I'll keep at it and be back.

    Thank you all.

    ReplyDelete
  23. Not sure how much this applies to this particular block of code, but change "Return value of function might be undefined" from True to Error in your project's Hints and Warning section. That way you are at least ensuring the Result var is being assigned something on all code paths.

    ReplyDelete
  24. Ryan McGinty​ Not for managed types. They are deemed always to be initialised. And they are var parameters by implementation.

    ReplyDelete
  25. David Heffernan Ah yes, good point.

    ReplyDelete
  26. Ryan McGinty as I said: managed types dont result in W1035, even if you treat this warning as error.

    ReplyDelete
  27. Fabian S. Biehn and it's been on the bug list for more than a decade now.
    Luckily FixInsight catches these.

    ReplyDelete
  28. I will not be defeated

    It might take me years to get my code working (and now this little bit does) but I will prevail.

    ReplyDelete
  29. Did you switch to working with binary yet. Or are you still making the mistake of treating the data as text.

    ReplyDelete
  30. For the time being still following the dark path.

    ReplyDelete
  31. Every day you continue down that path is a day wasted. Your data are byte arrays.

    ReplyDelete
  32. Well, I need to educate myself and get things fixed up. I'm transitioning from my current product written in D5 to a complete rewrite to Berlin so it's as good a time as any to follow your advice.

    But for all practical purpose isn't a byte array simply a representation of a "value" whether it be a string or a integer, float, etc..?

    I mean when you boil it down every thing is binary.

    (I'm probably really exposing my ignorance here, but that's ok, I have no shame)

    ReplyDelete
  33. Yes, everything is a byte array. But text is subject to encodings and liable to get mangled and transformed. Do some research. There are loads of articles on the subject.

    ReplyDelete
  34. Patrick Hughes Nope. A string has an encoding. A byte array hasn't.

    ReplyDelete
  35. Yeah, I'm seeing your contributions on SO. So if I'm understanding things correctly basically all you are saying is rather than passing the data into the function as a string I need to determine my encoding, pass it in as a byte array, operate on the byte array, and return the string.

    ReplyDelete
  36. I must have been typing my reply to David Heffernan while you replied Jeroen Wiert Pluimers 

    But I'm also thinking that what the two of you are also saying is that it gets a little deeper that just the encryption/decryption routine. It also has to do with not loading the data into a stringlist. Is that correct?

    ReplyDelete
  37. Patrick Hughes I'd say first solve it in non-unicode Delphi ensuring you are encrypting/decrypting opaque bytes, then try again porting it.
    And even there, you should be aware that strings have an encoding (hence all the code-page trouble back then).

    ReplyDelete
  38. From a string encode to byte array usually UTF8. Then encrypt. Then convert to string with base64. Reverse process in opposite direction.

    ReplyDelete
  39. Patrick Hughes and right so. You are in big trouble.

    ReplyDelete
  40. Patrick Hughes Yes it's all bytes but encoding matters. You can write English using the Norwegian alphabet, the characters are the same, but the words are different.

    Same with computers. It's all zeroes and ones in the end, but the "language" (encoding) used can make the zeroes and ones mean something else.

    ReplyDelete
  41. Jeroen Wiert Pluimers that's encouraging.

    ReplyDelete
  42. Patrick Hughes I know. But it sets a baseline where you can start working from.

    ReplyDelete
  43. I meant the "you're in big trouble" comment :-)

    ReplyDelete

Post a Comment