AV in handling TDynByteArray

214 views
Skip to first unread message

Dave Scofield

unread,
Nov 5, 2001, 1:22:15 PM11/5/01
to
I'm obviously not doing things the way Borland anticipated, since I can't
get my SOAP app to work, and I've uncovered an AV along the way.

This code is from OPToSoapDomCov.pas, and is in the
.ConvertNativeArrayToSoap procedure:

if not Assigned(P) or IsArrayRect(P, Dims) then
begin
SetLength(DimAr, Dims);
if Assigned(P) then
GetDims(P, DimAr, Dims);
GetDynArrayElTypeInfo(Info, ElemInfo, Dims);
if (Dims = 1) and (ElemInfo.Kind = tkInteger) and
(GetTypeData(ElemInfo).OrdType = otUByte) then
begin
SetLength(S, GetDynArrayLength(P));
Move(P^, S[1], Length(S));

You can see in the above code where it fully anticipates P being nil, and
yet the last quoted line shows dereferencing P, with P never getting set to
anything non-nil. Boom.

Anyway - what I'm trying to do is pass an object from the client to the
server, which has a TDynArray field:

type
TPublishInfo = class(TRemotable)
private
FUserID: Integer;
FFileContents: TByteDynArray;
public
constructor Create; override;
procedure LoadFile(S: TStream);
published
property UserID: Integer read FUserID write FUserID;
property FileContents: TByteDynArray read FFileContents;
end;

I'm registering things like this:

initialization
RemClassRegistry.RegisterXSClass(TPublishInfo);
RemClassRegistry.RegisterXSInfo(TypeInfo(TByteDynArray));

Although I'm pretty sure that the RegisterXSInfo isn't needed, since Types
or XSBuiltIns does that.

Anyway - this object arrives fine at the server as long as the length of the
byte array is 0. As soon as the length > 0, the exe AV's - in the above
quoted code - so it never even calls the server.

So my question is obviously: how am I supposed to pass a dynamic byte array?
The help indicates that this should work as is. Also, I'm writing both the
client and the server, so it's all just Delphi code.

Thanks for any help,
Dave

Jean-Marie Babet

unread,
Nov 6, 2001, 8:55:05 PM11/6/01
to
Hello Dave,

Thanks for the post.

I'll double check the routine to make sure we're not dereferencing nil.
However, I also wanted to ask whether you have the update#1 installed.
ByteArray was one of the items fixed in the update.

Also, is your snippet complete or are you missing the writer in the
following property:

> property FileContents: TByteDynArray read FFileContents;

I would suggest

property FileContents: TByteDynArray read FFileContents write
FFileContents;


Regards,


Bruneau.


"Dave Scofield" <spammen...@epiphanysoftware.com> wrote in message
news:3be6d8f8$1_1@dnews...

Dave Scofield

unread,
Nov 7, 2001, 12:10:07 PM11/7/01
to
Hi Bruneau,

To answer your questions:

- Yes, I have the update installed.
- Yes, the snippet was complete. The property was read-only because the
class has a method on it which loads a file into the array.

Thanks,
Dave


"Jean-Marie Babet" <bba...@borland.com> wrote in message
news:3be89485$1_2@dnews...

Jean-Marie Babet

unread,
Nov 7, 2001, 1:35:28 PM11/7/01
to
Thanks! I'll put a sample together to investigate.

More later...


Bruneau.

"Dave Scofield" <spammen...@epiphanysoftware.com> wrote in message

news:3be96b02$2_1@dnews...

Jean-Marie Babet

unread,
Nov 8, 2001, 2:35:27 PM11/8/01
to
Dave,

Quick note to confirm that I'm able to reproduce the AV. I've setup a server
with a method that returns a TPublishInfo at

http://soap-server.borland.com/WebServices/Interop/cgi-bin/ByteArrayServer.e
xe/wsdl/TestService
http://soap-server.borland.com/WebServices/Interop/cgi-bin/TypeWithByteArray
.pas

I'll relay my findings as soon as possible (this afternoon or tomorrow
morning, barring interruptions).

Regards,


Bruneau.

Jean-Marie Babet

unread,
Nov 8, 2001, 4:45:12 PM11/8/01
to
Dave,

Good find!!

Here's the scoop: When we [de]serialize a ByteArray we usually have the
address of the array. However, if the array is a published member of an
object, we call some base routine in TypeInfo.pas to retrieve the address of
the member. In this particular case, we call 'GetOrdProp' - see
CreateObjectNode in OpToSOAPDomConv.pas:

end else if Kind = tkDynArray then
begin
P := Pointer(GetOrdProp(Instance, PropList[I])); <<<<<<<<
ConvertNativeArrayToSoap(RootNode, InstNode, ExtPropName,
(PropList[I].PropType)^, P, 0);


'GetOrdProp' from TypInfo.pas has a bug: It assumes that if the member type
is not a class [tkClass], then the member type size must be the first byte
in the TypeInfo data:

CMP [EDI].TTypeInfo.Kind,tkClass <<<<<<<<
JE @@isClass
XOR ECX,ECX
MOV CL,[EDI].TTypeInfo.Name.Byte[0]
MOV BL,[EDI].TTypeInfo.Name[ECX+1].TTypeData.OrdType <<<<<<<<


That's the value that's being moved in 'BL' above. If the member is a
dynamic array, the first byte of the TypeData will be the type of the Array
element. So if we had an array of integer things would work fine because the
size of an integer is the same as the size of the pointer to the array.
However, in your case you have a Byte array. The current logic then believes
that the member is a 'byte', so it clears out the high bytes:

@@final:
CMP BL,otSLong
JAE @@exit
CMP BL,otSWord
JAE @@word
CMP BL,otSByte
MOVSX EAX,AL
JE @@exit
AND EAX,$FF
JMP @@exit

The net result is we end up with something like '00000074' for the address
of the member instead of '00C51A74'.

Fixing the above is easy, I've added a test for 'tkDynArray' [and
'tkInterface' too although SOAP does not remote published interfaces]. I've
made a routine called GetOrdPropEx (see below) that I call instead of
calling GetOrdProp.

A similar fix is necessary on the deserialization end. I've made a routine
called SetOrdPropEx (see below) that I call from LoadObject
(OpToSOAPDomConv.pas):

end else if Kind = tkDynArray then
begin
IsNull := NodeIsNull(Node.ChildNodes[K]);
if not IsNull then
begin
GetElementType( Node.ChildNodes[K], URI, TypeName);
ArrayPtr := nil;
ArrayPtr := ConvertSoapToNativeArray(@ArrayPtr,
(PropList[I].PropType)^, RootNode, Node.ChildNodes[K]);
SetOrdPropEx(Instance, PropList[I], Integer(ArrayPtr));
<<<<<<<<<<<
end;


I've updated the server mentioned earlier (
http://hirondelle/WebServices/Interop/cgi-bin/ByteArrayServer.exe/wsdl/TestS
ervice ) and tested it from both a Delphi and a Java (using GLUE) client.

Let me know if you need more information to workaround this for now. I'll
pass my GetOrdPropEx and SetOrdPropEx on to the 'TypeInfo' person for review
and inclusion in the next patch. SOAP is likely to release a patch first. So
I'm keeping GetOrdPropEx and SetOrdPropEx until TypeInfo.pas is updated.

Regards,


Bruneau.

================================== [
GetOrdPropEx ]=============================================


function GetOrdPropEx(Instance: TObject; PropInfo: PPropInfo): Longint;
asm
{ -> EAX Pointer to instance }
{ EDX Pointer to property info }
{ <- EAX Longint result }

PUSH EBX
PUSH EDI
MOV EDI,[EDX].TPropInfo.PropType
MOV EDI,[EDI]
MOV BL,otSLong
CMP [EDI].TTypeInfo.Kind,tkClass
JE @@isClass
CMP [EDI].TTypeInfo.Kind,tkDynArray
JE @@isDynArray
CMP [EDI].TTypeInfo.Kind,tkInterface
JE @@isInterface
XOR ECX,ECX
MOV CL,[EDI].TTypeInfo.Name.Byte[0]
MOV BL,[EDI].TTypeInfo.Name[ECX+1].TTypeData.OrdType
@@isDynArray:
@@isInterface:
@@isClass:
MOV ECX,[EDX].TPropInfo.GetProc
CMP [EDX].TPropInfo.GetProc.Byte[3],$FE
MOV EDX,[EDX].TPropInfo.Index
JB @@isStaticMethod
JA @@isField

{ the GetProc is a virtual method }
MOVSX ECX,CX { sign extend slot offs }
ADD ECX,[EAX] { vmt + slotoffs }
CALL dword ptr [ECX] { call vmt[slot] }
JMP @@final

@@isStaticMethod:
CALL ECX
JMP @@final

@@isField:
AND ECX,$00FFFFFF
ADD ECX,EAX
MOV AL,[ECX]
CMP BL,otSWord
JB @@final
MOV AX,[ECX]
CMP BL,otSLong
JB @@final
MOV EAX,[ECX]
@@final:
CMP BL,otSLong
JAE @@exit
CMP BL,otSWord
JAE @@word
CMP BL,otSByte
MOVSX EAX,AL
JE @@exit
AND EAX,$FF
JMP @@exit
@@word:
MOVSX EAX,AX
JE @@exit
AND EAX,$FFFF
@@exit:
POP EDI
POP EBX
end;


================================== [
SetOrdPropEx ]=============================================

procedure SetOrdPropEx(Instance: TObject; PropInfo: PPropInfo;
Value: Longint); assembler;
asm
{ -> EAX Pointer to instance }
{ EDX Pointer to property info }
{ ECX Value }

PUSH EBX
PUSH ESI
PUSH EDI
MOV EDI,EDX

MOV ESI,[EDI].TPropInfo.PropType
MOV ESI,[ESI]
MOV BL,otSLong
CMP [ESI].TTypeInfo.Kind,tkClass
JE @@isClass
CMP [ESI].TTypeInfo.Kind,tkDynArray
JE @@isDynArray
CMP [ESI].TTypeInfo.Kind,tkInterface
JE @@isInterface
XOR EBX,EBX
MOV BL,[ESI].TTypeInfo.Name.Byte[0]
MOV BL,[ESI].TTypeInfo.Name[EBX+1].TTypeData.OrdType
@@isDynArray:
@@isInterface:
@@isClass:
MOV EDX,[EDI].TPropInfo.Index { pass Index in DX }
CMP EDX,$80000000
JNE @@hasIndex
MOV EDX,ECX { pass value in EDX }
@@hasIndex:
MOV ESI,[EDI].TPropInfo.SetProc
CMP [EDI].TPropInfo.SetProc.Byte[3],$FE
JA @@isField
JB @@isStaticMethod

{ SetProc turned out to be a virtual method. call it }
MOVSX ESI,SI { sign extend slot offset }
ADD ESI,[EAX] { vmt + slot offset }
CALL dword ptr [ESI]
JMP @@exit

@@isStaticMethod:
CALL ESI
JMP @@exit

@@isField:
AND ESI,$00FFFFFF
ADD EAX,ESI
MOV [EAX],CL
CMP BL,otSWord
JB @@exit
MOV [EAX],CX
CMP BL,otSLong
JB @@exit
MOV [EAX],ECX
@@exit:
POP EDI
POP ESI
POP EBX
end;


Dave Nottage

unread,
Nov 8, 2001, 6:40:32 PM11/8/01
to
"Jean-Marie Babet" wrote:
> 'GetOrdProp' from TypInfo.pas has a bug: It assumes that if the member
type
> is not a class [tkClass], then the member type size must be the first
byte
> in the TypeInfo data...

> Let me know if you need more information to workaround this for now.
I'll
> pass my GetOrdPropEx and SetOrdPropEx on to the 'TypeInfo' person for
review
> and inclusion in the next patch. SOAP is likely to release a patch
first. So
> I'm keeping GetOrdPropEx and SetOrdPropEx until TypeInfo.pas is
updated.

Good catch! I just so happen to be working on a webservice for a client
that uses TByteDynArray, and this fix may mean hours of headaches have
been saved.

--
Dave Nottage


Dave Scofield

unread,
Nov 12, 2001, 10:35:16 AM11/12/01
to
Bruneau,

Glad you found/fixed it -- as Dave said, that'll save some people lots of
headaches, which I already went though :-)

Anyway, since I was in a time-crunch, I went ahead and implemented what I
needed myself - which was a mechanism to handle uploading files to the
server. (Our product supports a "community library" where users of our
software can publish documents to our server, which can then be browsed and
downloaded by other users.) So I do the conversion to base64 text (after
compressing the file), and use soap to upload chunks of the file, passed in
a string.

Thanks again,
Dave


Reply all
Reply to author
Forward
0 new messages