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?
3 Réponses :
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;
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;
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.
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 !!
Je réponds (également) à ma propre question car je vais adopter une autre approche. Il prévoit les exigences suivantes:
raise
, comme elles l'étaient initialement,Dans ma solution:
Exception
, FAcquireInnerException
particulier.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
ExceptionFields
etException
. 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!
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
J'ai souvent une sous-
procedure Inv; begin raise EFrogProperties.Create('Invalid frog properties.'); end;
locale comme laprocedure 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 uneException
existante ou acquiert l'Exception
actuelle, ou au moins une méthodeCreateOuterException()
qui retourne une nouvelleException
qui peut êtreraise
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 laprotected 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é avecException
. OuFAcquireInnerException
la visibilité deFAcquireInnerException
? Souhaitez-vous partager le code de votreEChainedException
?