नियम: डायनामिक चेक फॉर गो


इस लेख में, मैं नए go-ruleguard स्टैटिक एनालिसिस लाइब्रेरी (और यूटिलिटी) के बारे में बात go-ruleguard जो gogrep के अंदर उपयोग के लिए गोग्रेप को एडाप्ट करता है।


विशिष्ट सुविधा: आप एक विशेष गो-जैसे डीएसएल पर स्टेटिक विश्लेषण के नियमों का वर्णन करते हैं, जो ruleguard की शुरुआत में निदान के एक सेट में बदल जाता है। शायद यह गो के लिए कस्टम निरीक्षण को लागू करने के लिए सबसे आसानी से कॉन्फ़िगर करने योग्य उपकरण में से एक है।


एक बोनस के रूप में, हम go/analysis और इसके पूर्ववर्तियों के बारे में बात करेंगे।


स्थैतिक विश्लेषण विस्तार


गो के लिए कई लिंटर हैं, जिनमें से कुछ का विस्तार किया जा सकता है। आमतौर पर, लिंटर को विस्तारित करने के लिए, आपको विशेष लिंटर एपीआई का उपयोग करके गो कोड लिखना होगा।


दो मुख्य तरीके हैं: प्लग इन और मोनोलिथ। मोनोलिथ का तात्पर्य है कि सभी जाँच (आपके व्यक्तिगत सहित) संकलन चरण में उपलब्ध हैं।


विस्तार के लिए इसके कर्नेल में शामिल होने के लिए नए चेक की आवश्यकता होती है। इसके अलावा go-critic प्लग-इन कर सकता है, जो आपको मुख्य कोड की परवाह किए बिना एक्सटेंशन एकत्र करने की अनुमति देता है। इन दोनों दृष्टिकोणों का अर्थ है कि आप go/ast लागू करते हैं और लिंटर एपीआई का उपयोग करते हुए गो पर go/types जोड़तोड़ करते हैं। साधारण चेक के लिए भी बहुत सारे कोड की आवश्यकता होती है।


go/analysis उद्देश्य इस तथ्य से तस्वीर को सरल बनाना है कि लिंटर का "ढांचा" लगभग समान हो जाता है, लेकिन यह स्वयं निदान के तकनीकी कार्यान्वयन की जटिलता की समस्या को हल नहीं करता है।


`लोडर` और` गो / पैकेज` पर पाचन


जब आप गो के लिए एक विश्लेषक लिखते हैं, तो आपका अंतिम लक्ष्य एएसटी और प्रकारों के साथ बातचीत करना है, लेकिन इससे पहले कि आप ऐसा कर सकें, स्रोत कोड को सही तरीके से "लोड" करने की आवश्यकता है। सरल बनाने के लिए, लोडिंग की अवधारणा में पार्सिंग , टाइप चेकिंग और आयात निर्भरता शामिल हैं


इस पाइपलाइन को सरल बनाने में पहला कदम था go/loader पैकेज, जो आपको कॉल के एक जोड़े के माध्यम से आपकी ज़रूरत की सभी चीज़ों को "डाउनलोड" करने की अनुमति देगा। सब कुछ लगभग ठीक था, और फिर वह go/packages पक्ष में पदावनत हो गया। go/packages में थोड़ा बेहतर एपीआई है और सिद्धांत रूप में, मॉड्यूल के साथ अच्छी तरह से काम करता है।


अब, एनालाइज़र लिखने के लिए उपरोक्त में से किसी का भी उपयोग नहीं करना सबसे अच्छा है, क्योंकि go/analysis ने go/packages कुछ दिया जो कि पिछले समाधानों में से कोई भी नहीं था - आपके कार्यक्रम के लिए एक संरचना। अब हम तयशुदा go/analysis प्रतिमान का उपयोग कर सकते हैं और go/analysis अधिक कुशलता से पुन: उपयोग कर सकते हैं। इस प्रतिमान में विवादास्पद बिंदु हैं, उदाहरण के लिए, go/analysis एक पैकेज और इसकी निर्भरता के स्तर पर विश्लेषण के लिए अच्छी तरह से अनुकूल है, लेकिन चालाक इंजीनियरिंग चाल के बिना इस पर वैश्विक विश्लेषण करना आसान नहीं होगा।


go/analysis भी विश्लेषक परीक्षण को सरल करता है।




रूलगार्ड क्या है?



go-ruleguard एक स्थिर विश्लेषण उपयोगिता है जो डिफ़ॉल्ट रूप से एक भी चेक शामिल नहीं करता है।


ruleguard नियम शुरू में लोड किए ruleguard , एक विशेष gorules फ़ाइल से जो कि कोड पैटर्न के बारे में घोषणा करता है जिसमें चेतावनी जारी की जानी चाहिए। यह फाइल ruleguard उपयोगकर्ताओं द्वारा स्वतंत्र रूप से संपादित की जा सकती है।


नए चेक को जोड़ने के लिए नियंत्रण कार्यक्रम को gorules करना आवश्यक नहीं है, इसलिए gorules से नियमों को गतिशील कहा जा सकता है।


ruleguard नियंत्रण कार्यक्रम इस तरह दिखता है:


 package main import ( "github.com/quasilyte/go-ruleguard/analyzer" "golang.org/x/tools/go/analysis/singlechecker" ) func main() { singlechecker.Main(analyzer.Analyzer) } 

उसी समय, analyzer ruleguard पैकेज के माध्यम से लागू किया ruleguard है, जिसे आपको लाइब्रेरी के रूप में उपयोग करना है।


नियम रक्षक वी.एस.


एक सरल लेकिन वास्तविक दुनिया का उदाहरण लें: मान लें कि हम अपने कार्यक्रमों में runtime.GC() से बचना चाहते हैं। पुनर्जीवित में इसके लिए पहले से ही एक अलग निदान है, इसे "call-to-gc" कहा जाता है।


कॉल-टू-जीसी कार्यान्वयन (एलेन में 70 लाइनें)


 package rule import ( "go/ast" "github.com/mgechev/revive/lint" ) // CallToGCRule lints calls to the garbage collector. type CallToGCRule struct{} // Apply applies the rule to given file. func (r *CallToGCRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { var failures []lint.Failure onFailure := func(failure lint.Failure) { failures = append(failures, failure) } var gcTriggeringFunctions = map[string]map[string]bool{ "runtime": map[string]bool{"GC": true}, } w := lintCallToGC{onFailure, gcTriggeringFunctions} ast.Walk(w, file.AST) return failures } // Name returns the rule name. func (r *CallToGCRule) Name() string { return "call-to-gc" } type lintCallToGC struct { onFailure func(lint.Failure) gcTriggeringFunctions map[string]map[string]bool } func (w lintCallToGC) Visit(node ast.Node) ast.Visitor { ce, ok := node.(*ast.CallExpr) if !ok { return w // nothing to do, the node is not a call } fc, ok := ce.Fun.(*ast.SelectorExpr) if !ok { return nil // nothing to do, the call is not of the form pkg.func(...) } id, ok := fc.X.(*ast.Ident) if !ok { return nil // in case X is not an id (it should be!) } fn := fc.Sel.Name pkg := id.Name if !w.gcTriggeringFunctions[pkg][fn] { return nil // it isn't a call to a GC triggering function } w.onFailure(lint.Failure{ Confidence: 1, Node: node, Category: "bad practice", Failure: "explicit call to the garbage collector", }) return w } 



अब इसकी तुलना go-ruleguard कैसे करें:


 package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func callToGC(m fluent.Matcher) { m.Match(`runtime.GC()`).Report(`explicit call to the garbage collector`) } 

इससे ज्यादा और कुछ नहीं, बस जो मायने रखता है - runtime.GC और उस संदेश को जारी करने की आवश्यकता है जो नियम के ट्रिगर होने की आवश्यकता है।


आप पूछ सकते हैं: क्या यह सब है? मैंने विशेष रूप से इस तरह के एक सरल उदाहरण के साथ शुरू किया, यह दिखाने के लिए कि पारंपरिक दृष्टिकोण के मामले में बहुत तुच्छ निदान के लिए कितना कोड की आवश्यकता हो सकती है। मैं वादा करता हूं कि अधिक रोमांचक उदाहरण होंगे।


जल्दी शुरू करो


go-critic पास एक rangeExprCopy डायग्नोस्टिक है जो कोड में संभावित अप्रत्याशित सरणी प्रतियां पाता है।


यह कोड सरणी की एक प्रति पर आधारित है :


 var xs [2048]byte for _, x := range xs { // Copies 2048 bytes // Loop body. } 

इस समस्या का हल एक वर्ण जोड़ना है:


  var xs [2048]byte - for _, x := range xs { // Copies 2048 bytes + for _, x := range &xs { // No copy // Loop body. } 

सबसे अधिक संभावना है, आपको इस प्रतिलिपि की आवश्यकता नहीं है, और सही संस्करण का प्रदर्शन हमेशा बेहतर होता है। जब तक गो संकलक बेहतर नहीं हो जाता, तब तक आप प्रतीक्षा कर सकते हैं या आप कोड में ऐसी जगहों का पता लगा सकते हैं और आज उसी go-critic का उपयोग करके उन्हें ठीक कर सकते हैं।


इस निदान को gorules भाषा में लागू किया जा सकता है ( rules.go फ़ाइल):


 package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func _(m fluent.Matcher) { m.Match(`for $_, $_ := range $x { $*_ }`, `for $_, $_ = range $x { $*_ }`). Where(m["x"].Addressable && m["x"].Type.Size >= 128). Report(`$x copy can be avoided with &$x`). At(m["x"]). Suggest(`&$x`) } 

नियम सभी for-range छोरों को ढूंढता है जहां दोनों चलने योग्य चर का उपयोग किया जाता है (यह मामला है जो नकल की ओर जाता है)। Iterable अभिव्यक्ति $x addressable होना चाहिए और बाइट्स में चयनित सीमा से बड़ा होना चाहिए।


Report() उपयोगकर्ता को जारी किए जाने वाले संदेश को परिभाषित करती है, और Suggest() एक quickfix टेम्प्लेट का वर्णन करता है quickfix उपयोग आपके संपादक में गोपल्स (एलएसपी) के माध्यम से किया जा सकता है, साथ ही अंतःक्रियात्मक रूप से अगर ruleguard तर्क (हम इस पर वापस आ जाएंगे) के साथ बुलाया जाता है। At() टेम्पलेट के एक विशिष्ट भाग के लिए चेतावनी और quickfix संलग्न करता है। हमें पूरे लूप को फिर से लिखने के बजाय &$x से बदलने की आवश्यकता है।


Report() और Suggest() दोनों एक स्ट्रिंग को स्वीकार करते हैं जिसमें Match() से टेम्पलेट द्वारा कैप्चर किए गए अभिव्यक्तियों को प्रक्षेपित किया जा सकता है। पूर्वनिर्धारित चर $$ अर्थ है "सभी कैप्चर किए गए टुकड़े" (नियमित अभिव्यक्तियों में $0 के रूप में)।


rangecopy.go फ़ाइल बनाएँ:


 package example // sizeof(builtins[...]) = 240 on x86-64 var builtins = [...]string{ "append", "cap", "close", "complex", "copy", "delete", "imag", "len", "make", "new", "panic", "print", "println", "real", "recover", } func builtinID(name string) int { for i, s := range builtins { if s == name { return i } } return -1 } 

अब हम ruleguard कर सकते हैं:


 $ ruleguard -rules rules.go -fix rangecopy.go rangecopy.go:12:20: builtins copy can be avoided with &builtins 

यदि उसके बाद हम rangecopy.go को देखते हैं, तो हम एक निश्चित संस्करण देखेंगे, क्योंकि ruleguard को ruleguard पैरामीटर के साथ कहा जाता था।


gorules फ़ाइल gorules बिना सबसे सरल नियमों को डीबग किया जा सकता है:


 $ ruleguard -c 1 -e 'm.Match(`return -1`)' rangecopy.go rangecopy.go:17:2: return -1 16 } 17 return -1 18 } 

go/analysis/singlechecker के उपयोग के लिए धन्यवाद, हमारे पास -c विकल्प है, जो हमें चेतावनी के साथ ही निर्दिष्ट संदर्भ लाइनों को प्रदर्शित करने की अनुमति देता है। इस पैरामीटर को नियंत्रित करना थोड़ा उल्टा है: डिफ़ॉल्ट मान -c=-1 , जिसका अर्थ है "कोई संदर्भ नहीं", और -c=0 संदर्भ की एक पंक्ति (डायग्नोस्टिक्स द्वारा संकेतित) को आउटपुट करेगा।


यहाँ कुछ और दिलचस्प gorules :


  • प्रकार के टेम्पलेट जो आपको अपेक्षित प्रकार निर्दिष्ट करते हैं। उदाहरण के लिए, अभिव्यक्ति map[$t]$t सभी मैप्स का वर्णन करता है जिसके लिए मूल्य प्रकार कुंजी के प्रकार से मेल खाता है, और *[$len]$elem एलएम सभी बिंदुओं को सरणियों के लिए कैप्चर करता है।
  • एक ही कार्य के भीतर, कई नियम हो सकते हैं,
    और कार्यों को स्वयं नियम समूह कहा जाना चाहिए।
  • समूह में नियम एक के बाद एक लागू होते हैं, जिस क्रम में उन्हें परिभाषित किया जाता है। पहला नियम जो ट्रिगर किया गया है, शेष नियमों के साथ तुलना को रद्द करता है। यह महत्वपूर्ण है कि अनुकूलन के लिए इतना नहीं है क्योंकि विशिष्ट मामलों के लिए विशिष्ट नियमों के लिए। एक उदाहरण जहां यह उपयोगी है, $x=$x+$y को फिर से लिखने का नियम है $y=1 के मामले में आप $x++ पेशकश करना चाहते हैं, न कि $x+=1

उपयोग किए जाने वाले डीएसएल पर अधिक जानकारी docs/gorules.md में पाई जा सकती है।


और उदाहरण


 package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func exampleGroup(m fluent.Matcher) { //     json.Decoder. // . http://golang.org/issue/36225 m.Match(`json.NewDecoder($_).Decode($_)`). Report(`this json.Decoder usage is erroneous`) //   unconvert,    . m.Match(`time.Duration($x) * time.Second`). Where(m["x"].Const). Suggest(`$x * time.Second`) //   fmt.Sprint()    String(), //   $x  . m.Match(`fmt.Sprint($x)`). Where(m["x"].Type.Implements(`fmt.Stringer`)). Suggest(`$x.String()`) //   . m.Match(`!($x != $y)`).Suggest(`$x == $y`) m.Match(`!($x == $y)`).Suggest(`$x != $y`) } 

यदि नियम के लिए कोई Report() कॉल नहीं है, तो Suggest() से संदेश आउटपुट का उपयोग किया जाएगा। यह कुछ मामलों में दोहराव से बचने की अनुमति देता है।


प्रकार फिल्टर और सबएक्सप्रेसन विभिन्न गुणों की जांच कर सकते हैं। उदाहरण के लिए, Pure और Const गुण उपयोगी हैं:


  • Var.Pure अर्थ है कि अभिव्यक्ति का कोई दुष्प्रभाव नहीं है।
  • Var.Const अर्थ है कि अभिव्यक्ति का उपयोग निरंतर संदर्भ में किया जा सकता है (उदाहरण के लिए, एक सरणी का आयाम)।

Where() स्थितियों में package-qualified नामों के लिए, आपको Import() विधि का उपयोग करने की आवश्यकता है। सुविधा के लिए, सभी मानक पैकेज आपके लिए आयात किए गए थे, इसलिए ऊपर दिए गए उदाहरण में हमें अतिरिक्त आयात करने की आवश्यकता नहीं है।


go/analysis क्विकफिक्स एक्शन


quickfix लिए समर्थन हमारे लिए go/analysis द्वारा quickfix किया गया है।


go/analysis मॉडल में, विश्लेषक निदान और तथ्य उत्पन्न करता है। डायग्नोस्टिक्स को उपयोगकर्ताओं को भेजा जाता है, और अन्य एनालाइज़र द्वारा उपयोग के लिए तथ्यों का इरादा होता है।


डायग्नॉस्टिक्स में सुझाए गए फ़िक्सेस का एक सेट हो सकता है, जिनमें से प्रत्येक डायग्नोस्टिक्स द्वारा पाई गई समस्या को ठीक करने के लिए निर्दिष्ट सीमा में स्रोत कोड को बदलने का तरीका बताता है।


आधिकारिक विवरण go/analysis/doc/suggested_fixes.md में उपलब्ध है।


निष्कर्ष



अपने प्रोजेक्ट्स पर ruleguard करने का प्रयास करें, और यदि आप बग ruleguard या एक नई सुविधा के लिए पूछना चाहते हैं, तो खुला मुद्दा


यदि आपको अभी भी एक ruleguard एप्लिकेशन के साथ आना मुश्किल है, तो यहां कुछ उदाहरण दिए गए हैं:


  • गो के लिए अपने स्वयं के निदान को लागू करें।
  • उपसर्ग के साथ स्वचालित रूप से अपग्रेड या रिफैक्टर कोड।
  • कोड के आंकड़े -json परिणाम के -json प्रसंस्करण के साथ संग्रह।

निकट भविष्य में ruleguard की विकास योजनाएँ:



उपयोगी लिंक और संसाधन


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


All Articles