2012-03-16 8 views
11

Estoy intentando construir un mapa de calor “ ” a partir de un historial de varios años almacenado en un repositorio de git donde la unidad de granularidad es funciones individuales. Las funciones deberían calentarse más ya que cambian más veces, con más frecuencia y con más líneas no en blanco cambiadas.¿Cómo asocio líneas cambiadas con funciones en un repositorio git de código C?

Como punto de partida, que examinó la salida de

git log --patch -M --find-renames --find-copies-harder --function-context -- *.c 

Miré usando Language.C de Hackage, pero parece querer unidad — ampliado encabezados de una traducción completa y todos — en lugar de ser capaz de hacer frente a una fragmento fuente La opción --function-context es nueva desde la versión 1.7.8. El fundamento de la aplicación in v1.7.9.4 is a regex:

PATTERNS("cpp", 
     /* Jump targets or access declarations */ 
     "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n" 
     /* C/++ functions/methods at top level */ 
     "^([A-Za-z_][A-Za-z_0-9]*([ \t*]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n" 
     /* compound type at top level */ 
     "^((struct|class|enum)[^;]*)$", 
     /* -- */ 
     "[a-zA-Z_][a-zA-Z0-9_]*" 
     "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" 
     "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), 

Esto parece reconocer límites razonablemente bien, pero doesn t ’ deje siempre la función como la primera línea del trozo de diferencias, por ejemplo, , con directivas #include en la parte superior o con un trozo que contiene varias definiciones de funciones. Una opción para decirle a diff que emita trozos separados para cada función modificada sería realmente útil.

Esto no es ’ t de seguridad crítica, así que puedo tolerar algunas fallas. ¿Eso significa que probablemente tenga Zawinski ’ s “two problems”?

+2

¡Pregunta muy interesante! Finalmente algo diferente a "Soy nuevo en git, ¿cómo puedo deshacer un commit?". Esperando que la artillería pesada marche (es decir, VonC) :) – ralphtheninja

+0

¿Podría confirmar que el idioma de origen es C o C++? – gbulmer

+0

@gbulmer El idioma de origen es C. 'git diff' usa el mismo patrón internamente para reconocer definiciones de funciones en C y C++. –

Respuesta

1

Me doy cuenta de que esta sugerencia es un poco tangencial, pero puede ayudar a aclarar y clasificar los requisitos. Esto funcionaría para C o C++ ...

En lugar de tratar de encontrar bloques de texto que son funciones y compararlos, use el compilador para hacer bloques binarios. Específicamente, para cada archivo de origen C/C++ en un conjunto de cambios, compilarlo en un objeto. Luego use el código objeto como base para las comparaciones.

Esto puede no ser factible para usted, pero IIRC existe una opción en gcc para compilar para que cada función se compile en un "fragmento independiente" dentro del archivo de código de objeto generado. El enlazador puede tirar cada 'pedazo' en un programa. (Se está haciendo bastante tarde aquí, así que lo veré por la mañana, si está interesado en la idea)

Por lo tanto, suponiendo que podamos hacer esto, tendrá muchas funciones definidas por trozos de código binario, por lo que una simple comparación de 'calor' es '¿cuánto más o menos es el código entre versiones para cualquier función?'

También estoy pensando que podría ser práctico usar objdump para reconstituir el ensamblador para las funciones. Podría utilizar algunas expresiones regulares en esta etapa para recortar los nombres de registro, de modo que los cambios en la asignación de registro no causen demasiados falsos positivos (cambios).

Incluso podría tratar de ordenar las instrucciones del ensamblador en los cuerpos de las funciones, y diferirlas para obtener un patrón de "eliminado" frente a "agregado" entre dos implementaciones de funciones. Esto daría una medida de cambio que es bastante independiente del diseño, e incluso algo independiente del orden de parte de la fuente.

Por lo que podría ser interesante ver si dos implementaciones alternativas de la misma función (es decir,de diferente un conjunto de cambios) son las mismas instrucciones :-)

Este enfoque también debería funcionar para C++ porque todos los nombres han sido apropiadamente mutilados, lo que debería garantizar que se están comparando las mismas funciones.

Por lo tanto, las expresiones regulares se podría mantener :-) muy simple

Suponiendo que todo esto es sencillo, lo que este enfoque podría no dar?

Nota al margen: Esta estrategia básica podría funcionar para cualquier lenguaje que tenga como objetivo el código de máquina, así como los conjuntos de instrucciones VM como el código de bytes Java VM, .NET CLR, etc.

+0

Esto sería rechazado por la configuración del optimizador y la alineación –

+0

Ese es un enfoque interesante. Este repositorio particular depende de una biblioteca en otro repositorio, por lo que tendré que volver atrás en ambas historias a diferentes velocidades para tratar de mantener compilado el repositorio del cliente (* es decir *, declaraciones y encabezados apropiados disponibles). –

+0

@Ben Voigt - Estaba asumiendo que las opciones de compilación serían las mismas, y eso debería ser fácil de arreglar. Supongo que el compilador no es demasiado caótico (en el sentido fractal). Al ordenar los códigos de operación dentro de una función y eliminar los nombres de registro, las diferencias en el código real indicarán cuánto cambio "efectivo" ha sucedido. Esto no es perfecto, pero en mi humilde opinión es una alternativa interesante a una comparación textual.Para las mismas opciones de compilador, las funciones que en realidad no han cambiado en el nivel de código generado, pero que han tenido cambios de texto, también podrían ser un análisis interesante. – gbulmer

0

Puede valer la pena considerar la construcción de un analizador simple, utilizando una de las herramientas comunes, en lugar de simplemente usar expresiones regulares. Claramente, es mejor elegir algo con lo que está familiarizado o que su organización ya utiliza.

Para este problema, un analizador en realidad no necesita validar el código (supongo que es válido cuando está registrado), y no necesita entender el código, por lo que podría ser bastante tonto.

Puede descartar comentarios (conservar nuevas líneas), ignorar el contenido de las cadenas de texto y tratar el texto del programa de una manera muy simple. Principalmente necesita realizar un seguimiento de '{' '}', '(' ') equilibrado y todo el otro texto de programa válido es solo tokens individuales que se pueden pasar' directamente '.

Su salida puede ser un archivo/función independiente para facilitar el seguimiento.

Si el lenguaje es C o C++, y los desarrolladores son razonablemente disciplinados, es posible que nunca utilicen 'macros no sintácticas'. Si ese es el caso, entonces los archivos no necesitan ser preprocesados.

A continuación, un programa de análisis es en su mayoría sólo en busca de un nombre de función (un identificador) en el ámbito de archivo seguido de (parámetro-list) {...} ... código

me gustaría SWAG sería unos días de trabajo usando yacc & lex/flex & bisonte, y podría ser tan simple que no es necesario para el generador de analizador sintáctico.

Si el código es Java, entonces ANTLR es posible, y creo que hubo un simple ejemplo de analizador de Java.

Si Haskell es su centro de atención, pueden haber publicado proyectos de estudiantes que hayan hecho una prueba lógica de un analizador sintáctico.

+0

Eso suena similar al enfoque que he estado esbozando. Quiero saber el rango de líneas en el trozo de diferencias que pertenecen a una definición de función dada. Coincidir con las llaves rizadas más externas es complicado debido a los apoyos desequilibrados adicionales debido a las líneas que se agregan o eliminan. –

+0

@Greg Bacon - ¡ah! ¡Creo que entendí! Estoy pensando en otro enfoque, pero necesito algo de comer. ¿Puedo proponer una "tercera vía"? – gbulmer

Cuestiones relacionadas