Contracts si Removed Code

O parte din voi stiti ca libraria Code Contracts foloseste IL code rewriting pentru a implementa contract programming. Aceasta solutie arhitecturala, pe de o parte, este foarte logica deoarece iti permite sa folosesti aceasta librarie cu orice limbaj de programare din platforma .Net. Pe de alta parte insa, cauzeaza destul de multe probleme deoarece ccrewrite trebuie sa faca un reverse engineer al codului IL in constructs high-level si sa le rescrie intr-un mod diferit.

Mar 18, 2016 1991
O parte din voi stiti ca libraria Code Contracts foloseste IL code rewriting pentru a implementa contract programming. Aceasta solutie arhitecturala, pe de o parte, este foarte logica deoarece iti permite sa folosesti aceasta librarie cu orice limbaj de programare din platforma .Net. Pe de alta parte insa, cauzeaza destul de multe probleme deoarece ccrewrite trebuie sa faca un reverse engineer al codului IL in constructs high-level si sa le rescrie intr-un mod diferit.

Astazi vreau sa analizam una din consecintele lucrului la nivel IL, care poate duce la rezultate neasteptate, si sa vedem ce solutii putem gasi.

Hai sa ne uitam la codul de mai jos:

Contracts and Removed Code_1.jpg

Acest exemplu arata una dintre erorile de programare tipice, atunci cand o actiune cu un efect secundar este efectuata in ceea ce am numi conditionally compiled function. In acest caz testam o ipoteza care trebuie sa fie adevarata dar comportamentul aplicatiei va depinde de prezenta/absenta unei directive DEBUG. Acum, sa presupunem ca in loc clasa Debug folosim contracte (contracts). Dar in loc sa apelam hashSet.Add direct din metoda Contract.Assert, am alocat o vairabila locala:

Contracts and Removed Code_2.jpg

Este acest comportament unul corect? Contractele nu folosesc compilari conditionate pentru indepartarea preconditiilor/postconditiilor si assetions. Mai precis, compilarea conditionata este prezenta si fara simbolul CONTRACT_FULL, toate mentiunile legate de contracte vor fi sterse. Dar o configuratie mai specifica este efectuata in executare. CCREWRITE lasa preconditiile/postconditiile sau assertions in functie de setari.

Mai mult, apelam metoda Add in afara metodei Assert astfel incat si daca rulam acest cod intr-un mod unde assertions sunt dezactivate, hashSet trebuie sa contina valoarea 42. Ar trebui? S-ar putea sa o contina.

Raspunsul depinde de modul in care C# compiler se comporta si daca vom avea inline pentru variabila b. Compilerul poate decide ca o variabila temporara nu este foarte necesara si sa converteasca acest cod in ceea ce vedem mai jos:

Contracts and Removed Code_3.jpg

In acest caz, daca in proprietatile Code Contracts va fi specificat sa nu se includa assertions (spre exemplu, sunt selectate Preconditii sau PreandPost), atunci ccrewrite va elimina metoda Contract.Assert impreuna cu codul apelat hashSet.Add.

Si in acest punct ajungem la o concluzie trista: arhitectura originala ccrewrite este foarte fragila si dependenta de comportamentul compiler. Chiar daca utilizatorul a alocat o variabila locala, dar a folosit doar Assert/Assume, comportamentul aplicatiei poate sa varieze daca variabila a fost inlined sau nu.

VS2015 are o optimizare mai agresiva fata de VS2013. Astfel ca avem cod care functiona foarte bine in VS2013 si nu mai functioneaza in VS2015!

Ceea ce este interesant este ca ccrewrite nu elimina de fiecare data assertions. Daca o assertion (Assert/Assume) contine o conditie mai complexa (spre exemplu, && sau ||), ccrewrite va elimina apelarea metodei Contract.Assert/Assume, dar va lasa calculul argumentului in stack (care este de fapt un bug).

Spre exemplu, urmatorul cod va functiona corect chiar si in VS2015, si apelarea metodei hashSet.Add va duce la codul de mai jos:

Contracts and Removed Code_4.jpg

Ce putem face?

Cel mai dificil lucru in aceasta situatie, daca este posibil, este sa incercam sa obtinem un comportament corect in cadrul arhitecturii existente ccrewrite.
Sunt doua optiuni:

1) Ccrewrite elimina doar apelarile metodelor Contract.Assert, dar pastreaza calculul primului argument in stack.

PRO: comportamentul va fi stabil si nu va depinde de optimizarile compiler.
CONS:
  • Fara compilari conditionale nu vom putea sa eliminam apelarile metodelor precum Contract.Assert
  • Eficienta va suferi
2)  Ccrewrite elimina apelarile Contract.Assert cu toate argumentele.

PRO: Acesta este comportamentul curent si este in linie cu comportamentul Debug.Assert
CONS:
  • Comportamentul programului variaza in functie de optimizarea compilerului, aspect care este complet neclar pentru utilizator
  • Corectitudinea programului va avea de suferit
Tind sa cred ca acest comportament ar trebui schimbat, in special la tranzitia catre VS2015, astfel incat toate expresiile Code Contracts, prezente in metodele Contract.Assert/Assume, sa ramana chiar si cand apelarile vor fi eliminate.

Sergey Teplyakov
Expert in .Net, ++ and Application Architecture

Daca iti place acest articol, distribuie-l si prietenilor tai!




Mai ai intrebari?
Contacteaza-ne.
Thank you.
Your request has been received.