mORMot and Open Source friends
Check-in [6e28c20745]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:fixed unexpected GPF error when clearing existing record content during JSON unserialization
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 6e28c20745e734e6a840d2f9cacb9a28dfcad3d5
User & Date: User 2013-12-20 10:52:43
Context
2013-12-20
11:04
fixed another potential GPF issue check-in: 98387083f9 user: User tags: trunk
10:52
fixed unexpected GPF error when clearing existing record content during JSON unserialization check-in: 6e28c20745 user: User tags: trunk
2013-12-18
18:44
now text-defined JSON record serialization is able to handle any pre-defined record types - added regression tests and updated documentation check-in: b0371f5e5a user: User tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SynCommons.pas.

21834
21835
21836
21837
21838
21839
21840





21841
21842
21843
21844
21845
21846
21847
21848
21849
21850
21851
21852
21853
21854
21855
21856
21857
21858
21859
21860
.....
21880
21881
21882
21883
21884
21885
21886
21887
21888
21889
21890
21891
21892
21893
21894
21895
21896
.....
21935
21936
21937
21938
21939
21940
21941


21942
21943
21944
21945
21946
21947
21948
.....
21983
21984
21985
21986
21987
21988
21989
21990
21991
21992
21993
21994
21995
21996
21997
.....
22013
22014
22015
22016
22017
22018
22019
22020
22021
22022
22023
22024
22025
22026
22027
.....
22136
22137
22138
22139
22140
22141
22142
22143
22144



22145
22146
22147
22148
22149
22150
22151
22152
22153
22154
22155
22156
22157
22158
22159
    raise ESynException.CreateFmt('"%s" type should not have been un-registered',
      [fRecordTypeName]);
end;


{ TJSONCustomParserRTTI }






const
  // binary size (in bytes) of each kind of property - 0 for ptRecord/ptCustom
  JSONRTTI_SIZE: array[TJSONCustomParserRTTIType] of byte = (
    SizeOf(PtrUInt),SizeOf(Boolean),SizeOf(Byte),SizeOf(Cardinal),SizeOf(Currency),
    SizeOf(Double),SizeOf(Int64),SizeOf(Integer),SizeOf(RawUTF8),0,SizeOf(Single),
    SizeOf(String),SizeOf(SynUnicode),SizeOf(TDateTime),SizeOf(TGUID),
    SizeOf(TTimeLog),SizeOf(WideString),SizeOf(Word),0);

/// if defined, will try to mimic the default record alignment
// -> is buggy, and compiler revision specific -> we would rather use packed records
{.$define ALIGNCUSTOMREC}

procedure TJSONCustomParserRTTI.ComputeDataSizeAfterAdd;
var i: integer;
begin
  if fDataSize=0 then begin
    assert(fFullDataSize=0);
    for i := 0 to high(NestedProperty) do begin
      NestedProperty[i].ComputeDataSizeAfterAdd; 
      inc(fFullDataSize,NestedProperty[i].fDataSize);
................................................................................
    case NestedProperty[j].PropertyType of
    ptRawUTF8: PRawUTF8(Data)^ := '';
    ptString:  PString(Data)^ := '';
    ptSynUnicode: PSynUnicode(Data)^ := '';
    ptWideString: PWideString(Data)^ := '';
    ptArray:  NestedProperty[j].FinalizeNestedArray(PPtrUInt(Data)^);
    ptRecord: NestedProperty[j].FinalizeNestedRecord(Data);
    ptCustom: RecordClear(Data,TJSONCustomParserCustom(self).fRecordTypeInfo);
    end;
    inc(Data,JSONRTTI_SIZE[NestedProperty[j].PropertyType]);
  end;
end;

procedure TJSONCustomParserRTTI.FinalizeNestedArray(var Data: PtrUInt);
var i: integer;
    Rec: ^TDynArrayRec;
    ItemData: PByte;
................................................................................
    case Prop.PropertyType of
    ptRecord: begin
      if not Prop.ReadOneLevel(P,Data,Options) then
        exit;
      EndOfObject := P^;
      if P^ in [',','}'] then
        inc(P);


    end;
    ptArray: begin
      if P^<>'[' then
        exit; // we expect a true array here
      repeat inc(P) until P^<>' ';
      // allocate nested array at once
      ArrayLen := JSONArrayCount(P);
................................................................................
      end;
    end;
    ptCustom: begin
      P := TJSONCustomParserCustom(Prop).GetJSONCustomParserRegistration.
        Reader(P,Data^,valid);
      if (P=nil) or not valid then
        exit;
      inc(Data,Prop.fDataSize);
      EndOfObject := P^;
      if P^ in [',','}'] then
        inc(P);
    end;
    else begin
      PropValue := GetJSONField(P,P,@wasString,@EndOfObject);
      if (PropValue=nil) or (wasString<>(Prop.PropertyType in
................................................................................
      ptTimeLog:   PInt64(Data)^ := Iso8601ToSecondsPUTF8Char(PropValue,0);
      ptWideString:UTF8ToWideString(PropValue,StrLen(PropValue),PWideString(Data)^);
      ptWord:      PWord(Data)^ := GetCardinal(PropValue);
      ptGUID:      TextToGUID(PropValue,pointer(Data));
      end;
    end;
    end;
    inc(Data,JSONRTTI_SIZE[Prop.PropertyType]);
    result := true;
  end;
var i,j: integer;
    PropName: RawUTF8;
    wasString: boolean;
    Values: array of PUTF8Char;
begin
................................................................................
            inc(DynArray,8-(PtrUInt(DynArray)and 7);
          {$endif}
        end;
      aWriter.CancelLastComma;
      aWriter.Add(']');
      dec(aWriter.fHumanReadableLevel);
    end;
    ptRecord:
      Prop.WriteOneLevel(aWriter,Value,Options);



    ptCustom: begin
      TJSONCustomParserCustom(Prop).GetJSONCustomParserRegistration.
        Writer(aWriter,Value^);
      Inc(Value,Prop.fDataSize);
    end;
    end;
    aWriter.Add(',');
    inc(Value,JSONRTTI_SIZE[Prop.PropertyType]);
  end;
var i: integer;
    SubProp: TJSONCustomParserRTTI;
begin
  if P=nil then begin
    aWriter.AddShort('null');
    exit;






>
>
>
>
>







<
<
<
<
<
<







 







|

|







 







>
>







 







<







 







|







 







|

>
>
>



<



|







21834
21835
21836
21837
21838
21839
21840
21841
21842
21843
21844
21845
21846
21847
21848
21849
21850
21851
21852






21853
21854
21855
21856
21857
21858
21859
.....
21879
21880
21881
21882
21883
21884
21885
21886
21887
21888
21889
21890
21891
21892
21893
21894
21895
.....
21934
21935
21936
21937
21938
21939
21940
21941
21942
21943
21944
21945
21946
21947
21948
21949
.....
21984
21985
21986
21987
21988
21989
21990

21991
21992
21993
21994
21995
21996
21997
.....
22013
22014
22015
22016
22017
22018
22019
22020
22021
22022
22023
22024
22025
22026
22027
.....
22136
22137
22138
22139
22140
22141
22142
22143
22144
22145
22146
22147
22148
22149
22150

22151
22152
22153
22154
22155
22156
22157
22158
22159
22160
22161
    raise ESynException.CreateFmt('"%s" type should not have been un-registered',
      [fRecordTypeName]);
end;


{ TJSONCustomParserRTTI }

/// if defined, will try to mimic the default record alignment
// -> is buggy, and compiler revision specific -> we would rather use packed records
{.$define ALIGNCUSTOMREC}

procedure TJSONCustomParserRTTI.ComputeDataSizeAfterAdd;
const
  // binary size (in bytes) of each kind of property - 0 for ptRecord/ptCustom
  JSONRTTI_SIZE: array[TJSONCustomParserRTTIType] of byte = (
    SizeOf(PtrUInt),SizeOf(Boolean),SizeOf(Byte),SizeOf(Cardinal),SizeOf(Currency),
    SizeOf(Double),SizeOf(Int64),SizeOf(Integer),SizeOf(RawUTF8),0,SizeOf(Single),
    SizeOf(String),SizeOf(SynUnicode),SizeOf(TDateTime),SizeOf(TGUID),
    SizeOf(TTimeLog),SizeOf(WideString),SizeOf(Word),0);






var i: integer;
begin
  if fDataSize=0 then begin
    assert(fFullDataSize=0);
    for i := 0 to high(NestedProperty) do begin
      NestedProperty[i].ComputeDataSizeAfterAdd; 
      inc(fFullDataSize,NestedProperty[i].fDataSize);
................................................................................
    case NestedProperty[j].PropertyType of
    ptRawUTF8: PRawUTF8(Data)^ := '';
    ptString:  PString(Data)^ := '';
    ptSynUnicode: PSynUnicode(Data)^ := '';
    ptWideString: PWideString(Data)^ := '';
    ptArray:  NestedProperty[j].FinalizeNestedArray(PPtrUInt(Data)^);
    ptRecord: NestedProperty[j].FinalizeNestedRecord(Data);
    ptCustom: RecordClear(Data^,TJSONCustomParserCustom(NestedProperty[j]).fRecordTypeInfo);
    end;
    inc(Data,NestedProperty[j].fDataSize);
  end;
end;

procedure TJSONCustomParserRTTI.FinalizeNestedArray(var Data: PtrUInt);
var i: integer;
    Rec: ^TDynArrayRec;
    ItemData: PByte;
................................................................................
    case Prop.PropertyType of
    ptRecord: begin
      if not Prop.ReadOneLevel(P,Data,Options) then
        exit;
      EndOfObject := P^;
      if P^ in [',','}'] then
        inc(P);
      result := true;
      exit;
    end;
    ptArray: begin
      if P^<>'[' then
        exit; // we expect a true array here
      repeat inc(P) until P^<>' ';
      // allocate nested array at once
      ArrayLen := JSONArrayCount(P);
................................................................................
      end;
    end;
    ptCustom: begin
      P := TJSONCustomParserCustom(Prop).GetJSONCustomParserRegistration.
        Reader(P,Data^,valid);
      if (P=nil) or not valid then
        exit;

      EndOfObject := P^;
      if P^ in [',','}'] then
        inc(P);
    end;
    else begin
      PropValue := GetJSONField(P,P,@wasString,@EndOfObject);
      if (PropValue=nil) or (wasString<>(Prop.PropertyType in
................................................................................
      ptTimeLog:   PInt64(Data)^ := Iso8601ToSecondsPUTF8Char(PropValue,0);
      ptWideString:UTF8ToWideString(PropValue,StrLen(PropValue),PWideString(Data)^);
      ptWord:      PWord(Data)^ := GetCardinal(PropValue);
      ptGUID:      TextToGUID(PropValue,pointer(Data));
      end;
    end;
    end;
    inc(Data,Prop.fDataSize);
    result := true;
  end;
var i,j: integer;
    PropName: RawUTF8;
    wasString: boolean;
    Values: array of PUTF8Char;
begin
................................................................................
            inc(DynArray,8-(PtrUInt(DynArray)and 7);
          {$endif}
        end;
      aWriter.CancelLastComma;
      aWriter.Add(']');
      dec(aWriter.fHumanReadableLevel);
    end;
    ptRecord: begin
      Prop.WriteOneLevel(aWriter,Value,Options);
      aWriter.Add(',');
      exit;
    end;
    ptCustom: begin
      TJSONCustomParserCustom(Prop).GetJSONCustomParserRegistration.
        Writer(aWriter,Value^);

    end;
    end;
    aWriter.Add(',');
    inc(Value,Prop.fDataSize);
  end;
var i: integer;
    SubProp: TJSONCustomParserRTTI;
begin
  if P=nil then begin
    aWriter.AddShort('null');
    exit;

Changes to SynSelfTests.pas.

4028
4029
4030
4031
4032
4033
4034

4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048

4049
4050
4051
4052
4053
4054
4055
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomJSON2Title),
    __TTestCustomJSON2Title).Options := [soWriteHumanReadable];
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomJSON2),
    __TTestCustomJSON2).Options := [soWriteHumanReadable];
  fillchar(Trans,sizeof(Trans),0);
  U := RecordSaveJSON(Trans,TypeInfo(TTestCustomJSON2));
  Check(U=#$D#$A'{'#$D#$A#9'"Transactions": []'#$D#$A'}');

  U := '{"transactions":[{"TRTYPE":"INCOME","TRDATE":"2013-12-09 02:30:04","TRAA":"1.23",'+
   '"TRCAT1":{"TITYPE":"C1","TIID":"1","TICID":"","TIDSC30":"description1","TIORDER":"0","TIDEL":"false"},'+
   '"TRCAT2":{"TITYPE":"C2","TIID":"2","TICID":"","TIDSC30":"description2","TIORDER":"0","TIDEL":"false"},'+
   '"TRCAT3":{"TITYPE":"C3","TIID":"3","TICID":"","TIDSC30":"description3","TIORDER":"0","TIDEL":"false"},'+
   '"TRRMK":"Remark",'+
   '"TRACID":{"TITYPE":"AC","TIID":"4","TICID":"","TIDSC30":"account1","TIORDER":"0","TIDEL":"false"}}]}';
  RecordLoadJSON(Trans,@U[1],TypeInfo(TTestCustomJSON2));
  Check(length(Trans.Transactions)=1);
  Check(Trans.Transactions[0].TRTYPE='INCOME');
  Check(Trans.Transactions[0].TRACID.TIDEL='false');
  Check(Trans.Transactions[0].TRRMK='Remark');
  U := RecordSaveJSON(Trans,TypeInfo(TTestCustomJSON2));
  FileFromString(U,'transactions.json');
  Check(Hash32(U)=$7F799CD9);

  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomJSON2Title),'');
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomJSON2),'');

  Parser := TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomDiscogs),
    __TTestCustomDiscogs) as TJSONCustomParserFromTextDefinition;
  Parser.Options := [soReadIgnoreUnknownFields];
  fillchar(Disco,sizeof(Disco),0);






>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>







4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomJSON2Title),
    __TTestCustomJSON2Title).Options := [soWriteHumanReadable];
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomJSON2),
    __TTestCustomJSON2).Options := [soWriteHumanReadable];
  fillchar(Trans,sizeof(Trans),0);
  U := RecordSaveJSON(Trans,TypeInfo(TTestCustomJSON2));
  Check(U=#$D#$A'{'#$D#$A#9'"Transactions": []'#$D#$A'}');
  for i := 1 to 10 do begin
    U := '{"transactions":[{"TRTYPE":"INCOME","TRDATE":"2013-12-09 02:30:04","TRAA":"1.23",'+
     '"TRCAT1":{"TITYPE":"C1","TIID":"1","TICID":"","TIDSC30":"description1","TIORDER":"0","TIDEL":"false"},'+
     '"TRCAT2":{"TITYPE":"C2","TIID":"2","TICID":"","TIDSC30":"description2","TIORDER":"0","TIDEL":"false"},'+
     '"TRCAT3":{"TITYPE":"C3","TIID":"3","TICID":"","TIDSC30":"description3","TIORDER":"0","TIDEL":"false"},'+
     '"TRRMK":"Remark",'+
     '"TRACID":{"TITYPE":"AC","TIID":"4","TICID":"","TIDSC30":"account1","TIORDER":"0","TIDEL":"false"}}]}';
    RecordLoadJSON(Trans,@U[1],TypeInfo(TTestCustomJSON2));
    Check(length(Trans.Transactions)=1);
    Check(Trans.Transactions[0].TRTYPE='INCOME');
    Check(Trans.Transactions[0].TRACID.TIDEL='false');
    Check(Trans.Transactions[0].TRRMK='Remark');
    U := RecordSaveJSON(Trans,TypeInfo(TTestCustomJSON2));
    FileFromString(U,'transactions.json');
    Check(Hash32(U)=$7F799CD9);
  end;
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomJSON2Title),'');
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomJSON2),'');

  Parser := TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestCustomDiscogs),
    __TTestCustomDiscogs) as TJSONCustomParserFromTextDefinition;
  Parser.Options := [soReadIgnoreUnknownFields];
  fillchar(Disco,sizeof(Disco),0);