6

I'm working on some legacy software written in Delphi 7 which runs on Windows. I've minified the problem to the following program:

var f: text;
begin
  assign(f, 'a.txt');
  rewrite(f);
  writeln(f, 'before' + chr(14) + 'after');
  close(f);

  assign(f, 'a.txt');
  append(f);
  close(f);
end.

I expect it to create a.txt file containing "before#14after#13#10" and then append nothing to it. However, after I run this program on Windows, I see before in a.txt instead, like if Delphi's append truncates the file. If I do not re-open the file, it shows before#14after#13#10 as expected.

If I write something (FooBar) in the re-opened file, it's appended, but as if the file was already truncated: beforeFooBar.

This effect does not occur with any other character between 0 and 32, even with 26 (which stands for EOF).

Is this a bug in Delphi or a well-defined behavior? What is so special about chr(14)?

yeputons
  • 8,478
  • 34
  • 67
  • It works as expected in both Delphi 4 (Windows 95) and Delphi 10.3 (Windows 7). I don't have access to any Delphi 7 installation at the moment. (ASCII 14 is SHIFT OUT, by the way.) – Andreas Rejbrand May 02 '20 at 18:37
  • Very interesting, thank you for checking other versions! I'll keep digging locally. – yeputons May 02 '20 at 18:42
  • I can confirm: using Delphi 7 and debugging the file is written as intended (sizes 14 bytes), but when `append()` is called, the file's length is truncated to 6 bytes. I can only assume that ASCII 14 is not really considered "text", while the function tries to find an end of a line. Consider this as undefined behaviour. – AmigoJack May 02 '20 at 18:58
  • Strangely, indeed an EOF itself is preserved (though it's decimal 26 and not 27). – Sertac Akyuz May 02 '20 at 19:04
  • Found in another chat: looks like Delphi 7 treats 14 as 26 for some reason: if it's found in the last 128 bytes of the file, the file is truncated, see [here](http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/System_Append.html). – yeputons May 02 '20 at 19:04
  • 1
    Ok, wild guess: someone wrote `eof` in the source code of Delphi 7 assuming it's a correct constant, but it [turns out to be 14](https://stackoverflow.com/questions/8702908/delphi-assembler-constant-eof) O_O – yeputons May 02 '20 at 19:15
  • Looks like it (line 4273 of system.pas), it should be `cEOF`. – Sertac Akyuz May 02 '20 at 19:21
  • @SertacAkyuz (or yeputons himself): can you please write an answer? Maybe even a binary patch could be made to address this bug. – AmigoJack May 02 '20 at 21:00
  • 1
    @yep 's find. If source is not available, this line `CMP byte ptr [ESI].TTextRec.Buffer[EAX],eof` should be `CMP byte ptr [ESI].TTextRec.Buffer[EAX],cEOF` . – Sertac Akyuz May 02 '20 at 21:11

1 Answers1

5

Thanks to some friends from a chat and Sertac Akyuz from comments: it looks like a bug in Delphi 7.

It's supposed to have special handling of the EOF symbol (ASCII 26), quoting from here:

Note: If a Ctrl+Z (ASCII 26) is present in the last 128-byte block of the file, the current file position is set so that the next character added to the file overwrites the first Ctrl+Z in the block. In this way, text can be appended to a file that terminates with a Ctrl+Z.

Kind of CP/M backward compatibility, I guess.

However, there is a bug in the implementation of TextOpen for Windows (see Source/Rtl/Sys/System.pas from your Delphi 7 installation around line 4282):

@@loop:
        CMP     EAX,EDX
        JAE     @@success

//    if  (f.Buffer[i] == eof)

        CMP     byte ptr [ESI].TTextRec.Buffer[EAX],eof
        JE      @@truncate
        INC     EAX
        JMP     @@loop

Here it says eof instead of cEof. Unfortunatley, that compiles for some reason and it even appeared on StackOverflow already. There is a label called @@eof and that's pretty much it.

Consequence: instead of having special case for 26, we have special case for 14. The exact reason is yet to be found.

yeputons
  • 8,478
  • 34
  • 67