5
votes

Exception.RaiseOuterException vs W1035 La valeur de retour de la fonction '% s' peut être indéfinie

Ceci est déjà signalé comme RSP-25603: "Exception.RaiseOuterException peut provoquer un avertissement W1035 incorrect" .


Étant donné la fonction (de démonstration) suivante F , j'ai changé une instruction de levée d'exception pour maintenant enchaîner les exceptions:

--- before
+++ after
@@ -1,11 +1,11 @@
 function F(X: NativeInt): NativeInt;
 begin
   try
     Result := 1 div X;
   except
     on EDivByZero do
-      {ECustom}Exception.Create('...');
+      Exception.RaiseOuterException({ECustom}Exception.Create('...'));
     else
       raise;
   end;
 end;

Maintenant, Ctrl-F9 donne l'avertissement W1035 :

[Avertissement dcc32]: W1035 La valeur de retour de la fonction 'F' est peut-être indéfinie

Cependant, tous les cas sont traités. Le compilateur ne parvient pas à reconnaître Exception.RaiseOuterException tant qu'opération de raise puissance.

Malheureusement FAcquireInnerException: Boolean est privé pour la classe Exception , même pas pour être défini sur True dans les classes personnalisées dérivées que je pourrais continuer à raise ECustomException.Create directement ( raise ECustomException.Create ).

Existe-t-il un moyen de faire comprendre au compilateur, tout en gardant les exceptions enchaînées? Sinon, je peux penser à {$Warn No_RetVal Off} . Sinon, comment pourrais-je contourner cet avertissement?


5 commentaires

J'ai souvent une sous- procedure Inv; begin raise EFrogProperties.Create('Invalid frog properties.'); end; locale comme la procedure Inv; begin raise EFrogProperties.Create('Invalid frog properties.'); end; et aimerait aussi un mot-clé noreturn pour que le compilateur sache qu'il ne reviendra jamais à l'appelant.


J'ai toujours aimé la conception de RaiseOuterException() . Pourquoi ils n'ont pas simplement introduit un nouveau constructeur qui capture une Exception existante ou acquiert l' Exception actuelle, ou au moins une méthode CreateOuterException() qui retourne une nouvelle Exception qui peut être raise séparément, est derrière moi.


@RemyLebeau: Juste être curieux: que diriez-vous d'un mot-clé ou d'un attribut noreturn ? (Peut-être pas pour résoudre ce problème particulier, mais en général.)


J'ai créé une classe EChainedException qui, lorsqu'elle est levée, capture toujours l'exception interne (comme Exception.RaiseOuterExcepotion). Comme vous pouvez utiliser relancer directement, l'avertissement disparaîtra également, mais les excès seront toujours enchaînés


@ H.Hasenack En regardant l'implémentation, private FAcquireInnerException: Boolean = False rend l'ensemble du mécanisme d' Exception inaccessible; c'est-à-dire que la protected procedure SetInnerException devient un no-op pour les classes détruites, par exemple. Réimplémentez-vous la mécanique? Ce sont pas mal de choses à suivre pour rester à égalité avec Exception . Ou FAcquireInnerException la visibilité de FAcquireInnerException ? Souhaitez-vous partager le code de votre EChainedException ?


3 Réponses :


3
votes

Une façon dont je peux penser pour éviter l'avertissement, sans le désactiver, est de faire ce qui suit à la place:

function F(X: NativeInt): NativeInt;
begin
  try
    Result := 1 div X;
  except
    on E: EDivByZero do
    begin
      Exception.RaiseOuterException({ECustom}Exception.Create('...'));
      Result := 0; // <-- just to keep the compiler happy
    end;
  end;
end;

MISE À JOUR: Une autre façon, comme indiqué dans un commentaire, serait de simplement définir une valeur de retour qui n'est pas réellement atteinte au moment de l'exécution, par exemple:

function F(X: NativeInt): NativeInt;
begin
  try
    Result := 1 div X;
  except
    on E: Exception do
    begin
      if E is EDivByZero then
        Exception.RaiseOuterException({ECustom}Exception.Create('...'));
      raise;
    end;
  end;
end;


1 commentaires

Bien sûr, il existe de nombreuses variantes. Par exemple, vous pouvez également supprimer l'avertissement en attribuant quelque chose à Result . Vous pouvez le faire après (!) L' RaiseOuterException . Dans le code d'origine de l'OP: Replace Exception.RaiseOuterException({ECustom}Exception.Create('...‌​')); avec begin Exception.RaiseOuterException({ECustom}Exception.Create('...‌​')); Exit(0) end;



1
votes

Solution EChainedException

(comme demandé par Max)

Mise à jour J'ai publié un FR pour cela chez Embarcadero. Veuillez voter si vous aimez cette solution proposée. RSP-31679

En utilisant cette classe, l'exception interne est toujours enregistrée "comme si" vous aviez appelé Exception.RaiseOuterException . Cela vous permet d'utiliser la simple instruction raise , cela évite que le message d'avertissement soit émis par le compilateur.

Utilisation

EChainedException simplement vos exceptions personnalisées de EChainedException au lieu d' Exception , et utilisez raise plutôt Exception.RaiseOuterException .

Code source

Le code correspondant est ci-dessous. Mon EChainedException complet est un peu plus compliqué que cela pour supporter la détection des exceptions fatales et le stacktracing, etc. S'il ne compile pas, faites-moi savoir ce qui manque et j'ajouterai la partie manquante.

unit uChainedException;

interface

uses Sysutils;

{$M+} // ensures RTTI info is present for EChainedException

type
    EChainedException = class(Exception)
    protected
      procedure RaisingException(P: system.sysutils.PExceptionRecord); override;
    end;

implementation

uses rtti;

var // rtti pointers for handling the inner exception
  vInnerExceptionOffset: NativeInt = -1;
  vAcquireInnerExceptionOffset: NativeInt = -1;
  vRunningInIDEInitialized: Boolean;
  vRunningInIDE: Boolean;

function RunningInIDE:boolean;
begin
  if not vRunningInIDEInitialized then
  begin
    vRunningInIDE:=AnsiSameText(ExtractFileName(ParamStr(0)),'BDS.EXE');
    vRunningInIDEInitialized:=True;
  end;
  Result:=vRunningInIDE;
end;


procedure EChainedException.RaisingException(P: System.sysutils.PExceptionRecord);
var
  PBoolean: ^Boolean;
  PObject : ^TObject;
begin
  if (ExceptObject<>self) and (vAcquireInnerExceptionOffset >=0)  then
  begin
    PBoolean := Pointer(NativeInt(Self)+vAcquireInnerExceptionOffset);
    PBoolean^ := PBoolean^ or not RunningInIDE;
  end;
  inherited;
  // in some rare cases (like reraise exception from another thread)
  // it may happen that the innerexception points to self
  // this is corrected here.
  if InnerException=self then
  begin
    PObject := Pointer(NativeInt(Self)+vInnerExceptionOffset);
    PObject^ := nil;
  end;
end;

procedure UnprepAutoInnerException;
begin
  vInnerExceptionOffset:=-1;
  vAcquireInnerExceptionOffset:=-1;
end;

procedure PrepAutoInnerException;
var
  lRTTIContext: TRttiContext;
  lInnerException:TRttiField;
  lAcquireInnerException:TRttiField;
  lClass: TRttiInstanceType;
begin
  try
    lRTTIContext.Create;     //Notice vRTTIContext is a record, .Create initializes properties
    try
      lClass:=lRTTIContext.GetType(Exception) as TRttiInstanceType;
      lInnerException:=lClass.GetField('FInnerException');
      vInnerExceptionOffset := lInnerException.Offset;
      lAcquireInnerException:=lClass.GetField('FAcquireInnerException');
      vAcquireInnerExceptionOffset := lAcquireInnerException.Offset;
    except
      UnprepAutoInnerException;
      raise;
    end;
  finally
    lRTTIContext.Free;
  end;
end;

initialization
  PrepAutoInnerException;
finalization
  UnprepAutoInnerException;
end.

En regardant ce code, je trouve qu'il pourrait utiliser une certaine modernisation, par exemple en utilisant des variables de classe au lieu de globals, et en utilisant des variables locales en ligne. L'unité entière est de retour de Delphi 6 jours et contient de nombreux $ ifdefs, et laissée de côté car elle surpasserait la réponse.

Je me demande toujours pourquoi le chaînage d'exceptions n'est pas la valeur par défaut dans delphi / rad studio comme c'est le cas dans d'autres langues. Très probablement parce que cela briserait le code existant d'une manière ou d'une autre.


1 commentaires

Merci beaucoup pour votre code! +1 Ce n'était pas difficile de comprendre les parties manquantes, alors je suis allé de l'avant et je les ai ajoutées moi-même à votre réponse, afin qu'elle soit compilée. Votre réponse m'a orienté vers une autre solution. Voyez ma réponse . J'espère qu'il y a quelque chose pour vous aussi !!



0
votes

Je réponds (également) à ma propre question car je vais adopter une autre approche. Il prévoit les exigences suivantes:

  • J'aime garder les déclarations de raise , comme elles l'étaient initialement,
    • il n'y aura donc pas de modifications de code nécessaires ici, et
    • ce qui signifie également qu'il n'y aura pas d'avertissements nouvellement introduits comme W1035 ou W1036 .
  • Je ne veux pas reconstruire la mécanique interne de RTL, cependant
  • Je veux interférer le moins possible avec la mécanique RTL.
  • Je veux être flexible dans le contrôle des exceptions de chaînage
    • parfois forcé ou par défaut, du côté de l'implémentation des exceptions, ainsi que
    • parfois par argument, du côté de l'utilisation des exceptions, pour étendre les fonctionnalités.

Dans ma solution:

  • J'accepte de percer la visibilité des champs d' Exception , FAcquireInnerException particulier.
  • Je compte sur RTTI pour vérifier l'alignement des champs (dans ExceptionFields , selon Exception ).

Ici, je propose une implémentation condensée à copier-coller:

Le constructeur d' EException présente l'utilisation d' ExceptionFields :

EDemoException: Level 1
EException: Level 2
EZeroDivide: Level 3

- à utiliser dans toute Exception dérivée d'une exception, et cela déclenchera le mécanisme RTL pour définir InnerException pendant qu'il déclenche l'exception. En outre, EException peut servir de racine commune pour les classes d'exceptions personnalisées, si vous le souhaitez. Certains constructeurs sont réintroduits pour être étendus avec const AcquireInnerException: Boolean = True , pour remettre le contrôle à l'appelant tout en fournissant une valeur par défaut pour le chaînage souhaité.

Exécutez ExceptionFields.VerifyFieldAlignments , si vous souhaitez vérifier les alignements de

  • les champs ("re") déclarés accessibles de l'extérieur dans ExceptionFields et
  • leurs homologues (privés) dans Exception .

S'il ne peut pas vérifier cela, il lèvera une exception. Il est exécuté dans le constructeur de classe d' EException . Déplacez-le comme vous le souhaitez, si vous n'utilisez pas EException , mais que vous souhaitez conserver la vérification.

Mise en œuvre (condensée):

program ExceptionsDemo;

{$AppType Console}

{$R *.res}

uses
  System.SysUtils,
  Exceptions in 'Exceptions.pas';

type
  EDemoException = class (EException)
  end;

begin
  try
    try
      try
        raise EZeroDivide.Create('Level 3');
      except
        raise EException.Create('Level 2', False);
      end;
    except
      raise EDemoException.Create('Level 1');
    end;
  except
    on E: Exception do
    begin
      WriteLn(E.ClassName, ': ', E.Message);
      while Assigned(E.InnerException) do
      begin
        E := E.InnerException;
        WriteLn(E.ClassName, ': ', E.Message);
      end;
    end;
  end;
  ReadLn;
end.

Et une démo:

unit Exceptions;

interface

uses
  System.SysUtils;

type
  EException = class (Exception)
  public
    class constructor Create;
    constructor Create(const Msg: String; const AcquireInnerException: Boolean = True);
    constructor CreateFmt(const Msg: String; const Args: array of const; const AcquireInnerException: Boolean = True); overload;
    constructor CreateRes(const Msg: PResStringRec; const AcquireInnerException: Boolean = True);
    constructor CreateResFmt(const Msg: PResStringRec; const Args: array of const; const AcquireInnerException: Boolean = True); overload;
  end;

type
  ExceptionFields = class (TObject)
  {$Hints Off} // H2219
  strict private
    FMessage: String;
    FHelpContext: Integer;
    FInnerException: Exception;
    FStackInfo: Pointer;
  {$Hints On}

  public
    FAcquireInnerException: Boolean;

  private
    class procedure VerifyFieldAlignments;
  end;

implementation

uses
  System.Generics.Collections,
  System.RTTI,
  System.TypInfo;

{ ExceptionFields }

class procedure ExceptionFields.VerifyFieldAlignments;

  procedure RaiseTypeNotFound(const ClassName: String);
  begin
    raise Exception.CreateFmt(
      'Typ nicht gefunden: %s',
      [ClassName]
    );
  end;

  procedure RaiseFieldNotFound(const ClassName, FieldName: String);
  begin
    raise Exception.CreateFmt(
      'Feld nicht gefunden: %s.%s',
      [ClassName, FieldName]
    );
  end;

  procedure RaiseFieldNotAligned(const LeftClassName: String; const LeftField: TPair<String, Integer>; const RightClassName: String; const RightField: TRTTIField);
  begin
    raise Exception.CreateFmt(
      'Feld nicht ausgerichtet: %s.%s+%d (tatsächlich) vs. %s.%s+%d (erwartet)',
      [
        LeftClassName,
        LeftField.Key,
        LeftField.Value,
        RightClassName,
        RightField.Name,
        RightField.Offset
      ]
    );
  end;

  type
    TMemberVisibilities = set of TMemberVisibility;

  function GetDeclaredFields(const RTTIContext: TRTTIContext; const &Class: TClass; const IncludedVisibilities: TMemberVisibilities = [mvPublic, mvPublished]): TArray<TPair<String, Integer>>;
  var
    RTTIType: TRTTIType;
    RTTIFields: TArray<TRTTIField>;
    Index: NativeInt;
    RTTIField: TRTTIField;
  begin
    RTTIType := RTTIContext.GetType(&Class);
    if not Assigned(RTTIType) then
      RaiseTypeNotFound(&Class.ClassName);
    RTTIFields := RTTIType.GetDeclaredFields;
    SetLength(Result, Length(RTTIFields));
    Index := 0;
    for RTTIField in RTTIFields do
      if RTTIField.Visibility in IncludedVisibilities then
      begin
        Result[Index] := TPair<String, Integer>.Create(
          RTTIField.Name,
          RTTIField.Offset
        );
        Inc(Index);
      end;
    SetLength(Result, Index);
  end;

const
  Left: TClass = ExceptionFields;
  Right: TClass = Exception;
var
  RTTIContext: TRTTIContext;
  DeclaredFields: TArray<TPair<String, Integer>>;
  RTTIType: TRTTIType;
  DeclaredField: TPair<String, Integer>;
  RTTIField: TRTTIField;
begin
  RTTIContext := TRTTIContext.Create;
  try
    DeclaredFields := GetDeclaredFields(RTTIContext, Left);
    RTTIType := RTTIContext.GetType(Right);
    if not Assigned(RTTIType) then
      RaiseTypeNotFound(Right.ClassName);
    for DeclaredField in DeclaredFields do
    begin
      RTTIField := RTTIType.GetField(DeclaredField.Key);
      if not Assigned(RTTIField) then
        RaiseFieldNotFound(Right.ClassName, DeclaredField.Key);
      if DeclaredField.Value <> RTTIField.Offset then
        RaiseFieldNotAligned(
          Left.ClassName, DeclaredField,
          RTTIType.Name, RTTIField
        );
    end;
  finally
    RTTIContext.Free;
  end;
end;

{ EException }

class constructor EException.Create;
begin
  inherited;
  ExceptionFields.VerifyFieldAlignments;
end;

constructor EException.Create(const Msg: String;
  const AcquireInnerException: Boolean);
begin
  inherited Create(Msg);
  ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;

constructor EException.CreateFmt(const Msg: String;
  const Args: array of const;
  const AcquireInnerException: Boolean);
begin
  inherited CreateFmt(Msg, Args);
  ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;

constructor EException.CreateRes(const Msg: PResStringRec;
  const AcquireInnerException: Boolean);
begin
  inherited CreateRes(Msg);
  ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;

constructor EException.CreateResFmt(const Msg: PResStringRec;
  const Args: array of const;
  const AcquireInnerException: Boolean);
begin
  inherited CreateResFmt(Msg, Args);
  ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;

end.

Sortie - la dernière ligne est uniquement là lors de l' raise EException.Create('Level 2', True) :

ExceptionFields(Self).FAcquireInnerException := True;

Merci à tous les répondants!


2 commentaires

C'est une solution intéressante pour contourner la solution RTTI. Je suis principalement allé pour le poids léger utilisant des pointeurs (et non TrttiField.SetValue) en raison des performances. De plus, comme vous, je saurai immédiatement si Emb décide de changer. Renommer FInnerException puisque RTTI ne pourra pas les trouver. Pensez-y: avez-vous envisagé d'utiliser une classe d'aide pour faire le truc du piratage?


Quoi qu'il en soit, assurez-vous de vérifier quality.embarcadero.com/browse/RSP-31679