Manipulação de Bytecodes Java Giuliano Mega 1 Introdução • O que é bytecode? • Código intermediário • Semelhante aos códigos de montagem (assembly) • Byte-code – opcodes de 1 byte • Processadores virtuais (Virtual Machines) • Quem usa? • Java (JVM), Smalltalk (Slang), .NET (CLR) 2 Introdução • Assuntos em pauta • Por que? • Reflexão • Como? • JVM • Bytecodes • Técnicas de compilação • Arcabouços • Suposição • Você conhece Java • Você sabe o que é um Class Loader 3 Reflexão Computacional • Sistemas computacionais • Sistema de computador que responde perguntas ou dá apoio a certas ações sobre algum domínio • Sistema representa o seu domínio por meio de estruturas de dados • Representação causalmente conexa 4 Reflexão Computacional (cont.) • Meta-sistema • Domínio = outro sistema. • Sistema-objeto. • Sistema reflexivo • Meta-sistema causalmente sistema-objeto é ele próprio. conexo cujo • Conseqüência • Pode modificar aspectos de sua execução como efeito dela mesma. 5 Arquiteturas Reflexivas • Linguagem dá acesso a estruturas que modificam aspectos da interpretação da linguagem. • Interpretadores metacirculares e variantes • Forma muito explorada de implementação; • poderosa e garantida; • conexão causal garantida pela circularidade. programa interpretador 6 Arquiteturas Reflexivas (cont.) • Reflexão [1] • Ad hoc • Facilidades providas pela linguagem • Arquitetura reflexiva: • Reconhece reflexão como conceito; • provê meios para fazê-la. • Finalidade aberta (open-ended). • Alguns exemplos: • 3-LISP,BROWN (funcionais) • META-PROLOG, FOL (lógicas) • TEIRESIAS, SOAR (regras) • 3-KRS, SuperEgo (OO) • Prá que que serve afinal? 7 Reflexão - aplicações • Aplicações • depuração [1]; • prog. concorrente [2]; • backtracking [3]; • futures [4]; • AOP [5]; • etc. • Tudo isso no nível da linguagem • Sem extensões sinistras ao interpretador 8 Reflexão Java • Afinal, Java é “reflexiva”? • Não conta com uma arquitetura reflexiva. • Segunda categoria • Ausência de meta-nível claro • Introspecção confusa • Limitada • Class loaders • Proxies dinâmicos (JDK 1.3) • Justificativa: desempenho, type-safety [6]. 9 Reflexão Java (cont.) • Programadores Java também precisam de reflexão • logging, estatísticas, tracing, depuração, persistência, AOP... • Reflexão comportamental/estrutural • Proxies dinâmicos • Factories • Não-ortogonais • Aplicações já prontas – difícil. 10 Reflexão Java (cont.) • “Reflexão” em tempo de compilação (Open Java [7] e cia.) • Não é reflexão, é metaprogramação. • Reflexão em tempo de carga. • • • • Class loaders Portável entre JVMs Dizem que é reflexão estrutural Opinião pessoal: forçada de barra • Formas desajeitadas e não-portáveis de reflexão comportamental/estrutural pela JVMTI (Sun). • Java e adaptação não combinam. • Flexibilidade => adaptação [8]. 11 Manipulação em Tempo de Carga • Forma mais comum e aceita • Portável • Não requer código fonte • “Fácil” e “eficiente” class SRLoader extends ClassLoader { public Class loadClass(String name) { byte[] bytecode = readClassFile(name); <mexa no bytecode o quanto quiser> return resolveClass(defineClass(bytecode)); } } 12 JVM – formato das classes Formato de classes Java [9] 13 JVM – básico • JVM é um processador virtual • pilhas, “registradores” • Preocupação com segurança • verificação • opcodes tipados (tipos primitivos) 14 JVM - A linguagem de montagem • Um pequeno exemplo: • Estáticos: • Tamanho máximo da pilha • Número de variáveis locais (slots) 15 Manipulação Revisitada • Manipular classes: • Complexo • Muitas dependências • Classe = array de bytes • Necessário interpretar os dados • Arcabouços (BCEL e muitos outros): 16 Arcabouços de Manipulação • Forma OO de representar/manipular o bytecode • Variam em grau de abstração, tamanho, desempenho... • ByteCode Engineering Library* (BCEL) [10] • ASM [11] • Java Assistant* (Javassist) [12] • Code Generation LIBrary (CGLIB) • SERP • JOIE • etc. 17 O Java Assistant (Javassist) • Nosso foco: manipulação de alto nível • Intuitivo • Fácil de usar • Dificilmente produz código inválido • Exemplo: • Medir desempenho (interceptadores): private String buildString(int length) { String result = ""; for (int i = 0; i < length; i++){ result += (char)(i%26 + 'a'); } return result; } 18 Javassist (cont.) • Desempenho private String buildString(int length) { long start = System.currentTimeMillis(); String result = ""; for (int i = 0; i < length; i++) { result += (char)(i%26 + 'a'); } System.out.println("Call to buildString took " + (System.currentTimeMillis()-start) + " ms."); return result; } • Class Loader • O Javassist já provê um esqueleto • javassist.Loader extends java.lang.ClassLoader • Você só escreve os ganchos • interface javassist.Translator 19 Javassist (cont.) • Translator public interface Translator { public void start(ClassPool pool) throws NotFoundException, CannotCompileException; public void onLoad(ClassPool pool, String classname) throws NotFoundException, CannotCompileException; } • Restrições • variáveis locais (lembre-se do bytecode...) • como faço para implementar o exemplo? • “expressão idiomática Javassist” prá isso 20 Javassist (cont.) CtClass clas = pool.get(cname); CtMethod[] meths = clas.getDeclaredMethods(); for (int i = 0; i < meths.length; i++) { CtMethod mold = methds[i]; String nname = mname+"$impl"; /* 1) Muda o nome do método e duplica com o nome antigo */ mold.setName(nname); CtMethod mnew = CtNewMethod.copy(mold, mname, clas, null); /* 2) Cria o embrulho-cronômetro para o método original */ String type = mold.getReturnType().getName(); StringBuffer body = new StringBuffer(); body.append("{\nlong start = System.currentTimeMillis();\n"); if (!"void".equals(type)) { body.append(type + " result = "); } body.append(nname + "($$);\n"); ... /* 3) Substitui o código do método embrulhado */ mnew.setBody(body.toString()); clas.addMethod(mnew); } 21 Javassist (cont.) • A técnica envolve ainda um “lançador” • Em geral provido pelo arcabouço • Limitações • • • • • Não é muito flexível Classes internas e anônimas Código original vai parar em outro método Lento Modificações internas • Introspecção limitada • Proposta não é essa • Tem uma API para bytecodes. 22 A ByteCode Engineering Library (BCEL) • Transforma a classe Java num grafo • ByteCodes orientados a objeto • Poderoso • Mais “intuitivo” que o Javassist • Prá quem conhece bytecodes • Mais rápido que o Javassist • Exemplo: • Instrumentar todos os métodos Run de todos os Runnables 23 BCEL (cont.) public JavaClass modifyClass(JavaClass jc) { /* 1) Testa se a classe é instância de Runnable */ JavaClass runnableIntf = Repository.lookupClass("java.lang.Runnable"); if(!jc.instanceOf(runnableIntf)) return jc; Method [] methods = jc.getMethods(); int i = 0; for(i = 0; i < methods.length; i++){ if(methods[i].getName().equals("run")) break; } /* 2) Cria as versões maleáveis (de-serializa) e as modifica */ ClassGen newClass = new ClassGen(jc); ConstantPoolGen cg = newClass.getConstantPool(); MethodGen mg = new MethodGen(m, newClass.getClassName(), cg); newClass.removeMethod(methods[i]); ... /* 3) Calcula dados estáticos, re-serializa o bytecode */ mg.setMaxStack(); mg.setMaxLocals(); newClass.addMethod(mg.getMethod()); return newClass.getJavaClass(); } 24 BCEL (cont.) InstructionList il = new InstructionList(); InstructionFactory iFactory = new InstructionFactory(nc); il.append(iFactory.createFieldAccess("java.lang.System", "out", new ObjectType("java.io.PrintStream"), Constants.GETSTATIC)); il.append(new PUSH(cg, "Método \"run\" chamado na classe " + jc.getName()); il.append(iFactory.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL)); InstructionList old_il = mg.getInstructionList(); InstructionHandle [] handles = old_il.getInstructionHandles(); old_il.insert(handles[0], il); mg.setInstructionList(old_il); 25 BCEL (cont.) • Problemas • • • • Ainda é lento; pode ser muito complexo; manipulação direta de bytecodes; muito fácil gerar código inválido. 26 ASM • Visitor pattern • Não seria/de-seria o grafo • Composição de visitors • Substituição • Muito rápido • 700% mais rápido que o BCEL • 1100% mais rápido que o SERP • Compacto • 25 kb vs. 350 kb do BCEL • Lembra um pull parser prá XML. • Não alivia muito em manipulações complexas. 27 Verificação de Bytecode • O que há de errado com este (pseudo) bytecode? 00 aload_0 01 invokevirtual “MyClass#test()” 02 ifne 05 03 aload_0 04 invokevirtual “MyClass#test()” 05 return Rode isso e...: java.lang.VerifyError: stack sizes differ (1 != 0). 28 Verificação de Bytecode (cont.) • Esse código não passa pelo verificador (pág. 12) • Verificador • Algoritmo de análise de fluxo • Prova um teorema ou arruma um contraexemplo • Problemas de decidibildade • Restrições mais fortes do que o necessário. • Rejeita código “válido”. 29 Verificação de Bytecode (cont.) 00 aload_0 01 invokevirtual “MyClass.test()” 02 ifne 05 03 aload_0 04 invokevirtual “MyClass.test()” 05 return • Análise de tipos + stack merging = código seguro + chatice. • Merging em laços. 30 Verificação de Bytecode (cont.) • Compilador “burla” o verificador • Sempre produz código válido; • equivalente ao código-fonte; • nós não temos o código-fonte. • Transformações difíceis • Armadilhas (traps); • implementadas com subrotinas. 31 Problemas Adicionais • Class Loader que instrumenta: • • • • Tem que definir a classe; saber o que “deixar passar” (i.e. java.lang.*); getSystemClassloader(); a opção -Dendorsed.dirs=...; • Limitação: classes de sistema. 32 Conclusão • Java: reflexão deficitária. • Desempenho. • Segurança. • Manipulação: • Reflexão estrutural limitada. • Manipulação em tempo de carga: • em acordo com a filosofia Java. • Arcabouços: • facilitam a tarefa. • compromisso entre flexibilidade, complexidade e desempenho. 33 Referências: [1] P. Maes. Concepts and Experiments in Computational Reflection. In Proceedings of the OOPSLA’87. pages 147-155. Orlando, Florida. [2] B. Foote. Objects, Reflection and Open Languages. In Proceedings of the 1992 ECOOP Workshop on Object-Oriented reflection and Meta-Level Architectures. Utrecht, Netherlands [3] W. R. LaLonde and M. V. Gulik. Building a Backtracking Facility in Smalltalk Without Kernel Support. In Proceedings of the OOPSLA’88. pages 105-122. San Diego, California. [4] B. Foote and R. Johnson. Reflective Facilities in Smalltalk-80. In Proceedings of the OOPSLA’89. pages 327-335. New Orleans, Louisiana. [5] S. Kojarski and D. Lorentz. AOP as a First-class reflective mechanism. In Companion to the OOPSLA’04. pages 216 – 217. Vancouver, Canada. [6] S. Liang and G. Bracha. Dynamic Class Loading in the Java Virtual Machine. In Proceedings of the OOPSLA’98. pages 36-44. Vancouver, Canada. [7] M. Tsubori, S. Chiba et. al. OpenJava: A Class-based Macro System for Java. In Reflection and Software Engineering, LNCS 1826, Springer-Verlag, 2000. /15 34 Referências (cont.) [8] B. Foote. Class Warfare: Classes vs. Prototypes. In Proceedings of the OOPSLA’89 Workshop on Objects without Classes, New Orleans, LA. [9] T. Lindholm and F. Yellin. The Java Virtual Machine Specification. AddisonWesley, 2nd edition, 1999. [10] BCEL website. http://apache.jakarta.org/bcel. [june 9, 2005] [11] E. Bruneton, R. Lenglet and T. Coupaye. ASM: a code manipulation tool to implement adaptable systems. In ACM SIGOPS Adaptable and extensible component systems. 2002, Grenoble, France. [12] Javassist website. http://www.csg.is.titech.ac.jp/~chiba/javassist [june 8, 2005] 35 Dúvidas? 36