Page 1 of 1

TOraXML bug

Posted: Fri 09 Jul 2010 10:35
by eugene_kr
Hi!

It looks like I have found a bug with TOraXml data storing.
Let take a look at the TOraXML.LoadFromStream procedure. It loads xml data from the source stream to the internal buffer. If the source stream has 10 bytel length, then internal buffer will be allocated for 10 bytes storing. Then try to get the xml data back with TOraXML.SaveToStream procedure. For some reason you will get 11+ bytes length xml data.

When exploring the issue I have found the folowing:
1) the TOraXML.SaveToStream procedure is expecting than xml data ends with zero-value byte (like any null-terminated string):
2) the TOraXML.LoadFromStream procedure does not append zero-value byte to the xml data stored as expected;
3) the TOraXML.SaveToStream procedure saves the xml-data to stream from the internal buffer begin till the zero-value byte (including zero byte) even if the internal data buffer does not contain zero byte at all, so it can append many garbage to the xml data end and can cause access violation memory error.

Below is my workaround. It is not perfect, but please check it and address the issue.

Code: Select all

procedure TOraXML.LoadFromStream(Stream: TStream);
var
  Remainder, Readed: integer;
  BufLen: integer;
  Buffer: TBytes;
  Ptr: IntPtr;
begin
  CheckType;
  FreeObject;

  Stream.Seek(0, soFromBeginning);
  Remainder := Stream.Size;
  Readed := 0;
  while Remainder > 0 do begin
    if Remainder > 10000 then
      BufLen := 10000
    else
      BufLen := Remainder;

    SetLength(Buffer, BufLen);

    Stream.ReadBuffer(Buffer{$IFNDEF CLR}[0]{$ENDIF}, BufLen);

    if Readed + BufLen  0 then begin
      Ptr := Marshal.AllocHGlobal(Readed + BufLen + 1);  // added +1 to allocate place for zero byte
      FillChar(Ptr, Readed + BufLen + 1, 0);                     // memory clear with zero for any case
      if Readed  0 then
        CopyBuffer(CacheValue, Ptr, Readed);
      Marshal.Copy(Buffer, 0, IntPtr(Integer(Ptr) + Readed), BufLen);
      if CacheValue  nil then
        Marshal.FreeHGlobal(CacheValue);
      CacheValue := Ptr;
    end;

    Dec(Remainder, BufLen);
    Inc(Readed, BufLen);
  end;
end;

procedure TOraXML.SaveToStream(Stream: TStream);
var
  Buffer: TBytes;
  Len: integer;
begin
//  if CacheIsNull then begin
//    if ObjectIsNull then
//      Exit;
//    ReadXML;
//  end;
  if CacheValue = nil then
    Len := 0
  else
    Len := StrLen(CacheValue);
  SetLength(Buffer, Len);            // removed +1 to exclude zero byte
  if CacheValue  nil then
    Marshal.Copy(CacheValue, Buffer, 0 , Len); // removed +1 to exclude zero byte
  Stream.WriteBuffer(Buffer{$IFNDEF CLR}[0]{$ENDIF}, Len); // removed +1 to exclude zero byte
end;

--
Eugene

Posted: Tue 13 Jul 2010 15:17
by bork
Hello

Thank you for the information. But really error is in the SaveToStream procedure. This procedure shouldn't add the #0 value to the end of the stream. As an example we can use the TXMLDocument class that implements an interface for access to the MSXML service. When TXMLDocument saves XML to the stream (really TXMLDocument calls the method of MSXML) we can see that XML is saved to the stream without the #0 value in the end.

TOraXML should save XML in the same format as MSXML therefore we will correct the SaveToStream procedure and it will not add the #0 value in the end of the stream.

The #0 value cannot be used as an end of the stream because if XML was stored in the UTF16 encoding then the #0 value will be a part of char in the UTF16 encoding (for example 'A' = #0041).

Posted: Mon 09 Aug 2010 12:59
by eugene_kr
Hello!

When this error will be fixed?

Posted: Mon 09 Aug 2010 13:37
by bork
Hello

This fix will be added in the next ODAC build that will be released this week.

fix

Posted: Thu 21 Oct 2010 10:14
by eugene_kr
Hello

I've checked your fix provided with 6.90.0.60 a few days and found that simple sample code with TOraXML.Load/SaveToStream calls does not work. The test case is below.

Code: Select all

procedure Test;
var
  lStream: TMemoryStream;
begin
  with TOraStoredProc.Create(Nil) do begin
    Session := TEST_DB_SESSION;
    lStream := TMemoryStream.Create();
    lStream.Write('Test', 4);
    lStream.Seek(0, soFromBeginning);
    try
      Params.CreateParam(TFieldType(ftXML), 'Result', ptInputOutput);
      ParamByName('Result').AsXML.OCISvcCtx := Session.OCISvcCtx;
      ParamByName('Result').AsXML.LoadFromStream(lStream);
      lStream.Seek(0, soFromBeginning);
      ParamByName('Result').AsXML.SaveToStream(lStream);
    finally
      lStream.Free();
      Free();
    end;
  end;
end;
The LoadFromStream (or SaveToStream) procedure call raises EInvalidPointer exception. Please check it.

Posted: Tue 26 Oct 2010 11:53
by eugene_kr
Hello!

Is there any progress on this issue?

Posted: Mon 01 Nov 2010 09:29
by AlexP
Hello,

I have tested your example with the 6.90.0.60 and 7.00.0.1 versions of ODAC, and it works without any errors.

Please check that you don't use any old ODAC *.dcu files.

Posted: Mon 01 Nov 2010 10:34
by eugene_kr
Hello,

Thank you for the check. You are right.