Search code examples
pascalfreepascal

Why "BlockRead" can use a buffer whose size is smaller than the size of the data being read


I have a program:

program OverflowTest;
var
    src: file;
    dest: file;
    res: longint;
    buf: byte;
begin
    assign(src, '48_bytes_file.txt');
    assign(dest, 'copy_of_48_bytes_file.txt');
    reset(src, 2);
    rewrite(dest, 2);
    BlockRead(src, buf, 24, res);   
    BlockWrite(dest, buf, 24, res);
    close(src);
    close(dest);
end.

Here I am reading 48 bytes of data into the variable buf, which can only contain one byte (because it is of type byte). And it works. The data is successfully copied to the second file without data loss.

Why can I write 48 bytes of data to a 1 byte variable? Why there are no errors like "memory overflow", "invalid buffer size"?


Solution

  • There are no errors because blockRead and blockWrite are by design unsafe. The signature of blockRead reads

    procedure BlockRead(var f: file; var Buf; count: Word; var Result: Word)
    

    Note the absence of a data type for Buf. This Borland Pascal extension means that any data type is permissible, only var restricts it to variables. For untyped parameters only the memory address of the (beginning of the) variable is passed, the data type byte is lost.

    BlockRead and blockWrite can therefore be deemed UCSD Pascal’s equivalent to C’s read and write suffering from the same kind of anomaly. You as the programmer/user of routines accepting so‑called “untyped parameters” need to pay attention for mistakes like this one you experienced.


    You do not observe any strange effects or crashes, because in all your tests the memory space following buf is apparently not used by something else and belongs to your program. Let’s apply following changes to your test program:

    • Insert another variable right after buf:
      program overflowTest(input, output, stdErr);
      var
          src, dest: file;
          res: longInt;
          buf: byte;
          fer: byte;
      
    • Initialize the additional variable with a value not appearing as the second byte of 48_bytes_file.txt.
      begin
          fer := 0;
          { and the remaining program unchanged }
      
    • If you are not using a debugger, insert at the end a statement rendering fer visible.
          { … }
          writeLn(fer)
      end.
      

    When compiling and running the modified program, you may notice that writeLn(fer) does not write 0, the value we initialized fer with. This means

    1. the FPC places var variables next to each in memory, and
    2. blockRead bluntly writes 48 bytes to memory starting at the location of buf and beyond without regard to the size of buf.

    As a consequence you cannot use fer productively. If your program relies on fer being 0, the intervening blockRead overwrote the 0.

    : I have tested the behavior only for an Linux target.