2015-03-16 10 views
15

Nel seguente codice di esempio la chiamata a AssertTestObj() causa una violazione di accesso.Come verificare se un riferimento alla procedura è nulla?

Progetto InvokeTest2.exe sollevato classe di eccezione C0000005 $ con il messaggio 'violazione di accesso a 0x00000000: lettura di indirizzo 0x00000000'.

durante il debug posso vedere che il test Assigned(NotifyProc) in TSafeCall<T>.Invoke() non funziona come previsto - in modo che Invoke() cerca di eseguire NotifyProc che è nil e provoca la violazione di accesso in tal modo.

Tutte le idee perché questo non riesce e come risolverlo?

program InvokeTest2; 

{$APPTYPE CONSOLE} 

uses 
    System.SysUtils; 

type 
    TSafeCall<T> = class 
    public 
    type 
     TNotifyProc = reference to procedure (Item: T); 
    class procedure Invoke(NotifyProc: TNotifyProc; Item: T); overload; 
    end; 

    TOnObj = procedure (Value: String) of object; 

{ TSafeCall<T> } 

class procedure TSafeCall<T>.Invoke(NotifyProc: TNotifyProc; Item: T); 
begin 
    if Assigned(NotifyProc) then 
    NotifyProc(Item); 
end; 

procedure AssertTestObj(OnExceptionObj_: TOnObj; Value_: String); 
begin 
    TSafeCall<String>.Invoke(OnExceptionObj_, Value_); 
end; 

begin 
    try 
    TSafeCall<String>.Invoke(nil, 'works as expected'); 

    AssertTestObj(nil, 'this causes an access violation!'); 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end. 

risposta

18

Questo è un bug del compilatore. Ecco la mia riproduzione semplificata:

{$APPTYPE CONSOLE} 

type 
    TProc = reference to procedure; 
    TOnObject = procedure of object; 

procedure Invoke(Proc: TProc); 
begin 
    if Assigned(Proc) then 
    Proc(); 
end; 

procedure CallInvokeOnObject(OnObject: TOnObject); 
begin 
    Invoke(OnObject); 
end; 

begin 
    Invoke(nil); // succeeds 
    CallInvokeOnObject(nil); // results in AV 
end. 

Si potrebbe chiedere perché ho semplificato. Il tuo codice era una superba riproduzione del problema. Tuttavia, volevo renderlo il più semplice possibile in modo che potessi davvero essere sicuro che il problema fosse quello che ritengo fosse. Così ho rimosso i generici e le classi.

Ora, il test utilizzando Assigned è corretta. Hai ragione di aspettarti che si comporti come tu intendi. Il problema è che quando il compilatore genera il codice per chiamare Invoke da CallInvokeOnObject, è necessario racchiudere il metodo dell'oggetto in un'interfaccia della procedura di riferimento. Per fare ciò correttamente è necessario verificare se il metodo dell'oggetto è assegnato o meno. In caso contrario, non è necessario creare alcuna interfaccia wrapper e passare nil.

Il compilatore non riesce a farlo. Incapsula incondizionatamente il metodo dell'oggetto in un'interfaccia della procedura di riferimento. Puoi vedere questo nel codice emesso per CallInvokeOnObject.

 
Project1.dpr.16: begin // this is the beginning of CallInvokeOnObject 
004064D8 55    push ebp 
004064D9 8BEC    mov ebp,esp 
004064DB 6A00    push $00 
004064DD 53    push ebx 
004064DE 33C0    xor eax,eax 
004064E0 55    push ebp 
004064E1 683B654000  push $0040653b 
004064E6 64FF30   push dword ptr fs:[eax] 
004064E9 648920   mov fs:[eax],esp 
004064EC B201    mov dl,$01 
004064EE A1F4634000  mov eax,[$004063f4] 
004064F3 E8DCDAFFFF  call TObject.Create 
004064F8 8BD8    mov ebx,eax 
004064FA 8D45FC   lea eax,[ebp-$04] 
004064FD 8BD3    mov edx,ebx 
004064FF 85D2    test edx,edx 
00406501 7403    jz $00406506 
00406503 83EAF8   sub edx,-$08 
00406506 E881F2FFFF  call @IntfCopy 
0040650B 8B4508   mov eax,[ebp+$08] 
0040650E 894310   mov [ebx+$10],eax 
00406511 8B450C   mov eax,[ebp+$0c] 
00406514 894314   mov [ebx+$14],eax 
Project18.dpr.17: Invoke(OnObject); 
00406517 8BC3    mov eax,ebx 
00406519 85C0    test eax,eax 
0040651B 7403    jz $00406520 
0040651D 83E8E8   sub eax,-$18 
00406520 E8DFFDFFFF  call Invoke 

quella chiamata TObject.Create è quello che avvolge il metodo di oggetto in un'interfaccia procedura di riferimento. Si noti che l'interfaccia viene creata in modo incondizionato e quindi passata a Invoke.

Non c'è modo per lavorare intorno a questo dall'interno Invoke. Nel momento in cui il codice arriva lì è troppo tardi. Non è possibile rilevare che il metodo non è stato assegnato. Questo dovrebbe essere segnalato a Embarcadero come un bug.

tua unica soluzione praticabile è quella di aggiungere un controllo supplementare assegnato in CallInvokeOnObject.

+2

segnalato come [RSP-10204] (https://quality.embarcadero.com/browse/RSP-10204) –

+3

@Martin È possibile modificare utilmente la versione per l'aggiornamento 1 XE7, con numero di versione 21.0.17707.5020. Questa è l'ultima, e dove ho fatto i miei test. –

Problemi correlati