• Nenhum resultado encontrado

Algoritmo de inferˆencia de anotac¸˜oes

A figura 4.2 apresenta o pseudo-c´odigo do nosso algoritmo. Este efetua duas passagens sobre o c´odigo fonte. Na primeira passagem associa duas novas vari´aveisLi1eLi2a cada trinco li declarado e gera restric¸˜oes que relacionam Li1, Li2 e li (1.1). Estas vari´aveis s˜ao definidas sobre conjuntos de trincos e denotam os trincos a apanhar antes (Li1) e de- pois (Li2) do trinco em quest˜ao. As restric¸˜oes geradas capturam estes factos: o trinco li

´e apanhado ap´os todos os trincos em Li1 e antes de qualquer trinco em Li2. Estas trˆes restric¸˜oes,Li1 < li, li < Li2 eLi1 < Li2, s˜ao escritas abreviadamente comoLi1 < li < Li2. Adicionalmente, requeremos que os trincos deLi1e deLi2correspondam a trincos que es- tejam no ˆambito do bloco ou da instruc¸˜ao em causa. Caso contr´ario, a soluc¸˜ao encontrada pode ser inv´alida por mencionar trincos declarados noutro ponto do programa. No caso de um trinco li conter anotac¸˜oes (1.2), essas anotac¸˜oes s˜ao colecionadas nesta fase do algoritmo atrav´es de restric¸˜oes. Neste caso, as restric¸˜oes adicionadas registam o valor que as vari´aveis de conjuntos de li devem assumir, ou seja,Li1 = MieLi2 = Ni, em queLi1eLi2

s˜ao as vari´aveis associadas ao trinco li, eMie Nicorrespondem aos conjuntos definidos na anotac¸˜ao do trinco li.

A primeira passagem no exemplo da figura 4.1, enumera os conjuntos sequencial- mente (comec¸ando emL0), resulta que na linha 3 s˜ao associadas ag1as vari´aveisL0eL1

e as restric¸˜oesL0 < g1 < L1,L0⊆ ∅ eL1⊆ ∅. Na linha 14, por exemplo, s˜ao associadas ao

// Primeira passagem

1. Analisar cada bloco do programa:

1.1 para cada trinco li definido na assinatura de um bloco de c´odigo (da forma ∀li ::Lock.t), no corpo de um bloco (da forma li ::Lock), ou numa instruc¸˜ao unpack (da forma li , r:=unpack v), associar-lhe duas novas vari´aveis sobre conjuntos de trincos Li1 e Li2 e incluir as seguintes restric¸˜oes: Li1 < li < Li2, Li1 ⊆ Trincos,

Li2 ⊆ Trincos, em que Trincos representa o conjunto de trincos conhecidos no ˆambito do bloco ou da instruc¸˜ao em causa.

1.2 Se existirem anotac¸˜oes em trincos (da forma li ::Lock(Mi1,Mi2), Mi1

e Mi2 s˜ao conjuntos concretos que contˆem trincos do programa, podendo estes serem vazios), ent˜ao gerar as restric¸˜oes: Li1 = Mi1

e Li2 = Mi2, em que Li1 e Li2 est˜ao associados ao trinco li. // Segunda passagem

2. Analisar cada bloco do programa:

2.1 Se a instruc¸˜ao ´e um salto condicional sobre o trinco li (da forma

if r==0 jump v) com v do tipo {...} requires G, ent˜ao gerar a restric¸˜ao: G < li.

2.2 Se a instruc¸˜ao inclui a aplicac¸˜ao de um trinco li a um valor v

(da forma v[ li ]), em que o tipo de v ´e ∀m::Lock.t, e m est´a associado aos conjuntos Li1 e Li2, ent˜ao gerar as restric¸˜oes:

Li1 < li < Li2.

2.3 Se a instruc¸˜ao ´e um salto (da forma jump v) ou o lanc¸amento de um fio de execuc¸˜ao (da forma fork v), com v do tipo

{r1: t1 ,..., rn: tn} requires G, comparar os tipos ti que s˜ao da forma ∀li ::Lock.ui, com os tipos dos registos correspondentes conhecidos at´e ao momento (da forma ∀mi ::Lock.si). Se li est´a associado aos conjuntos Li1 e Li2 e mi aos conjuntos Mi1 e Mi2, ent˜ao gerar as restric¸˜oes: Li1 = Mi1, Li2 = Mi2.

3. Resolver as restric¸˜oes recolhidas usando um SMT

Figura 4.2: Algoritmo para colecionar restric¸˜oes

pois o trinco e encontra-se em ˆambito. Estes dois exemplos s˜ao referentes `a al´ınea 1.1 do algoritmo. Como o exemplo da figura 4.1 est´a parcialmente anotado, a regra 1.2 pode ser aplicada diversas vezes no mesmo. Por exemplo, na linha 5 (g3::Lock({g1,g2},{})) s˜ao adicionadas as restric¸˜oesL4 = {g1, g2}eL5 = {}, em queL4eL5s˜ao as vari´aveis associa- das ao trincog3. A tabela 4.1 apresenta todas as restric¸˜oes geradas na primeira passagem do algoritmo.

Na segunda passagem o algoritmo processa cada bloco de c´odigo e comporta-se de trˆes formas distintas consoante se trate de um teste (2.1), da instanciac¸˜ao de um valor polim´orfico (2.2) ou de uma instruc¸˜ao de salto ou de lanc¸amento de um fio de execuc¸˜ao (2.3). Em relac¸˜ao a (2.1), a recolha de restric¸˜oes ocorre na instruc¸˜ao if. Neste ponto do programa ficamos com a indicac¸˜ao de que os trincos j´a fechados tˆem de ser menores do que o trinco que est´a a tentar fechar-se. Esta regra pode ser aplicada em duas situac¸˜oes no

Linha Restric¸˜oes Al´ınea 3 L0 < g1 < L1,L0⊆ ∅ eL1⊆ ∅ 1.1 4 L2 < g2 < L3,L2⊆ {g1} eL3⊆ {g1} 1.1 L2 = {g1}eL3 = {} 1.2 5 L4 < g3 < L5,L4⊆ {g1, g2} eL5⊆ {g1, g2} 1.1 L4 = {g1, g2}eL5 = {} 1.2 14, 15 L6 < e < L7,L6⊆ ∅,L7⊆ ∅,L8 < d < L9,L8⊆ {e},L9⊆ {e}, 1.1 L10 < l < L11,L10⊆ {e, d} eL11⊆ {e, d} L6 = {},L7 = {},L10 = {e}eL11 = {} 1.2 22 L12 < e1 < L13,L12⊆ ∅,L13⊆ ∅,L14 < d1 < L15,L14⊆ {e1}, e 1.1 L15⊆ {e1} L12 = {},L13 = {},L14 = {e1}eL15 = {} 1.2 29 L16 < e2 < L17,L16⊆ ∅,L17⊆ ∅,L18 < d2 < L19,L18⊆ {e2}, e 1.1 L19⊆ {e2} L16 = {},L17 = {},L18 = {}eL19 = {} 1.2

Tabela 4.1: Restric¸˜oes geradas durante a primeira passagem do algoritmo

exemplo da figura 4.1. Na linha 18 pretende-se obter o trincoesem que tenha sido fechado qualquer outro trinco anteriormente. Na linha 25 pretende-se obter o trinco d1tendo j´a o trincoe1fechado (cf. requires {e1}na assinatura do bloco de c´odigo na linha 22). As restric¸˜oes adicionadas s˜ao, respetivamente,{} < ee{e1} < d1.

Em relac¸˜ao `a aplicac¸˜ao de valores (2.2) a restric¸˜ao adicionada regista o facto do trinco li respeitar a ordem do fecho dos trincos associado am, ou seja, ser fechado depois dos trincos denotados porLi1e antes dos deLi2. Esta regra pode ser aplicada por diversas vezes no exemplo que apresentamos. Por exemplo, na linha 6 (r6:=levantarGarfoDireito [g1]) s˜ao adicionadas as restric¸˜oesL12 < g1 < L13, em queL12eL13s˜ao as vari´aveis associadas ao trinco polim´orficoe1declarado na linha 21. A tabela 4.2 cont´em todas as restric¸˜oes geradas de acordo com esta regra, assinaladas por (2.2).

O tratamento das instruc¸˜oes jumpefork (2.3) ´e mais complicado porque h´a que de- terminar o tipo dos registos antes da execuc¸˜ao destas instruc¸˜oes, que ´e calculado pelo sistema de tipos. Vamos ilustrar a aplicac¸˜ao desta regra `a linha 7. O tipo dos registos relevantes para o lanc¸amento do fio de execuc¸˜ao s˜ao:

• r1:lock g1; • r2:lock g2;

• r6:∀ d1::Lock.{r1:lock g1, r2: lock d1} requires {g1}.

Tanto o registo r1 como o registo r2 n˜ao s˜ao do tipo universal, ou seja, n˜ao contˆem declarac¸˜oes de trincos polim´orficos. Neste caso, o que ´e relevante nas instruc¸˜oes fork

ejump ´e se os tipos dos registos, antes da execuc¸˜ao destas instruc¸˜oes, s˜ao subtipos dos tipos dos registos para onde ´e efetuado o salto ou o lanc¸amento do novo fio de execuc¸˜ao.

Linha Restric¸˜oes Al´ınea 6, 8, 10 L12 < g1 < L13,L12 < g2 < L13eL12 < g3 < L13 2.2 7 L6 < g1 < L7,L8[g1/e] < g2 < L9[g1/e], 2.2 L10[g1/e][g2/d] = L14[g1/e1] e L11[g1/e][g2/d] = L15[g1/e1] 2.3 9 L6 < g2 < L7,L8[g2/e] < g3 < L9[g2/e], 2.2 L10[g2/e][g3/d] = L14[g2/e1] e L11[g2/e][g3/d] = L15[g2/e1] 2.3 11 L6 < g3 < L7,L8[g3/e] < g1 < L9[g3/e], 2.2 L10[g3/e][g1/d] = L14[g3/e1] e L11[g3/e][g1/d] = L15[g3/e1] 2.3 18 L10 < d < L11e{} < e 2.2, 2.1 19 L6 < e < L7,L8[e/e] < d < L9[e/e], 2.2

L10[e/e][d/d] = L10[e/e]eL11[e/e][d/d] = L11[e/e] 2.3 25 L16 < e1 < L17, L18[e1/e2] < d1 < L19[e1/e2] e {e1} < d1 2.2, 2.1 26 L12 < e1 < L13eL14[e1/e1] < d1 < L15[e1/e1] 2.2 32 L12 < e2 < L13 2.2 33 L6 < e2 < L7,L8[e2/e] < d2 < L9[e2/e], 2.2 L10[e2/e][d2/d] = L14[e2/e1] e L11[e2/e][d2/d] = L15[e2/e1] 2.3

Tabela 4.2: Restric¸˜oes geradas durante a segunda passagem do algoritmo

Todavia, o tipo que considera as vari´aveis de conjuntos ´e o tipo universal. Um tipo univer- sal (∀ l1 :: Lock(L0, L1).t) ´e subtipo de outro (∀ m1::Lock(M0, M1).t) quando as vari´aveis de conjuntos menor (L0) e maior (L1) do trinco (l1) s˜ao iguais `as vari´aveis de conjuntos me- nor (M0) e maior (M1) do trinco (m1). Neste caso, o ´unico registo que ´e do tipo universal ´e or6. Com isto, o tipo do registor6delevantarGarfoEsquerdo, ap´os a instanciac¸˜ao porg1e

g2´er6:∀ l ::Lock.{r1:lock g1, r2: lock l } requires {g1}h´a que gerar as restric¸˜oesL10[g1/e] [g2/d]=L14[g1/e1]eL11[g1/e][g2/d]=L15[g1/e1], em queL10eL11s˜ao as vari´aveis associadas ao trinco polim´orfico l do blocolevantarGarfoEsquerdo e L14e L15 s˜ao as associadas ao trinco polim´orficod1do bloco levantarGarfoDireito.

Por ´ultimo, (3) as restric¸˜oes recolhidas s˜ao passadas a um SMT que afere da sua consistˆencia. Caso sejam consistentes, o SMT indica um modelo que instancia cada um dos conjuntos associados aos trincos, obtendo deste modo uma anotac¸˜ao v´alida; caso contr´ario, o SMT responde negativamente e o programa n˜ao tem qualquer anotac¸˜ao poss´ıvel. No caso de programas completamente anotados, o SMT n˜ao vai procurar obter conjuntos para as vari´aveis de conjuntos mas, no caso de programas em que n˜ao exis- tam anotac¸˜oes, o SMT vai procurar obter conjuntos para as vari´aveis de conjuntos. Ou- tra situac¸˜ao que pode ocorrer ´e ter um programa parcialmente anotado (exemplo da fi- gura 4.1) e, neste caso, o SMT vai procurar obter conjuntos para as vari´aveis de conjuntos cujo os trincos n˜ao tenham anotac¸˜oes. No caso de os trincos estarem anotados, o SMT

n˜ao vai procurar obter conjuntos para as vari´aveis de conjuntos desses trincos.

Um resolvedor SMT consegue determinar os conjuntos (Li), porque os programas MIL declaram um conjunto finito de trincos que pode ser determinado em tempo de compilac¸˜ao. Portanto, os conjuntos (Li) que pretendemos determinar s˜ao finitos e defi- nidos sobre um conjunto de trincos tamb´em finito.

Implementac¸˜ao da M´aquina Virtual

Neste cap´ıtulo damos conta das tecnologias sobre as quais o analisador semˆantico est´atico e a m´aquina virtual do MIL s˜ao constru´ıdos (secc¸˜ao 5.1), apresentamos a sua arqui- tetura geral e funcionamento (secc¸˜ao 5.2), distinguindo entre as fases de an´alise e de interpretac¸˜ao (secc¸˜ao 5.3 e secc¸˜ao 5.4).

5.1

Tecnologias utilizadas para a construc¸˜ao da m´aquina

virtual do MIL

A m´aquina virtual MIL aceita como entrada um ficheiro . mil com o c´odigo fonte. O conte´udo do ficheiro ´e analisado de acordo com a especificac¸˜ao da gram´atica MIL, ´e feita a an´alise semˆantica est´atica e ap´os a an´alise, a m´aquina virtual interpreta o fi- cheiro. A m´aquina virtual MIL ´e implementada em SableCC e Java. O SableCC ´e usado para gerar as classes Java que procedem `a verificac¸˜ao l´exica e sint´atica, enquanto que a an´alise semˆantica e interpretac¸˜ao ´e programada em Java. O resolvedor SMT usado para a verificac¸˜ao das restric¸˜oes ´e o Z3.

5.1.1

SableCC

O SableCC [16] ´e uma ferramenta que gera um analisador l´exico e sint´atico em Java a partir de um ficheiro que cont´em a especificac¸˜ao da gram´atica, simplificando a escrita de compiladores e interpretadores, pois gera um conjunto de classes Java, as quais contˆem os analisadores l´exicos e sint´aticos. O resultado da an´alise sint´atica ´e uma ´arvore sint´atica abstrata (AST) que representa o programa.

O SableCC oferece as seguintes funcionalidades: an´alise l´exica e sint´atica do pro- grama de entrada; construc¸˜ao autom´atica de uma AST do programa fonte (a ser com- pilado) e criac¸˜ao de visitantes para percorrer a AST, de acordo com o padr˜ao de dese- nho visitante apresentado em GoF [18]. Este padr˜ao separa as estruturas de dados da implementac¸˜ao, ou seja, permite fazer operac¸˜oes sobre a estrutura. Atrav´es do uso deste

padr˜ao ´e poss´ıvel adicionar visitantes, para implementar as v´arias fases do analisador semˆantico e do interpretador.

5.1.2

Z3

O Z3 [13] ´e um resolvedor SMT (Satisfiability Modulo Theories) eficiente, sendo o estado de arte dos provadores de teoremas, desenvolvido pela Microsoft Research. Este resolve- dor permite verificar a consistˆencia de f´ormulas l´ogicas sobre uma ou v´arias teorias. As teorias que suporta s˜ao as seguintes: aritm´etica de n´umeros inteiros e reais, vetores de bits de tamanho fixo, matrizes, func¸˜oes n˜ao interpretadas e quantificadores.

O Z3 aceita como entrada um ficheiro que cont´em uma sequˆencia de comandos. ´E atrav´es destes comandos que o Z3 determina se as f´ormulas l´ogicas introduzidas s˜ao sa- tisfaz´ıveis. Internamente, o Z3 mant´em uma pilha que armazena as f´ormulas e declarac¸˜oes de cada programa.

Para verificar a consistˆencia das f´ormulas s˜ao usados dois comandos: o assert e o

check−sat. O primeiro acrescenta uma f´ormula `a pilha interna do Z3. O segundo deter- mina se as f´ormulas que se encontram na pilha s˜ao consistentes. Em caso afirmativo o Z3, devolve sat; caso contr´ario, devolve unsat. Um conjunto de f´ormulas ´e consistente se existe uma interpretac¸˜ao que faz com que todas as f´ormulas sejam verdadeiras [17]. No caso de um conjunto de f´ormulas ser consistente, o Z3 tem a capacidade de produzir o modelo que satisfaz as f´ormulas.

A teoria usada para inferir as anotac¸˜oes definidas no cap´ıtulo 4 foi a de vetores de bits. Atrav´es dos vetores de bits representamos os trincos e as vari´aveis de conjuntos. Os trincos s˜ao conjuntos singulares e, ´e atribu´ıdo um bit diferente para representar cada trinco do programa. As vari´aveis de conjuntos s˜ao expressas como conjuntos de trincos, isto ´e, como cada trinco ´e representado por um bit diferente, facilmente determinamos quais os trincos que o conjunto cont´em. Assim, a partir desta teoria, ´e poss´ıvel encontrar conjuntos de bits que satisfac¸am as restric¸˜oes passadas ao Z3.

Documentos relacionados