Bagaimana jika tidak ada analisa statis untuk bahasa favorit Anda?

Nah, jika bahasa favorit Anda adalah Rusia, Inggris, dll., Maka ini adalah hub lain Dan jika bahasa pemrograman atau markup, maka tentu saja menulis penganalisa sendiri! Pada pandangan pertama, ini sangat sulit, tetapi, untungnya, ada alat multibahasa siap pakai yang relatif mudah untuk menambah dukungan untuk bahasa baru. Hari ini saya akan menunjukkan cara menambahkan dukungan bahasa Modelica ke penganalisa PMD dengan jumlah waktu yang cukup kecil.


Ngomong-ngomong, tahukah Anda apa yang dapat menurunkan kualitas basis kode yang diperoleh dari urutan permintaan tarikan ideal? Fakta bahwa programmer pihak ketiga menyalin potongan-potongan kode proyek yang ada ke dalam tambalan mereka alih-alih abstraksi yang kompeten. Anda harus mengakui bahwa, sampai taraf tertentu, bahkan lebih sulit untuk menangkap pemblokiran seperti itu daripada kode berkualitas rendah - itu berkualitas tinggi dan bahkan sudah dengan hati-hati disadap, jadi verifikasi lokal tidak cukup di sini, Anda perlu mengingat seluruh basis kode, tetapi ini tidak mudah bagi seseorang ... Jadi: jika menambahkan dukungan penuh untuk Modelica (tanpa membuat aturan khusus) ke negara "dapat menjalankan pemeriksaan primitif" membutuhkan waktu sekitar seminggu, kemudian dukungan untuk hanya detektor salin-tempel sering dapat ditambahkan dalam sehari!


Apa lagi Modelica?


Modelica adalah, seperti namanya, bahasa untuk menulis model sistem fisik. Bahkan, tidak hanya fisik: dimungkinkan untuk menggambarkan proses kimia, perilaku kuantitatif populasi hewan, dll - yang dijelaskan oleh sistem persamaan diferensial dari bentuk der(X) = f(X) , di mana X adalah vektor dari tidak diketahui. Potongan kode imperatif juga didukung. Persamaan diferensial parsial tidak didukung secara eksplisit, tetapi dimungkinkan untuk membagi area studi menjadi beberapa bagian (seperti yang mungkin kita lakukan dalam beberapa bahasa tujuan umum), dan kemudian menuliskan persamaan untuk setiap elemen, mengurangi masalah ke yang sebelumnya. Trik Modelka adalah bahwa solusi untuk der(X) = f(X) ini der(X) = f(X) terletak pada kompiler: Anda hanya dapat mengubah pemecah dalam pengaturan , persamaannya tidak harus linier, dll. Singkatnya, ada beberapa nilai tambah (saya menulis rumus dari buku teks - dan itu berhasil), dan kontra (dengan lebih banyak abstraksi, kita mendapatkan lebih sedikit kontrol). Pengantar Modelika adalah topik artikel terpisah (yang telah muncul beberapa kali di Habré), dan bahkan seluruh siklus, hari ini menarik minat saya sebagai terbuka dan memiliki beberapa implementasi, tetapi, sayangnya, masih merupakan standar yang lembab.


Selain itu, Modelika, di satu sisi, memiliki pengetikan statis (yang akan membantu kita untuk menulis beberapa analisis bermakna lebih cepat), di sisi lain, ketika membuat contoh model, kompiler tidak diharuskan untuk sepenuhnya memeriksa seluruh perpustakaan (oleh karena itu, penganalisa statis sangat berguna untuk menangkap "tidur" bug). Akhirnya, tidak seperti beberapa C ++, yang mana ada awan analisa statis dan kompiler dengan indah, dan yang paling penting dirinci, lihat templat C ++ diagnostik kesalahan, kompiler Model masih secara berkala menghasilkan kesalahan kompiler Internal, yang berarti bahwa ada ruang untuk membantu pengguna bahkan dengan analisa yang cukup sederhana.


Apa itu PMD?


Saya akan menjawab lagu dengan sepeda. Suatu kali saya ingin membuat beberapa permintaan tarik kecil ke lingkungan pengembangan untuk OpenModelica. Melihat bagaimana penghematan model diproses di bagian lain dari kode, saya perhatikan bagian kecil dari empat baris kode yang mendukung beberapa jenis invarian. Tidak memahami editor internal seperti apa yang berinteraksi dengannya, tetapi menyadari bahwa dari sudut pandang kode ini, tugas saya benar-benar identik, saya hanya memasukkannya ke dalam suatu fungsi sehingga saya dapat menggunakannya kembali dan tidak merusaknya. Menteiner berkata, ini luar biasa, baru kemudian mengganti kode ini dengan pemanggilan fungsi di dua puluh tempat yang tersisa ... Saya memutuskan untuk tidak terlibat lagi, dan hanya membuat salinan lain, mencatat bahwa entah bagaimana kemudian saya perlu menyisir semuanya sekaligus tanpa mencampur dengan tambalan saat ini. Googling, saya menemukan Copy-paste Detector (CPD) - bagian dari penganalisa statis PMD - yang mendukung lebih banyak bahasa daripada penganalisa itu sendiri. Setelah meletakkannya di basis kode OMEdit, saya berharap melihat dua lusin potongan dari empat baris itu. Saya tidak melihatnya (masing-masing tidak melebihi ambang batas dalam jumlah token), tetapi saya melihat, misalnya, pengulangan hampir lima puluh baris kode C ++. Seperti yang sudah saya katakan, tidak mungkin bahwa menter hanya menyalin sepotong raksasa dari file lain. Tapi dia bisa dengan mudah melewatkan ini di PR - karena kode, menurut definisi, sudah memenuhi semua standar proyek! Ketika saya berbagi pengamatan dengan menter, dia setuju bahwa perlu dibersihkan sebagai bagian dari tugas terpisah.


Dengan demikian, Program Detektor Kesalahan (PMD) adalah penganalisa statis yang mudah diperluas. Mungkin dia tidak menghitung set nilai yang dapat diambil oleh variabel (meskipun siapa yang tahu ...), tetapi untuk menambahkan aturan, Anda bahkan tidak perlu tahu Java dan entah bagaimana mengubah kodenya! Faktanya adalah bahwa hal pertama yang dia, dan tidak mengejutkan, adalah membangun file AST dengan kode sumber. Dan seperti apa bentuk pohon parsing sumber? Ke pohon parsing XML! Jadi, Anda bisa menggambarkan aturan hanya sebagai permintaan XPath - yang cocok, lalu kami mengeluarkan peringatan. Mereka bahkan memiliki debugger grafis untuk aturannya! Aturan yang lebih kompleks, tentu saja, dapat ditulis langsung di Jawa sebagai pengunjung AST.


Konsekuensi : PMD dapat digunakan tidak hanya untuk aturan yang keras dan universal yang telah dilakukan oleh programmer Java yang keras terhadap kode penganalisa, tetapi juga untuk gaya pengkodean lokal - bahkan jika Anda memasukkan ruleset.xml lokal Anda sendiri ke dalam setiap repositori!


Level 1: temukan salin-tempel secara otomatis


Pada prinsipnya, menambahkan dukungan untuk bahasa baru di CPD seringkali sangat sederhana. Saya tidak melihat adanya akal dalam menceritakan kembali dokumentasi "bagaimana melakukan" - sangat jelas, terstruktur dan langkah-demi-langkah. Untuk menceritakan kembali hal seperti itu - hanya bermain di telepon yang rusak. Saya sebaiknya menjelaskan apa yang menanti Anda (TLDR: tidak apa-apa) :



Saya akan memperingatkan Anda bahwa saya sedang mengembangkan di Ubuntu. Pada Windows, itu juga harus bekerja dengan sempurna - baik dari segi kualitas maupun dalam arti cara yang sedikit berbeda dari meluncurkan alat.


Jadi, untuk menambahkan bahasa baru ke CPD, Anda hanya perlu ...


  • PERHATIAN: jika Anda ingin dukungan penuh untuk PMD sebelum rilis PMD 7, maka lebih baik langsung ke level 2, karena dukungan normal untuk cara mudah melalui tata bahasa Antlr siap pakai akan muncul, menurut rumor, dalam versi yang sama 7, tetapi untuk saat ini Anda hanya akan menghabiskan waktu (meskipun dan sedikit ...)
  • Fork repositori pmd / pmd .
  • Temukan di antlr / grammars-v4 tata bahasa yang sudah jadi untuk bahasa Anda - tentu saja, jika bahasanya internal, Anda harus menulis sendiri, tetapi untuk Modelika, misalnya, ditemukan. Di sini, tentu saja, Anda harus mematuhi formalitas dengan lisensi - Saya bukan pengacara, tetapi setidaknya saya perlu menentukan sumber dari mana saya menyalin.
  • Setelah itu, Anda perlu membuat pmd-<your language name> , menambahkannya ke Gradle dan meletakkan file tata bahasa di sana. Selanjutnya, setelah membaca dua halaman dokumentasi yang tidak membuat stres, ulangi skrip perakitan dari modul untuk Go, beberapa kelas untuk memuat modul melalui refleksi, well, ada sedikit hal ...
  • Perbaiki hasil referensi di salah satu tes, karena sekarang CPD mendukung satu bahasa lagi! Bagaimana Anda menemukan tes ini? Sangat mudah: dia ingin merusak bangunan .
  • KEUNTUNGAN! Ini sangat sederhana asalkan ada tata bahasa yang sudah jadi

Sekarang, dengan berada di root repositori pmd, Anda dapat mengetik ./mvnw clean verify , sementara di pmd-dist/target Anda akan mendapatkan, antara lain, distribusi biner dalam bentuk arsip zip yang Anda perlu unzip dan dijalankan menggunakan ./bin/run.sh cpd --minimum-tokens 100 --files /path/to/source/dir --language <your language name> dari direktori yang belum dibongkar. Pada prinsipnya, Anda dapat melakukan ../mvnw clean verify dari dalam modul baru Anda, yang akan secara drastis mempercepat perakitan, tetapi kemudian Anda harus benar memasukkan julukan tabung yang dirakit ke dalam distribusi biner yang belum dibongkar (misalnya, dipasang satu kali setelah mendaftar modul baru).


Level 2: menemukan kesalahan dan pelanggaran panduan gaya


Seperti yang saya katakan, dukungan penuh untuk Antlr dijanjikan dalam PMD 7 . Jika Anda, seperti saya, tidak ingin menunggu pelepasan laut, maka Anda harus mendapatkan deskripsi tata bahasa dari bahasa tersebut dalam format JJTree dari suatu tempat. Mungkin Anda dapat mengeras sendiri dukungan parser yang sewenang-wenang - dokumentasi mengatakan bahwa ini mungkin, tetapi mereka tidak tahu persisnya ... Saya hanya mengambil modelica.g4 dari repositori yang sama dengan tata bahasa untuk Anltr sebagai dasar, dan secara manual membuat ulang menjadi JJTree. Secara alami, jika tata bahasa ternyata merupakan pemrosesan dari yang sudah ada, sekali lagi, tunjukkan sumbernya, verifikasi kepatuhannya dengan lisensi dan. dll.


Ngomong-ngomong, bagi seseorang yang fasih dalam semua jenis generator parser, ini sepertinya tidak mengejutkan. Sebelum itu, saya serius menggunakannya kecuali saya sendiri menulis kombinator tetap dan parser di Scala. Oleh karena itu, hal yang jelas, pada kenyataannya, membuat saya sedih pada awalnya: AST, tentu saja, saya akan dapatkan dari modelica.g4 , tetapi itu tidak terlihat sangat jelas dan "dapat digunakan": akan ada awan node tambahan di dalamnya, dan jika Anda tidak melihat token , tetapi hanya di node, tidak selalu jelas di mana, misalnya, then cabang berakhir, dan yang else dimulai.


Sekali lagi, saya tidak akan menceritakan kembali dokumentasi pada JJTree dan tutorial yang bagus - kali ini, bukan karena yang asli bersinar dengan detail dan kejelasan, tetapi karena saya sendiri tidak mengetahuinya sepenuhnya, tetapi dokumentasi itu dikirim kembali secara tidak benar, tetapi dengan keyakinan, jelas lebih buruk daripada tidak menceritakan kembali. Saya lebih baik meninggalkan sedikit petunjuk, ditemukan di sepanjang jalan:


  • Pertama, kode deskripsi parser JavaCC mengasumsikan memasukkan Java yang akan ditulis dalam parser yang dihasilkan
  • Jangan bingung bahwa ketika membangun AST, sintaksis seperti [ Expression() ] berarti opsional, dan dalam konteks menggambarkan token - memilih karakter, seperti dalam ekspresi reguler. Sejauh yang saya mengerti penjelasan dari pengembang PMD, ini adalah konstruksi serupa yang memiliki arti yang berbeda - warisan, pak ...
  • Untuk simpul root (dalam kasus saya, StoredDefinition ), Anda harus menentukan jenisnya alih-alih void (mis. ASTStoredDefiniton )
  • Menggunakan sintaks #void setelah nama node, Anda dapat menyembunyikannya dari parsing tree (yaitu, itu hanya akan mempengaruhi apa sumber yang benar dan apa yang tidak, dan bagaimana node lain akan bersarang)
  • Menggunakan konstruksi bentuk void SimpleExpression() #SimpleExpression(>1) kita dapat mengatakan bahwa node harus ditampilkan di AST yang dihasilkan, jika memiliki lebih dari satu turunan. Ini sangat nyaman ketika menggambarkan ekspresi dengan banyak operator dengan prioritas berbeda: yaitu, dari sudut pandang parser, satu-satunya yang konstan adalah sesuatu seperti LogicExpression(AdditiveExpression(MultiplicativeExperssion(Constant(1)))) - masukkan semua n level prioritas operasi - tetapi kode analyzer hanya akan mendapatkan Constant(1)
  • Node memiliki image variabel standar (lihat getImage , setImage ), yang biasanya setImage "esensi" dari node ini: misalnya, untuk node yang sesuai dengan nama variabel lokal, logis untuk menyalin token yang cocok dengan pengidentifikasi ke dalam image (secara default, semua token dari pohon akan dibuang, jadi ada baiknya menyalin makna yang terkandung di dalamnya, dalam hal apa pun, jika itu adalah variabel, dan bukan hanya kata kunci)
  • LOOKAHEAD - well, ini adalah lagu yang terpisah, bahkan bab terpisah dalam dokumentasi dikhususkan untuk itu
    • secara kasar, di JavaCC, jika Anda pergi ke sebuah simpul, Anda tidak dapat melemparkannya kembali dan mencoba mengurai secara berbeda, tetapi Anda dapat melihat ke depan sebelumnya dan memutuskan apakah akan pergi atau tidak
    • dalam kasus paling sederhana, setelah melihat peringatan JavaCC, Anda cukup mengatakan di header LOOKAHEAD = n dan Anda mendapatkan kesalahan penguraian misterius, karena dalam kasus umum, tampaknya, itu tidak dapat menyelesaikan semua masalah (kecuali, dengan menetapkan beberapa miliar token, Anda benar-benar mendapatkan pratinjau segala sesuatu, tetapi bukan fakta bahwa itu berfungsi seperti itu .. .)
    • di depan nama node tertanam, Anda dapat secara eksplisit menunjukkan berdasarkan berapa banyak token di sini Anda pasti dapat membuat keputusan akhir
    • jika dalam kasus umum tidak ada jumlah token yang pasti, Anda dapat mengatakan "buka di sini, jika sebelumnya, mulai dari titik ini, kami berhasil mencocokkan awalan seperti itu - dan kemudian deskripsi subtree biasa"
    • hati-hati: dalam kasus umum, JavaCC tidak dapat memeriksa kebenaran arahan LOOKAHEAD - ini mempercayai Anda, jadi setidaknya LOOKAHEAD bukti matematis mengapa pencarian seperti itu cukup ...

Sekarang setelah Anda memiliki deskripsi tata bahasa dalam format JJTree, 14 langkah sederhana ini akan membantu Anda menambahkan dukungan bahasa. Kebanyakan dari mereka memiliki bentuk "buat kelas yang mirip dengan implementasi untuk java atau vm, tetapi diadaptasi." Saya hanya akan mencatat fitur-fitur khas, beberapa di antaranya akan muncul di dokumentasi utama jika mereka menerima permintaan tarikan saya untuk dokumentasi :


  • Mengomentari penghapusan semua file yang dihasilkan dalam skrip perakitan alljavacc.xml (yang ada di modul baru Anda), Anda dapat mentransfernya ke hierarki sumber dari target/generated-sources . Tapi lebih baik tidak. Kemungkinan besar, hanya sebagian kecil yang akan diubah, jadi lebih baik menghapus beberapa saja: mereka melihat perlunya mengubah implementasi default, disalin ke hierarki sumber, ditambahkan ke daftar file yang dihapus, dibangun kembali - dan sekarang Anda mengelola file - khususnya file ini . Kalau tidak, akan sulit untuk mencari tahu apa yang sebenarnya telah diubah, dan dukungan itu hampir tidak dapat disebut menyenangkan.
  • sekarang setelah Anda menerapkan mode PMD "utama", Anda dapat dengan mudah bertahan pada parser JJTree Anda yang mengikat untuk CPD juga, mirip dengan Java atau beberapa implementasi lain yang tersedia
  • Ingatlah untuk mengimplementasikan metode yang mengembalikan nama host untuk permintaan XPath. Dalam implementasi default, rekursi tak terbatas diperoleh (nama simpul melalui toString dan sebaliknya), atau sesuatu yang lain, secara umum, karena ini, juga tidak mungkin untuk melihat pohon di PMD Designer, dan tanpa debugging tata bahasa ini benar-benar menyedihkan
  • bagian dari registrasi komponen dilakukan dengan menambahkan file teks dari titik entri nama kelas yang berkualifikasi penuh ke META-INF/services
  • apa yang dapat dideskripsikan secara deklaratif dalam aturan (misalnya, uraian terperinci tentang pemeriksaan dan contoh kesalahan) dijelaskan tidak dalam kode, tetapi dalam category/<language name>/<ruleset>.xml - dalam hal apa pun, Anda harus mendaftarkan aturan Anda di sana
  • ... tetapi ketika mengimplementasikan tes, tampaknya, beberapa, mungkin buatan sendiri, mekanisme penemuan otomatis aktif digunakan, oleh karena itu
    • jika Anda diberi tahu "tambahkan tes sepele untuk setiap versi bahasa" - lebih baik tidak berdebat, mereka mengatakan "Saya tidak membutuhkannya, itu berfungsi seperti itu" - mungkin ini adalah mekanisme penemuan otomatis
    • jika Anda melihat tes untuk aturan tertentu dengan badan kelas yang hanya berisi komentar // no additional unit tests , maka ini bukan tes, mereka hanya terletak pada sumber daya dalam bentuk XML-deskripsi data input dan reaksi analisis yang diharapkan, segera: beberapa yang benar dan beberapa contoh yang salah.

Sebuah pencarian kecil namun penting: menyelesaikan Designer PMD


Mungkin Anda dapat men-debug semuanya tanpa visualisator. Tapi mengapa? Pertama, untuk menyelesaikannya sangat sederhana. Kedua, ini akan sangat membantu pengguna Anda yang tidak terbiasa dengan Java: mereka mudah dan sederhana (jika ini berlaku untuk XPath sama sekali), baik, atau setidaknya tanpa mengkompilasi ulang PMD akan dapat menggambarkan pola-pola sederhana dari apa yang tidak mereka sukai (dalam kasus paling sederhana) - panduan gaya seperti "nama paket model selalu dimulai dengan huruf kecil").


Tidak seperti kesalahan lain yang langsung terlihat, masalah dengan PMD Designer cukup berbahaya: Anda mungkin sudah mengerti bahwa prasasti Java di sisi kanan menu bukanlah tombol, tetapi daftar drop-down dari pilihan bahasa O_o, di mana sudah muncul Modelica, karena modul baru dengan pendaftaran titik masuk telah muncul di classpath. Tapi di sini Anda memilih bahasa Anda, unduh file tes, dan lihat AST. Dan tampaknya menjadi kemenangan, tetapi entah bagaimana hitam dan putih, dan subtree yang disorot dapat disorot dalam teks - meskipun tidak, ada sorot, tetapi diperbarui secara bengkok - namun, bagaimana mereka tidak menebak untuk menyoroti pertandingan yang ditemukan dengan XPath ... Sudah memperkirakan jumlah pekerjaan, Anda memikirkan permintaan tarikan berikutnya, tetapi kemudian Anda secara tidak sengaja memutuskan untuk beralih bahasa ke Jawa dan mengunduh beberapa kode sumber PMD itu sendiri ... Oh! Warnanya berwarna! .. Dan highlight subtree berfungsi! Eh ... dan ternyata itu biasanya menyorot kecocokan yang ditemukan dan menulis potongan teks di kotak di sebelah kanan permintaan ... Rasanya seperti ketika pengecualian terjadi pada kode JavaFX saat membuat antarmuka, itu mengganggu rendering, tetapi tidak mencetak ke konsol ...


Secara umum, Anda hanya perlu menambahkan kelas ma-a-scarlet untuk menyorot sintaksis berdasarkan ekspresi reguler. Dalam kasus saya, itu net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.ModelicaSyntaxHighlighter , yang perlu didaftarkan di kelas AvailableSyntaxHighlighters . Harap dicatat bahwa kedua perubahan ini terjadi di repositori pmd-designer , artefak perakitan yang perlu dimasukkan ke dalam distribusi biner Anda.


Pada akhirnya, terlihat seperti ini (GIF diambil dari README dalam repositori Designer PMD):


Desainer PMD di tempat kerja


Subtotal


Jika Anda telah menyelesaikan semua level ini, maka sekarang Anda memiliki:


  • copy paste detektor
  • mesin aturan
  • visualizer untuk debug AST dan membawanya ke dalam bentuk yang mudah untuk analisis (seperti yang telah kita lihat, tidak semua tata bahasa dengan bahasa yang sama berguna!)
  • visualisator yang sama untuk men-debug aturan XPath yang dapat ditulis pengguna Anda tanpa mengkompilasi ulang PMD dan umumnya pengetahuan tentang Java (XPath, tentu saja, juga bukan BASIC, tetapi setidaknya merupakan standar dan bukan bahasa permintaan lokal)

Saya harap Anda juga memiliki pemahaman tentang fakta bahwa tata bahasa sekarang menjadi API yang stabil untuk implementasi dukungan bahasa Anda - jangan mengubahnya (atau lebih tepatnya fungsi mengubah sumber menjadi AST yang dijelaskan olehnya) kecuali jika benar-benar diperlukan, dan jika Anda telah berubah, beri tahu sebagai pemecah perubahan, dan kemudian pengguna akan kecewa: kemungkinan besar, tidak semua orang akan menulis tes untuk aturan mereka, tetapi sangat menyedihkan ketika aturan memeriksa kode, dan kemudian berhenti tanpa peringatan - hampir seperti cadangan, yang tiba-tiba rusak, dan setahun yang lalu ...


Ceritanya tidak berakhir di sini: setidaknya beberapa aturan yang berguna harus ditulis.


Tapi itu belum semuanya: PMD secara alami mendukung cakupan dan deklarasi. Setiap node AST memiliki cakupan yang terkait dengannya: tubuh kelas, fungsi, loop ... Seluruh file, paling buruk! Dan di setiap ruang lingkup ada daftar definisi (deklarasi) yang dikandungnya secara langsung. Seperti dalam kasus-kasus lain, diusulkan untuk diimplementasikan secara analogi dengan bahasa-bahasa lain, misalnya, Modelika (tetapi pada saat penulisan, logika dalam permintaan tarikan saya, sejujurnya, mentah). scopes declarations visitor, - ScopeAndDeclarationFinder , — , , , - , read-only AST. , .


 public class ModelicaHandler extends AbstractLanguageVersionHandler { // ... @Override public VisitorStarter getSymbolFacade() { return new VisitorStarter() { @Override public void start(Node rootNode) { new SymbolFacade().initializeWith((ASTStoredDefinition) rootNode); } }; } } 

Kesimpulan


PMD . , «» Clang Static Analyzer , . , CPD ( ), .

Source: https://habr.com/ru/post/id474920/


All Articles