View Issue Details

IDProjectCategoryView StatusLast Update
0035665LazarusTAChartpublic2019-06-12 14:27
ReporterMarcin WiazowskiAssigned Towp 
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version2.1 (SVN)Product Build61310 
Target VersionFixed in Version 
Summary0035665: TAChart: deriving from TListChartSource may lead to infinite loop
DescriptionI introduced some problem, when adding XCountMin / YCountMin functionality to TListChartSource, in 0035089 - so I'm attaching a Reproduce demo application, and a patch to fix the problem.



Currently, TListChartSource has two constructors:

  constructor TListChartSource.Create(AOwner: TComponent);
  begin
    ...
  end;

  constructor TListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
  begin
    Create(AOwner); <==== PROBLEM IS HERE
    FXCountMin := AXCountMin;
    FYCountMin := AYCountMin;
    if FXCount < FXCountMin then
      FXCount := FXCountMin;
    if FYCount < FYCountMin then
      FYCount := FYCountMin;
  end;



The attached demo application implements TListChartSource's descendant, in the following way:

  type
    TTestSource = class(TListChartSource)
    public
      constructor Create(AOwner: TComponent); override;
    end;

  constructor TTestSource.Create(AOwner: TComponent);
  begin
    inherited Create(AOwner, 2, 2);
  end;

Currently, creating a TTestSource instance causes an infinite recursion, which finally leads to stack overflow:

1) TTestSource.Create() calls the second variant of TListChartSource.Create()
2) second variant of TListChartSource.Create() calls Create() - since Create() is a virtual method, this leads us back to TTestSource.Create()
1) TTestSource.Create() calls the second variant of TListChartSource.Create()
2) second variant of TListChartSource.Create() calls Create() - since Create() is a virtual method, this leads us back to TTestSource.Create()
1) ...
2) ...



The attached patch solves the problem.
TagsNo tags attached.
Fixed in Revision61320
LazTarget2.2
WidgetsetWin32/Win64
Attached Files
  • Reproduce.zip (2,081 bytes)
  • patch.diff (1,552 bytes)
    Index: components/tachart/tasources.pas
    ===================================================================
    --- components/tachart/tasources.pas	(revision 61310)
    +++ components/tachart/tasources.pas	(working copy)
    @@ -27,6 +27,7 @@
         FDataPoints: TStrings;
         FXCountMin: Cardinal;
         FYCountMin: Cardinal;
    +    procedure DoCreate(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
         procedure ClearCaches;
         function NewItem: PChartDataItem;
         procedure SetDataPoints(const AValue: TStrings);
    @@ -672,16 +673,9 @@
       end;
     end;
     
    -constructor TListChartSource.Create(AOwner: TComponent);
    +procedure TListChartSource.DoCreate(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
     begin
       inherited Create(AOwner);
    -  FDataPoints := TListChartSourceStrings.Create(Self);
    -  ClearCaches;
    -end;
    -
    -constructor TListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
    -begin
    -  Create(AOwner);
       FXCountMin := AXCountMin;
       FYCountMin := AYCountMin;
       if FXCount < FXCountMin then
    @@ -688,8 +682,20 @@
         FXCount := FXCountMin;
       if FYCount < FYCountMin then
         FYCount := FYCountMin;
    +  FDataPoints := TListChartSourceStrings.Create(Self);
    +  ClearCaches;
     end;
     
    +constructor TListChartSource.Create(AOwner: TComponent);
    +begin
    +  DoCreate(AOwner, 0, 0);
    +end;
    +
    +constructor TListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
    +begin
    +  DoCreate(AOwner, AXCountMin, AYCountMin);
    +end;
    +
     procedure TListChartSource.Delete(AIndex: Integer);
     begin
       // Optimization
    
    patch.diff (1,552 bytes)
  • patch_ver2.diff (5,681 bytes)
    Index: components/tachart/tacustomseries.pas
    ===================================================================
    --- components/tachart/tacustomseries.pas	(revision 61312)
    +++ components/tachart/tacustomseries.pas	(working copy)
    @@ -844,7 +844,7 @@
     
       FListener := TListener.Create(@FSource,  @SourceChanged);
       GetXYCountNeeded(nx, ny);
    -  FBuiltinSource := TListChartSource.Create(Self, nx, ny);
    +  FBuiltinSource := TBuiltinListChartSource.Create(Self, nx, ny);
       FBuiltinSource.Name := BUILTIN_SOURCE_NAME;
       FBuiltinSource.Broadcaster.Subscribe(FListener);
       FMarks := TChartMarks.Create(FChart);
    Index: components/tachart/tasources.pas
    ===================================================================
    --- components/tachart/tasources.pas	(revision 61312)
    +++ components/tachart/tasources.pas	(working copy)
    @@ -25,8 +25,6 @@
       TListChartSource = class(TCustomSortedChartSource)
       private
         FDataPoints: TStrings;
    -    FXCountMin: Cardinal;
    -    FYCountMin: Cardinal;
         procedure ClearCaches;
         function NewItem: PChartDataItem;
         procedure SetDataPoints(const AValue: TStrings);
    @@ -40,8 +38,7 @@
           EXListEmptyError = class(EChartError);
           EYListEmptyError = class(EChartError);
       public
    -    constructor Create(AOwner: TComponent); override; overload;
    -    constructor Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal); overload;
    +    constructor Create(AOwner: TComponent); override;
         destructor Destroy; override;
       public
         function Add(
    @@ -56,7 +53,7 @@
           const AX: Double; const AY: array of Double; const ALabel: String = '';
           const AColor: TChartColor = clTAColor): Integer;
         procedure Clear;
    -    procedure CopyFrom(ASource: TCustomChartSource);
    +    procedure CopyFrom(ASource: TCustomChartSource); virtual;
         procedure Delete(AIndex: Integer);
         function SetColor(AIndex: Integer; AColor: TChartColor): Integer;
         function SetText(AIndex: Integer; const AValue: String): Integer;
    @@ -78,6 +75,24 @@
         property OnCompare;
       end;
     
    +  { TBuiltinListChartSource }
    +
    +  TBuiltinListChartSource = class(TListChartSource)
    +  private
    +    FXCountMin: Cardinal;
    +    FYCountMin: Cardinal;
    +  protected
    +    procedure SetXCount(AValue: Cardinal); override;
    +    procedure SetYCount(AValue: Cardinal); override;
    +  public
    +    type
    +      EConstructorError = class(EChartError);
    +    constructor Create({%H-}AOwner: TComponent); override; overload;
    +    constructor Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal); overload;
    +  public
    +    procedure CopyFrom(ASource: TCustomChartSource); override;
    +  end;
    +
       { TSortedChartSource }
     
       TSortedChartSource = class(TCustomSortedChartSource)
    @@ -640,11 +655,7 @@
       i: Integer;
       pcd: PChartDataItem;
     begin
    -  if ASource.XCount < FXCountMin then
    -    raise EXCountError.CreateFmt(rsSourceCountError2, [ClassName, FXCountMin, 'x']);
    -  if ASource.YCount < FYCountMin then
    -    raise EYCountError.CreateFmt(rsSourceCountError2, [ClassName, FYCountMin, 'y']);
    -
    +  if ASource = Self then exit;
       BeginUpdate;
       try
         Clear;
    @@ -679,17 +690,6 @@
       ClearCaches;
     end;
     
    -constructor TListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
    -begin
    -  Create(AOwner);
    -  FXCountMin := AXCountMin;
    -  FYCountMin := AYCountMin;
    -  if FXCount < FXCountMin then
    -    FXCount := FXCountMin;
    -  if FYCount < FYCountMin then
    -    FYCount := FYCountMin;
    -end;
    -
     procedure TListChartSource.Delete(AIndex: Integer);
     begin
       // Optimization
    @@ -716,7 +716,8 @@
     
     destructor TListChartSource.Destroy;
     begin
    -  Clear;
    +  if Assigned(FData) then
    +    Clear;
       FreeAndNil(FDataPoints);
       inherited;
     end;
    @@ -764,8 +765,6 @@
     var
       i, nx: Integer;
     begin
    -  if AValue < FXCountMin then
    -    raise EXCountError.CreateFmt(rsSourceCountError2, [ClassName, FXCountMin, 'x']);
       if AValue = FXCount then exit;
       FXCount := AValue;
       nx := Max(FXCount - 1, 0);
    @@ -822,8 +821,6 @@
     var
       i, ny: Integer;
     begin
    -  if AValue < FYCountMin then
    -    raise EYCountError.CreateFmt(rsSourceCountError2, [ClassName, FYCountMin, 'y']);
       if AValue = FYCount then exit;
       FYCount := AValue;
       ny := Max(FYCount - 1, 0);
    @@ -900,6 +897,47 @@
       (FDataPoints as TListChartSourceStrings).LoadingFinished;
     end;
     
    +{ TBuiltinListChartSource }
    +
    +constructor TBuiltinListChartSource.Create(AOwner: TComponent);
    +begin
    +  raise EConstructorError.CreateFmt('%0:s.Create() requires additional parameters.', [ClassName]);
    +end;
    +
    +constructor TBuiltinListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
    +begin
    +  inherited Create(AOwner);
    +  FXCountMin := AXCountMin;
    +  FYCountMin := AYCountMin;
    +  if FXCount < FXCountMin then
    +    FXCount := FXCountMin;
    +  if FYCount < FYCountMin then
    +    FYCount := FYCountMin;
    +end;
    +
    +procedure TBuiltinListChartSource.CopyFrom(ASource: TCustomChartSource);
    +begin
    +  if ASource.XCount < FXCountMin then
    +    raise EXCountError.CreateFmt(rsSourceCountError2, [ClassName, FXCountMin, 'x']);
    +  if ASource.YCount < FYCountMin then
    +    raise EYCountError.CreateFmt(rsSourceCountError2, [ClassName, FYCountMin, 'y']);
    +  inherited;
    +end;
    +
    +procedure TBuiltinListChartSource.SetXCount(AValue: Cardinal);
    +begin
    +  if AValue < FXCountMin then
    +    raise EXCountError.CreateFmt(rsSourceCountError2, [ClassName, FXCountMin, 'x']);
    +  inherited;
    +end;
    +
    +procedure TBuiltinListChartSource.SetYCount(AValue: Cardinal);
    +begin
    +  if AValue < FYCountMin then
    +    raise EYCountError.CreateFmt(rsSourceCountError2, [ClassName, FYCountMin, 'y']);
    +  inherited;
    +end;
    +
     { TSortedChartSource }
     
     constructor TSortedChartSource.Create(AOwner: TComponent);
    
    patch_ver2.diff (5,681 bytes)

Relationships

related to 0035089 closedwp TAChart: crash in IDE 
related to 0035705 closedwp TAChart: added lacking protection for TCustomColorMapSeries color source 

Activities

Marcin Wiazowski

2019-06-01 22:34

reporter  

Reproduce.zip (2,081 bytes)
patch.diff (1,552 bytes)
Index: components/tachart/tasources.pas
===================================================================
--- components/tachart/tasources.pas	(revision 61310)
+++ components/tachart/tasources.pas	(working copy)
@@ -27,6 +27,7 @@
     FDataPoints: TStrings;
     FXCountMin: Cardinal;
     FYCountMin: Cardinal;
+    procedure DoCreate(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
     procedure ClearCaches;
     function NewItem: PChartDataItem;
     procedure SetDataPoints(const AValue: TStrings);
@@ -672,16 +673,9 @@
   end;
 end;
 
-constructor TListChartSource.Create(AOwner: TComponent);
+procedure TListChartSource.DoCreate(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
 begin
   inherited Create(AOwner);
-  FDataPoints := TListChartSourceStrings.Create(Self);
-  ClearCaches;
-end;
-
-constructor TListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
-begin
-  Create(AOwner);
   FXCountMin := AXCountMin;
   FYCountMin := AYCountMin;
   if FXCount < FXCountMin then
@@ -688,8 +682,20 @@
     FXCount := FXCountMin;
   if FYCount < FYCountMin then
     FYCount := FYCountMin;
+  FDataPoints := TListChartSourceStrings.Create(Self);
+  ClearCaches;
 end;
 
+constructor TListChartSource.Create(AOwner: TComponent);
+begin
+  DoCreate(AOwner, 0, 0);
+end;
+
+constructor TListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
+begin
+  DoCreate(AOwner, AXCountMin, AYCountMin);
+end;
+
 procedure TListChartSource.Delete(AIndex: Integer);
 begin
   // Optimization
patch.diff (1,552 bytes)

wp

2019-06-03 19:16

developer   ~0116550

I still don't like it because the new constructor has XCountMin and YCountMin as parameters in a way the user can set them in any TListChartSource instance, not only for Builtins. I did not check it but don't think that the infrastructure is well-prepared for the case when the user sets XCountMin=10 and YCountMin=10 for a "normal" ListChartSource because he confuses these values with the number of data point x and y values...

Remembering your earlier explanation that XCountMin and YCountMin are needed only for the Builtin chart source, I rather do it like this:
- Restore the original constructors, i.e. Create without the CountMin parameters.
- in TASeries, declare a TBuiltInListChartSource = class(TListChartSource) in the implementation section, i.e. TBuiltinListChartSource is visible only within the TASeries unit, in particular for TChartSeries where it is needed.
- in the constructor of TChartSeries, create FBuiltinSource as TBuiltInListChartSource, not as TListChartsource. Set XCountMin and YCountMin directly after calling Create. i.e:

  constructor TChartSeries.Create(AOwner: TComponent);
  ...
  begin
    ...
    GetXYCountNeeded(nx, ny);
    FBuiltinSource := TBuiltinListChartSource.Create(Self);
    FBuiltinSource.FXCountMin := nx;
    FBuiltinSource.FYCountMax := ny;
  ...

- This way FBultinSource has these new values but behaves like a normal TListChartSource.
- Maybe, by adding appropriate virtual methods, it is possible to remove XCountMin and YCountMin from TListChartSource altogether and put this functionality only in TBuiltinListChartSource.

Marcin Wiazowski

2019-06-03 23:47

reporter   ~0116553

I'm attaching patch_ver2.diff, which implements XCountMin / YCountMin functionality as you invented - although with two adjustments:


1) Instead of having:

  FBuiltinSource := TBuiltinListChartSource.Create(Self);
  FBuiltinSource.FXCountMin := nx;
  FBuiltinSource.FYCountMax := ny;

there is:

  FBuiltinSource := TBuiltinListChartSource.Create(Self, nx, ny);

so nx and ny are still passed by using the constructor - this guarantees, that the required limits will be enforced (i.e. it's not possible to omit setting FXCountMin / FYCountMin values by mistake). To avoid mistakes, also the original constructor is overridden - it only raises an exception, so, again, mistakes are impossible.

This way, we have still two constructors - this time in TBuiltinListChartSource. I think it's not a problem here - TBuiltinListChartSource is neither documented, nor registered as a component. This way, it's not possible to create TBuiltinListChartSource instance by mistake. And using the default constructor will raise an exception, so - again - mistake is not possible.


2) As far as I can see, TBuiltinListChartSource should also be used in TCustomColorMapSeries.Create(), to create FBuiltinColorSource instance (I'll provide a separate patch for that). For this reason, TBuiltinListChartSource must be publicly available, so I implemented it in TASources.

patch_ver2.diff (5,681 bytes)
Index: components/tachart/tacustomseries.pas
===================================================================
--- components/tachart/tacustomseries.pas	(revision 61312)
+++ components/tachart/tacustomseries.pas	(working copy)
@@ -844,7 +844,7 @@
 
   FListener := TListener.Create(@FSource,  @SourceChanged);
   GetXYCountNeeded(nx, ny);
-  FBuiltinSource := TListChartSource.Create(Self, nx, ny);
+  FBuiltinSource := TBuiltinListChartSource.Create(Self, nx, ny);
   FBuiltinSource.Name := BUILTIN_SOURCE_NAME;
   FBuiltinSource.Broadcaster.Subscribe(FListener);
   FMarks := TChartMarks.Create(FChart);
Index: components/tachart/tasources.pas
===================================================================
--- components/tachart/tasources.pas	(revision 61312)
+++ components/tachart/tasources.pas	(working copy)
@@ -25,8 +25,6 @@
   TListChartSource = class(TCustomSortedChartSource)
   private
     FDataPoints: TStrings;
-    FXCountMin: Cardinal;
-    FYCountMin: Cardinal;
     procedure ClearCaches;
     function NewItem: PChartDataItem;
     procedure SetDataPoints(const AValue: TStrings);
@@ -40,8 +38,7 @@
       EXListEmptyError = class(EChartError);
       EYListEmptyError = class(EChartError);
   public
-    constructor Create(AOwner: TComponent); override; overload;
-    constructor Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal); overload;
+    constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
   public
     function Add(
@@ -56,7 +53,7 @@
       const AX: Double; const AY: array of Double; const ALabel: String = '';
       const AColor: TChartColor = clTAColor): Integer;
     procedure Clear;
-    procedure CopyFrom(ASource: TCustomChartSource);
+    procedure CopyFrom(ASource: TCustomChartSource); virtual;
     procedure Delete(AIndex: Integer);
     function SetColor(AIndex: Integer; AColor: TChartColor): Integer;
     function SetText(AIndex: Integer; const AValue: String): Integer;
@@ -78,6 +75,24 @@
     property OnCompare;
   end;
 
+  { TBuiltinListChartSource }
+
+  TBuiltinListChartSource = class(TListChartSource)
+  private
+    FXCountMin: Cardinal;
+    FYCountMin: Cardinal;
+  protected
+    procedure SetXCount(AValue: Cardinal); override;
+    procedure SetYCount(AValue: Cardinal); override;
+  public
+    type
+      EConstructorError = class(EChartError);
+    constructor Create({%H-}AOwner: TComponent); override; overload;
+    constructor Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal); overload;
+  public
+    procedure CopyFrom(ASource: TCustomChartSource); override;
+  end;
+
   { TSortedChartSource }
 
   TSortedChartSource = class(TCustomSortedChartSource)
@@ -640,11 +655,7 @@
   i: Integer;
   pcd: PChartDataItem;
 begin
-  if ASource.XCount < FXCountMin then
-    raise EXCountError.CreateFmt(rsSourceCountError2, [ClassName, FXCountMin, 'x']);
-  if ASource.YCount < FYCountMin then
-    raise EYCountError.CreateFmt(rsSourceCountError2, [ClassName, FYCountMin, 'y']);
-
+  if ASource = Self then exit;
   BeginUpdate;
   try
     Clear;
@@ -679,17 +690,6 @@
   ClearCaches;
 end;
 
-constructor TListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
-begin
-  Create(AOwner);
-  FXCountMin := AXCountMin;
-  FYCountMin := AYCountMin;
-  if FXCount < FXCountMin then
-    FXCount := FXCountMin;
-  if FYCount < FYCountMin then
-    FYCount := FYCountMin;
-end;
-
 procedure TListChartSource.Delete(AIndex: Integer);
 begin
   // Optimization
@@ -716,7 +716,8 @@
 
 destructor TListChartSource.Destroy;
 begin
-  Clear;
+  if Assigned(FData) then
+    Clear;
   FreeAndNil(FDataPoints);
   inherited;
 end;
@@ -764,8 +765,6 @@
 var
   i, nx: Integer;
 begin
-  if AValue < FXCountMin then
-    raise EXCountError.CreateFmt(rsSourceCountError2, [ClassName, FXCountMin, 'x']);
   if AValue = FXCount then exit;
   FXCount := AValue;
   nx := Max(FXCount - 1, 0);
@@ -822,8 +821,6 @@
 var
   i, ny: Integer;
 begin
-  if AValue < FYCountMin then
-    raise EYCountError.CreateFmt(rsSourceCountError2, [ClassName, FYCountMin, 'y']);
   if AValue = FYCount then exit;
   FYCount := AValue;
   ny := Max(FYCount - 1, 0);
@@ -900,6 +897,47 @@
   (FDataPoints as TListChartSourceStrings).LoadingFinished;
 end;
 
+{ TBuiltinListChartSource }
+
+constructor TBuiltinListChartSource.Create(AOwner: TComponent);
+begin
+  raise EConstructorError.CreateFmt('%0:s.Create() requires additional parameters.', [ClassName]);
+end;
+
+constructor TBuiltinListChartSource.Create(AOwner: TComponent; AXCountMin, AYCountMin: Cardinal);
+begin
+  inherited Create(AOwner);
+  FXCountMin := AXCountMin;
+  FYCountMin := AYCountMin;
+  if FXCount < FXCountMin then
+    FXCount := FXCountMin;
+  if FYCount < FYCountMin then
+    FYCount := FYCountMin;
+end;
+
+procedure TBuiltinListChartSource.CopyFrom(ASource: TCustomChartSource);
+begin
+  if ASource.XCount < FXCountMin then
+    raise EXCountError.CreateFmt(rsSourceCountError2, [ClassName, FXCountMin, 'x']);
+  if ASource.YCount < FYCountMin then
+    raise EYCountError.CreateFmt(rsSourceCountError2, [ClassName, FYCountMin, 'y']);
+  inherited;
+end;
+
+procedure TBuiltinListChartSource.SetXCount(AValue: Cardinal);
+begin
+  if AValue < FXCountMin then
+    raise EXCountError.CreateFmt(rsSourceCountError2, [ClassName, FXCountMin, 'x']);
+  inherited;
+end;
+
+procedure TBuiltinListChartSource.SetYCount(AValue: Cardinal);
+begin
+  if AValue < FYCountMin then
+    raise EYCountError.CreateFmt(rsSourceCountError2, [ClassName, FYCountMin, 'y']);
+  inherited;
+end;
+
 { TSortedChartSource }
 
 constructor TSortedChartSource.Create(AOwner: TComponent);
patch_ver2.diff (5,681 bytes)

wp

2019-06-04 16:39

developer   ~0116566

Applied, thanks. Did not change TColorMapSeries.BuiltinColorSource so far, because maybe there will once be a special TColorSource (not sure about the benefits)?

Marcin Wiazowski

2019-06-12 00:02

reporter   ~0116680

Fix confirmed.

As I just checked, TBuiltinListChartSource is fully sufficient also for the color source; I created a separate report for that: 0035705.

Issue History

Date Modified Username Field Change
2019-06-01 22:34 Marcin Wiazowski New Issue
2019-06-01 22:34 Marcin Wiazowski File Added: Reproduce.zip
2019-06-01 22:34 Marcin Wiazowski File Added: patch.diff
2019-06-02 11:55 wp Relationship added related to 0035089
2019-06-02 11:55 wp Assigned To => wp
2019-06-02 11:55 wp Status new => assigned
2019-06-03 19:16 wp Note Added: 0116550
2019-06-03 19:16 wp Status assigned => feedback
2019-06-03 19:16 wp LazTarget => -
2019-06-03 23:47 Marcin Wiazowski File Added: patch_ver2.diff
2019-06-03 23:47 Marcin Wiazowski Note Added: 0116553
2019-06-03 23:47 Marcin Wiazowski Status feedback => assigned
2019-06-04 16:39 wp Status assigned => resolved
2019-06-04 16:39 wp Resolution open => fixed
2019-06-04 16:39 wp Fixed in Revision => 61320
2019-06-04 16:39 wp LazTarget - => 2.2
2019-06-04 16:39 wp Widgetset Win32/Win64 => Win32/Win64
2019-06-04 16:39 wp Note Added: 0116566
2019-06-12 00:02 Marcin Wiazowski Status resolved => closed
2019-06-12 00:02 Marcin Wiazowski Note Added: 0116680
2019-06-12 14:27 wp Relationship added related to 0035705