unit FpDebugConvDebugForJson;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, ExtCtrls, StdCtrls, ActnList,
  FpDebugStringConstants, FpDebugValueConvertors, FpDebugDebuggerBase,
  LazDebuggerValueConverter, LazDebuggerIntfBaseTypes, LazDebuggerIntf,
  FpDbgInfo, FpDbgClasses, FpdMemoryTools, FpDbgCallContextInfo,
  FpErrorMessages, DbgIntfBaseTypes;

type

  { TJsonForDebugSettingsFrame }

  TJsonForDebugSettingsFrame = class(TFrame, ILazDbgValueConverterSettingsFrameIntf)
    ConverterWithFuncCallSettingsFrame1: TConverterWithFuncCallSettingsFrame;
    edFuncName: TEdit;
    edJsonAddress: TEdit;
    edJsonTypename: TEdit;
    Label1: TLabel;
    lblFuncName: TLabel;
    lblJsonAddress: TLabel;
    lblJsonTypename: TLabel;
  private

  protected
    procedure ReadFrom(AConvertor: ILazDbgValueConverterIntf);
    function WriteTo(AConvertor: ILazDbgValueConverterIntf): Boolean;
    function GetFrame: TObject;
  public
    constructor Create(TheOwner: TComponent); override;
  end;

  { TFpDbgValueConverterJsonForDebug }

  TFpDbgValueConverterJsonForDebug = class(TFpDbgValueConverterWithFuncCall)
  private
    FFunctionName: String;
    FJsonAddressKey: String;
    FJsonTypenameKey: String;
    function FunctionNameIsStored: Boolean;
    function GetProcAddr(AnFpDebugger: TFpDebugDebuggerBase; AnExpressionScope: TFpDbgSymbolScope): TDBGPtr;
    function JsonAddressKeyIsStored: Boolean;
  protected
    function GetSettingsFrame: ILazDbgValueConverterSettingsFrameIntf; override;
    procedure Init; override;
  public
    class function GetName: String; override;
    function GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass; override;
    procedure Assign(ASource: TFpDbgValueConverter); override;
    function ConvertValue(ASourceValue: TFpValue;
                          AnFpDebugger: TFpDebugDebuggerBase;
                          AnExpressionScope: TFpDbgSymbolScope;
                          var AnResData: IDbgWatchDataIntf
                         ): TFpValue; override;

  published
    property FunctionName: String read FFunctionName write FFunctionName stored FunctionNameIsStored;
    property JsonAddressKey: String read FJsonAddressKey write FJsonAddressKey stored JsonAddressKeyIsStored;
    property JsonTypenameKey: String read FJsonTypenameKey write FJsonTypenameKey;
  end;

  { TFpDbgValueConverterJsonForDebugRegistryEntry }

  TFpDbgValueConverterJsonForDebugRegistryEntry = class(TFpDbgValueConverterRegistryEntry)
  public
    class function GetConvertorClass: TClass; override;
  end;

implementation

{$R *.lfm}

{ TJsonForDebugSettingsFrame }

procedure TJsonForDebugSettingsFrame.ReadFrom(
  AConvertor: ILazDbgValueConverterIntf);
var
  c: TFpDbgValueConverterJsonForDebug;
begin
  ConverterWithFuncCallSettingsFrame1.ReadFrom(AConvertor);

  if not (AConvertor.GetObject is TFpDbgValueConverterJsonForDebug) then
    exit;

  c := TFpDbgValueConverterJsonForDebug(AConvertor.GetObject);

  edFuncName.Text := c.FFunctionName;
  edJsonAddress.Text := c.FJsonAddressKey;
  edJsonTypename.Text := c.FJsonTypenameKey;
end;

function TJsonForDebugSettingsFrame.WriteTo(
  AConvertor: ILazDbgValueConverterIntf): Boolean;
var
  c: TFpDbgValueConverterJsonForDebug;
begin
  Result := ConverterWithFuncCallSettingsFrame1.WriteTo(AConvertor);

  if not (AConvertor.GetObject is TFpDbgValueConverterJsonForDebug) then
    exit;

  c := TFpDbgValueConverterJsonForDebug(AConvertor.GetObject);

  Result :=
    Result or
    (c.FFunctionName    <> trim(edFuncName.Text)) or
    (c.FJsonAddressKey  <> trim(edJsonAddress.Text)) or
    (c.FJsonTypenameKey <> trim(edJsonTypename.Text));

  c.FFunctionName    := trim(edFuncName.Text);
  c.FJsonAddressKey  := trim(edJsonAddress.Text);
  c.FJsonTypenameKey := trim(edJsonTypename.Text);
end;

function TJsonForDebugSettingsFrame.GetFrame: TObject;
begin
  Result := Self;
end;

constructor TJsonForDebugSettingsFrame.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  lblFuncName.Caption := drsFunctionName;
  lblJsonAddress.Caption := drsKeyForAddress;
  lblJsonTypename.Caption := drsKeyForTypename;
end;

{ TFpDbgValueConverterJsonForDebug }

function TFpDbgValueConverterJsonForDebug.GetProcAddr(
  AnFpDebugger: TFpDebugDebuggerBase; AnExpressionScope: TFpDbgSymbolScope
  ): TDBGPtr;
var
  CurProc: TDbgProcess;
  ProcSymVal: TFpValue;
  ProcSym: TFpSymbol;
begin
  Result := AnFpDebugger.GetCachedData(pointer(Self));
  if Result <> 0 then
    exit;

  CurProc := AnFpDebugger.DbgController.CurrentProcess;
  if CurProc = nil then
    exit;

  ProcSymVal := AnExpressionScope.FindSymbol(FFunctionName, '', [fsfIgnoreEnumVals]);
  if ProcSymVal <> nil then begin
    if (ProcSymVal.Kind = skProcedure) and IsTargetAddr(ProcSymVal.DataAddress)
    //and
    //   (ProcSymVal.NestedSymbolCount = 3)
    then begin
      Result := ProcSymVal.DataAddress.Address;
      AnFpDebugger.SetCachedData(pointer(TFpDbgValueConverterJsonForDebug), Result);
      ProcSymVal.ReleaseReference;
      exit;
    end;
    Result := 0;
    Result := ProcSymVal.DataAddress.Address;
  end;

  ProcSym := CurProc.FindProcSymbol(FFunctionName);
  if (ProcSym <> nil) and (ProcSym.Kind = skProcedure) and
     (IsTargetAddr(ProcSym.Address))
  then begin
    Result := ProcSym.Address.Address;
    AnFpDebugger.SetCachedData(pointer(TFpDbgValueConverterJsonForDebug), Result);
  end;
  ProcSym.ReleaseReference;
end;

function TFpDbgValueConverterJsonForDebug.JsonAddressKeyIsStored: Boolean;
begin
  Result := FJsonAddressKey <> 'Address';
end;

function TFpDbgValueConverterJsonForDebug.FunctionNameIsStored: Boolean;
begin
  Result := FFunctionName <> 'JsonForDebug';
end;

function TFpDbgValueConverterJsonForDebug.GetSettingsFrame: ILazDbgValueConverterSettingsFrameIntf;
begin
  Result := TJsonForDebugSettingsFrame.Create(nil);
end;

procedure TFpDbgValueConverterJsonForDebug.Init;
begin
  inherited Init;
  FFunctionName := 'JsonForDebug';
  FJsonAddressKey := 'Address'
end;

class function TFpDbgValueConverterJsonForDebug.GetName: String;
begin
  Result := drsCallJsonForDebug;
end;

function TFpDbgValueConverterJsonForDebug.GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass;
begin
  Result := TFpDbgValueConverterJsonForDebugRegistryEntry;
end;

procedure TFpDbgValueConverterJsonForDebug.Assign(ASource: TFpDbgValueConverter);
begin
  inherited Assign(ASource);
  if ASource is TFpDbgValueConverterJsonForDebug then begin
    FFunctionName    := TFpDbgValueConverterJsonForDebug(ASource).FFunctionName;
    FJsonAddressKey  := TFpDbgValueConverterJsonForDebug(ASource).FJsonAddressKey;
    FJsonTypenameKey := TFpDbgValueConverterJsonForDebug(ASource).FJsonTypenameKey;
  end;
end;

function TFpDbgValueConverterJsonForDebug.ConvertValue(ASourceValue: TFpValue;
  AnFpDebugger: TFpDebugDebuggerBase; AnExpressionScope: TFpDbgSymbolScope;
  var AnResData: IDbgWatchDataIntf): TFpValue;
var
  CurProccess: TDbgProcess;
  TpName, JsonText: String;
  ProcAddr, SetLenProc, DecRefProc,
  TpNameAddr, TpNewNameAddr, TpNameRefAddr, TextAddr, TextRefAddr: TDbgPtr;
  CallContext: TFpDbgInfoCallContext;
  r: Boolean;
begin
  Result := nil;

  if (not (svfAddress in ASourceValue.FieldFlags)) or
     (not IsTargetAddr(ASourceValue.Address))
  then begin
    SetError(CreateError(fpErrAnyError, ['Value not in memory']));
    exit;
  end;

  TpName := '';
  if ASourceValue.TypeInfo <> nil then
    TpName := ASourceValue.TypeInfo.Name;

  if TpName = '' then begin
    SetError(CreateError(fpErrAnyError, ['no typename']));
    exit;
  end;

  ProcAddr := GetProcAddr(AnFpDebugger, AnExpressionScope);
  if ProcAddr = 0 then begin
    SetError(CreateError(fpErrAnyError, ['JsonForDebug not found']));
    exit;
  end;

  CurProccess := AnFpDebugger.DbgController.CurrentProcess;
  SetLenProc := AnFpDebugger.GetCached_FPC_ANSISTR_SETLENGTH;
  DecRefProc := AnFpDebugger.GetCached_FPC_ANSISTR_DECR_REF;
  if (SetLenProc = 0) or (DecRefProc = 0) or (CurProccess = nil)
  then begin
    SetError(CreateError(fpErrAnyError, ['internal error']));
    exit;
  end;


  TpNameAddr := 0;
  TpNewNameAddr := 0;
  TpNameRefAddr := 0;
  TextAddr := 0;
  TextRefAddr := 0;
  try
    if (not AnFpDebugger.CreateAnsiStringInTarget(SetLenProc, TpNameAddr, TpName, AnExpressionScope.LocationContext)) or
       (TpNameAddr = 0)
    then begin
      TpNameAddr := 0;
      SetError(CreateError(fpErrAnyError, ['failed to set param']));
      exit;
    end;


    CallContext := AnFpDebugger.DbgController.Call(TargetLoc(ProcAddr), AnExpressionScope.LocationContext,
      AnFpDebugger.MemReader, AnFpDebugger.MemConverter);
    if CallContext = nil then begin
      SetError(CreateError(fpErrAnyError, ['function call not possible']));
      exit;
    end;

    if (not CallContext.AddOrdinalParam(ASourceValue.Address.Address)) or
       (not CallContext.AddOrdinalViaRefAsParam(TpNameAddr, TpNameRefAddr)) or
       (not CallContext.AddOrdinalViaRefAsParam(0, TextRefAddr))
    then begin
      SetError(CreateError(fpErrAnyError, ['failed to set param']));
      exit;
    end;

    CallContext.FinalizeParams; // force the string as first param (32bit) // TODO

    AnFpDebugger.BeforeWatchEval(CallContext);
    AnFpDebugger.RunProcessLoop(not FuncCallRunAllThreads);

    if not CallContext.IsValid then begin
      if (IsError(CallContext.LastError)) then
        SetError(CallContext.LastError)
      else
      if (CallContext.Message <> '') then
        SetError(CreateError(fpErrAnyError, [CallContext.Message]));
      exit;
    end;

    r := True;
    if not CurProccess.ReadAddress(TpNameRefAddr, TpNewNameAddr) then begin
      r := False;
      TpNewNameAddr := 0;
    end;
    if not CurProccess.ReadAddress(TextRefAddr, TextAddr) then begin
      r := False;
      TextAddr:= 0;
    end;

    if not AnFpDebugger.ReadAnsiStringFromTarget(TpNewNameAddr, TpName) then
      r := False;
    if not AnFpDebugger.ReadAnsiStringFromTarget(TextAddr, JsonText) then
      r := False;

    if not r then begin
      SetError(CreateError(fpErrAnyError, ['failed to get result']));
      exit;
    end;


    AnFpDebugger.DbgController.AbortCurrentCommand(True);
    CallContext.ReleaseReference;

    Result := TFpValueConstString.Create(JsonText);
    TFpValueConstString(Result).SetTypeName(TpName);

  finally
    if TpNewNameAddr <> 0 then
      TpNameAddr := TpNewNameAddr;
    if TpNameAddr <> 0 then
      AnFpDebugger.CallTargetFuncStringDecRef(DecRefProc, TpNameAddr, AnExpressionScope.LocationContext);
    if TextAddr <> 0 then
      AnFpDebugger.CallTargetFuncStringDecRef(DecRefProc, TextAddr, AnExpressionScope.LocationContext);
  end;
end;

{ TFpDbgValueConverterJsonForDebugRegistryEntry }

class function TFpDbgValueConverterJsonForDebugRegistryEntry.GetConvertorClass: TClass;
begin
  Result := TFpDbgValueConverterJsonForDebug;
end;

initialization
  ValueConverterRegistry.Add(TFpDbgValueConverterJsonForDebugRegistryEntry);

end.

