İçeriğe geç
academia.sh

Ders 15 / 16

Statik Çözümleme ve Güvenlik Denetimi

Aynı iki kaynaktan biri temiz, öteki kusurlu; derleyicinin ikisini de aynı şekilde kabul ettiği, ama bir çözümleyicinin aralarındaki farkı çıkış koduyla bildirdiği ölçülür. Üç kalıptan derleyicinin hiçbirini yakalamadığı, çözümleyicinin ikisini yakaladığı sayılıyor. Sınırlayıcı ölçüm, çözümlemenin temiz çıkmasının kusursuz olmak anlamına gelmediğini — yakalanmayan üçüncü kalıbın koşum sırasında gerçekten çöktüğünü — koşturarak gösteriyor. Son ölçüm, bir bağımlılığın bildirilen sürümünü modellenmiş bir eşik listesiyle karşılaştırıyor.

Yayın Güncelleme

Bu derste

  • Aynı iki kaynak arasındaki farkı, derleyicinin göremediği ama bir çözümleyicinin çıkış koduyla bildirdiği bir ölçümle ayırt eder
  • Derleyicinin hiçbirini reddetmediği üç kalıptan kaçının çözümleyici tarafından yakalandığını sayar
  • Çözümlemenin temiz çıkmasının kusursuz olmak anlamına gelmediğini, yakalanmayan bir kalıbı koşturarak sınırlar
İçindekiler

Önceki derste ölçülen araç, kaynağı yalnızca isimler ve tipler düzeyinde okudu; gövdelerin içinde ne olduğuna hiç bakmadı. Bu ders tam o boşluğa iniyor: gövdenin kendisini okuyan, belirli kalıpları arayan bir araç. Ama bu aracın da kendi sınırı var — bulduğunu söylemesi, aramadığı bir şeyin de yok olduğu anlamına gelmiyor.

İki Kaynak, Bir Çözümleyici

Statik çözümleme (static analysis), bir kaynağı hiç çalıştırmadan, yalnızca metnini okuyarak belirli hata kalıplarını arayan bir işlemdir. Dilin kendi dağıtımının çözümleme aracı bu işi tek bir komutla yapıyor ve yalnızca çıkış kodu bildiriyor — bulduğu satırların metnini bu derste hiç okumuyoruz, yalnızca temiz mi kusurlu mu olduğunu.

Aşağıdaki ölçüm iki kaynak kuruyor: biri bir tam sayıyı doğru biçimlendiriciyle yazdırıyor, öteki aynı biçimlendiriciye bir dizgi veriyor — tip ile biçim arasında bir uyuşmazlık.

// yardimcilar.go — bu dersin paylastigi yardimcilar: gecici paket kurma ve arac cagirma
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-40s %-7s %s\n", bilgi, soyleyen, kanit)
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "cozumleme")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module cozumlemedeneme\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// aracCalistir dilin kendi komutunu kosturur, ciktiyi ve basari durumunu doner; cikti basilmaz.
func aracCalistir(dizin string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

// derlenebilirMi, verilen dizindeki paketi derlemeyi dener.
func derlenebilirMi(dizin string) bool {
	_, tamam := aracCalistir(dizin, "build", "-o", os.DevNull, ".")
	return tamam
}

// temizMi, verilen dizindeki paketin statik cozumlemeden temiz gecip gecmedigini soyler.
func temizMi(dizin string) bool {
	_, tamam := aracCalistir(dizin, "vet", "./...")
	return tamam
}
// olcum1.go — ayni iki kaynak, biri temiz biri kusurlu, tek bir cozumleyiciden geciyor
package main

import "fmt"

func main() {
	temiz := paket(map[string]string{
		"prog.go": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"deger=%d\\n\", 7)\n}\n",
	})
	kusurlu := paket(map[string]string{
		"prog.go": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"deger=%d\\n\", \"metin\")\n}\n",
	})

	fmt.Println("-- iki kaynak, bir cozumleyici --")
	goster("temiz kaynak derlendi mi", "arac", fmt.Sprint(derlenebilirMi(temiz)))
	goster("kusurlu kaynak derlendi mi", "arac", fmt.Sprint(derlenebilirMi(kusurlu)))
	goster("temiz kaynak cozumlemesi", "arac", fmt.Sprint("temiz=", temizMi(temiz)))
	goster("kusurlu kaynak cozumlemesi", "arac", fmt.Sprint("temiz=", temizMi(kusurlu)))
}
-- iki kaynak, bir cozumleyici --
temiz kaynak derlendi mi                 arac    true
kusurlu kaynak derlendi mi               arac    true
temiz kaynak cozumlemesi                 arac    temiz=true
kusurlu kaynak cozumlemesi               arac    temiz=false

DR15. İki kaynak da derleniyor — tip uyuşmazlığı derleyici düzeyinde bir hata değil, çünkü biçimlendirme dizgisi ile argümanlar arasındaki ilişki, derleyicinin tip denetiminin konusu değil. Çözümleyici ise bu iki satırı ayırt ediyor: birinde biçimlendirici ile argüman tipi eşleşiyor, ötekinde eşleşmiyor. Bu, önceki derslerdeki “araç” bilgisinin bir başka biçimi — çözümleyici kaynağı çalıştırmadan, yalnızca okuyarak bir kalıp arıyor ve bulduğunu tek bir çıkış koduyla bildiriyor.

Derleyicinin Yakalamadığı Kalıp

Yukarıdaki tek kalıp bir başlangıç. Aşağıdaki ölçüm üç ayrı kalıbı bir arada deniyor: bir biçim uyuşmazlığı, bir kilit değerinin kopyalanması ve bir hatanın yok sayılması. Üçü de derleniyor; sorulan soru, kaçının çözümleyici tarafından yakalandığı.

// olcum2.go — derleyicinin hicbirini reddetmedigi uc kalip, cozumleyiciden gecerken ayriliyor
package main

import "fmt"

const kalipBicim = "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"deger=%d\\n\", \"metin\")\n}\n"

const kalipKilit = "package main\n\nimport \"sync\"\n\ntype Sayac struct {\n\tkilit sync.Mutex\n\tdeger int\n}\n\nfunc kopyala(s Sayac) Sayac {\n\treturn s\n}\n\nfunc main() {\n\tkopyala(Sayac{})\n}\n"

const kalipYokSayilanHata = "package main\n\nimport \"fmt\"\n\ntype Kayit struct{ Ad string }\n\nfunc bul(anahtar string) (*Kayit, error) {\n\tif anahtar == \"\" {\n\t\treturn nil, fmt.Errorf(\"anahtar bos\")\n\t}\n\treturn &Kayit{Ad: anahtar}, nil\n}\n\nfunc main() {\n\tk, _ := bul(\"\")\n\tfmt.Println(k.Ad)\n}\n"

func main() {
	kaliplar := []struct{ ad, kaynak string }{
		{"bicim uyusmazligi", kalipBicim},
		{"kilit kopyalama", kalipKilit},
		{"yok sayilan hata", kalipYokSayilanHata},
	}

	fmt.Println("-- derleyicinin yakalamadigi uc kalip --")
	derlenenSayisi, temizSayisi := 0, 0
	for _, k := range kaliplar {
		dizin := paket(map[string]string{"prog.go": k.kaynak})
		d := derlenebilirMi(dizin)
		t := temizMi(dizin)
		if d {
			derlenenSayisi++
		}
		if t {
			temizSayisi++
		}
		goster(k.ad+" derlendi mi", "arac", fmt.Sprint(d))
		goster(k.ad+" cozumleme temiz mi", "arac", fmt.Sprint(t))
	}
	goster("uc kaliptan derlenen sayisi", "arac", fmt.Sprint(derlenenSayisi))
	goster("uc kaliptan cozumlemeyi gecen sayisi", "arac", fmt.Sprint(temizSayisi))
	goster("cozumleyicinin yakaladigi kalip sayisi", "arac", fmt.Sprint(len(kaliplar)-temizSayisi))
}
-- derleyicinin yakalamadigi uc kalip --
bicim uyusmazligi derlendi mi            arac    true
bicim uyusmazligi cozumleme temiz mi     arac    false
kilit kopyalama derlendi mi              arac    true
kilit kopyalama cozumleme temiz mi       arac    false
yok sayilan hata derlendi mi             arac    true
yok sayilan hata cozumleme temiz mi      arac    true
uc kaliptan derlenen sayisi              arac    3
uc kaliptan cozumlemeyi gecen sayisi     arac    1
cozumleyicinin yakaladigi kalip sayisi   arac    2

DR16. Üç kalıbın üçü de derleniyor — derleyicinin yakaladığı kalıp sayısı sıfır. Bu üç kalıp derleyicinin çalıştığı katmanın tümüyle dışında duruyor, çünkü üçü de tipçe geçerli kod: yanlış biçimlendirilmiş bir yazdırma çağrısı, bir alanı kopyalanan bir yapı, kullanılmayan bir hata değeri — bunların hiçbiri bir tip uyuşmazlığı ya da bir söz dizimi hatası değil. Çözümleyici üç kalıptan ikisini yakalıyor: biçim uyuşmazlığını ve kilit kopyalamasını. Üçüncüsü — hatanın yok sayılması — çözümlemeden temiz çıkıyor. Bu üçüncü kalıp bir sonraki bölümün konusu.

Yakalanan iki kalıbın ortak bir özelliği var: ikisi de dilin kendi tip sisteminin dışında kalan, ama yine de mekanik olarak tanınabilen bir örüntüye uyuyor. Biçim uyuşmazlığında örüntü, bir biçimlendirme dizgisindeki her % işaretinin, verilen argümanların tipiyle eşleşip eşleşmediği; kilit kopyalamasında örüntü, sync paketinin belgelediği bir tipin (kilit içeren herhangi bir yapı) değerle değil yalnızca işaretçiyle taşınması gerektiği. Çözümleyici bu iki kuralı, kaynağı ayrıştırıp söz dizim ağacında belirli bir biçimi arayarak uyguluyor — tıpkı önceki dersteki imza karşılaştırıcısının yaptığı gibi, yalnızca aranan biçim burada bir imza değil, bir kullanım kalıbı. Üçüncü kalıbın yakalanmaması, çözümleyicinin bu iki kuraldan hiçbirine uymadığı için değil, hatanın yok sayılmasını arayan bir kural listede hiç olmadığı için.

Sınırlayıcı Ölçüm: Temiz Çıkmak Kusursuz Demek Değil

Yukarıdaki üçüncü kalıp, bir işlevin döndürdüğü hatayı yok sayıp sonucu doğrudan kullanan bir çağrı. Çözümleyici bunu temiz buldu; derleyici de reddetmedi. Aşağıdaki ölçüm bu kalıbı gerçekten koşturuyor — bu kez bir alt süreç çağırarak değil, aynı mantığı bu ölçümün kendi içinde çalıştırarak, çünkü panik burada recover ile yakalanıp yalnızca bir doğruluk değeri olarak okunacak.

// olcum3.go — sinirlayici olcum: temiz cikmak kusursuz demek degil
package main

import "fmt"

const kalipYokSayilanHata = "package main\n\nimport \"fmt\"\n\ntype Kayit struct{ Ad string }\n\nfunc bul(anahtar string) (*Kayit, error) {\n\tif anahtar == \"\" {\n\t\treturn nil, fmt.Errorf(\"anahtar bos\")\n\t}\n\treturn &Kayit{Ad: anahtar}, nil\n}\n\nfunc main() {\n\tk, _ := bul(\"\")\n\tfmt.Println(k.Ad)\n}\n"

// Kayit ve bul, yukaridaki kaynagin gercek, calistirilabilir bir eslesi: cozumleyicinin
// okudugu metinle birebir ayni mantigi tasiyor.
type Kayit struct{ Ad string }

func bul(anahtar string) (*Kayit, error) {
	if anahtar == "" {
		return nil, fmt.Errorf("anahtar bos")
	}
	return &Kayit{Ad: anahtar}, nil
}

// dene, hatayi yok sayan cagriyi kosturur ve panigi yakalar; yigit izi hicbir yerde basilmaz.
func dene() (panikledi bool) {
	defer func() {
		if r := recover(); r != nil {
			panikledi = true
		}
	}()
	k, _ := bul("")
	_ = k.Ad
	return false
}

func main() {
	dizin := paket(map[string]string{"prog.go": kalipYokSayilanHata})

	fmt.Println("-- sinirlayici olcum: temiz cikmak kusursuz demek degil --")
	goster("cozumleme sonucu", "arac", fmt.Sprint("temiz=", temizMi(dizin)))
	goster("calistirildiginda panikledi mi", "kosum", fmt.Sprint(dene()))
}
-- sinirlayici olcum: temiz cikmak kusursuz demek degil --
cozumleme sonucu                         arac    temiz=true
calistirildiginda panikledi mi           kosum   true

DR17. Çözümleyicinin verdiği yanıt (temiz=true) ve koşumun verdiği yanıt (panikledi=true) aynı kaynak hakkında iki ayrı şey söylüyor ve ikisi de doğru. Çözümleyici “aradığım kalıplardan hiçbirini bulamadım” diyor — bu, bir hatanın yok sayılmasını arayan bir kural içermiyor. Koşum ise “bu kaynak, bu girdiyle çalıştırıldığında çöküyor” diyor. Bu iki yanıt çelişmiyor; ikisi de aracın kapsamıyla sınırlı. temiz=true satırı bu yüzden “bu kaynakta kusur yok” diye okunamaz — yalnızca “çözümleyicinin bildiği kalıplardan hiçbiri bu kaynakta yok” diye okunabilir. Bu, kursun daha önce kapsama oranı için kurduğu ayrımın aynısı: bir aracın sessiz kalması, o aracın aramadığı şeyin var olmadığı anlamına gelmiyor.

Bu sınırın pratikteki karşılığı, bir çözümleyicinin kural listesinin sabit olmadığı ama her zaman sonlu olduğu. Yeni bir kalıp fark edildikçe listeye yeni bir kural eklenebiliyor — bu ders özelinde kullanılan iki kural (biçim uyuşmazlığı, kilit kopyalama) da böyle eklenmiş, gerçek bir hatanın gerçek programlarda tekrar tekrar görülmesinden sonra listeye girmiş kurallar. Ama liste ne kadar büyürse büyüsün, her zaman henüz eklenmemiş bir kalıp kalıyor; bu ders bu boşluğu somutlaştırmak için tek bir örnek seçti, ama seçilen örneğin kendisi önemli değil — önemli olan, çözümlemenin temiz yanıtının her zaman “şu ana kadar bilinen kurallara göre temiz” anlamına geldiği, mutlak bir doğruluk iddiası taşımadığı.

Bilinen Açık Taraması: Modellenen Bir Liste

Çözümleyicinin aradığı kalıplar dilin kendi kurallarıyla sınırlı. Bir bağımlılığın taşıdığı bilinen bir açık (vulnerability) ise ayrı bir bilgi türü — kodun kendi biçiminden değil, o bağımlılığın hangi sürümde düzeltildiğinden geliyor. Bilinen açık taraması (known vulnerability scan), bir bağımlılığın bildirilen sürümünü, düzeltmenin geldiği sürümle karşılaştırıyor. Aşağıdaki ölçüm gerçek bir açık listesine hiç bağlanmıyor; karşılaştırma kaynak içinde, küçük bir eşik tablosuyla modelleniyor.

// olcum4.go — bilinen acik taramasi, modellenen bir esik listesiyle kurulan bir arac
package main

import "fmt"

// Surum uc parcali bir surum numarasini tasir (M09/K01 go-packages/02'deki kalibin aynisi).
type Surum struct{ Ana, Ikincil, Yama int }

func (s Surum) metin() string { return fmt.Sprintf("%d.%d.%d", s.Ana, s.Ikincil, s.Yama) }

func (s Surum) kucukMu(diger Surum) bool {
	if s.Ana != diger.Ana {
		return s.Ana < diger.Ana
	}
	if s.Ikincil != diger.Ikincil {
		return s.Ikincil < diger.Ikincil
	}
	return s.Yama < diger.Yama
}

// guvenliEsik, her modul icin bilinen en dusuk guvenli surumu tutan MODELLENMIS bir tablo;
// gercek bir aciklar veritabani degil, yalnizca bu olcumun kendi kurdugu kucuk bir liste.
var guvenliEsik = map[string]Surum{
	"kutuphaneX": {1, 4, 0},
	"kutuphaneY": {2, 0, 0},
}

type Bagimlilik struct {
	Modul      string
	Bildirilen Surum
}

// acikVarMi, bildirilen surumun modellenen esikten dusuk olup olmadigina bakar.
func acikVarMi(b Bagimlilik) bool {
	esik, varMi := guvenliEsik[b.Modul]
	if !varMi {
		return false
	}
	return b.Bildirilen.kucukMu(esik)
}

func main() {
	bagimliliklar := []Bagimlilik{
		{Modul: "kutuphaneX", Bildirilen: Surum{1, 2, 0}},
		{Modul: "kutuphaneY", Bildirilen: Surum{2, 3, 0}},
	}

	fmt.Println("-- bilinen acik taramasi: modellenen bir liste --")
	acikSayisi := 0
	for _, b := range bagimliliklar {
		a := acikVarMi(b)
		if a {
			acikSayisi++
		}
		goster(b.Modul+" bildirilen="+b.Bildirilen.metin(), "arac", fmt.Sprint("acik=", a))
	}
	goster("iki bagimliliktan acik bulunan sayisi", "arac", fmt.Sprint(acikSayisi))
}
-- bilinen acik taramasi: modellenen bir liste --
kutuphaneX bildirilen=1.2.0              arac    acik=true
kutuphaneY bildirilen=2.3.0              arac    acik=false
iki bagimliliktan acik bulunan sayisi    arac    1

DR18. kutuphaneX için bildirilen sürüm (1.2.0), modellenen eşiğin (1.4.0) altında kalıyor — açık var. kutuphaneY için bildirilen sürüm (2.3.0) eşiği (2.0.0) aşıyor — açık yok. Bu karşılaştırma da önceki iki ölçümle aynı sınırı taşıyor: eşik tablosunun kendisi bu ölçümün kurduğu bir modeldir, gerçek bir kayıttan gelmiyor ve bir modül tabloda hiç yoksa acikVarMi sessizce false dönüyor — tablo eksikse tarama hiçbir şey bulmuyor, tıpkı çözümleyicinin aramadığı kalıbı hiç görmediği gibi. M09/K01 Go Temelleri’nin en düşük sürüm seçimi ölçümü hangi sürümün seçildiğini kurmuştu; bu ölçüm o seçilen sürümün bilinen bir eşiğin altında kalıp kalmadığını, ayrı ve modellenmiş bir karşılaştırmayla soruyor.

Özet

  • Statik çözümleme kaynağı hiç çalıştırmadan, belirli kalıpları arayarak çalışır; derleyicinin kabul ettiği iki kaynak arasındaki farkı yalnızca çıkış koduyla bildirir.
  • Derleyicinin hiçbirini reddetmediği üç kalıptan ikisi çözümleyici tarafından yakalandı; biri — hatanın yok sayılması — temiz çıktı.
  • Sınırlayıcı ölçüm: temiz çıkan kalıp, gerçekten koşturulduğunda çöktü; çözümlemenin sessizliği kusursuzluk değil, aranan kalıbın bulunmaması anlamına geliyor.
  • Bilinen açık taraması, bir bağımlılığın bildirilen sürümünü modellenmiş bir eşikle karşılaştırıyor; tablo eksikse tarama hiçbir şey bulamıyor.
  • Bu ders boyunca kullanılan ölçü hep aynıydı: bir aracın sessiz kalması, aradığı şeyin bulunmadığını gösteriyor, o şeyin var olmadığını göstermiyor.

Sonraki Adım

Bu derste ölçülen çözümleyici, dilin kendi sınırları içinde kaldı — aradığı kalıplar hep Go kaynağının kendi söz dizimine ve tipine aitti. Kursun kapanış dersi bu sınırı aşıyor: bir Go programı başka bir dilin kütüphanesini çağırdığında, çözümleyicinin, çözümleyicinin göremediği bir sınır daha açılıyor. Sıradaki soru, dilin kendi sınırının ötesine geçmenin bedelinin hangi araçla, hangi sayıyla görüneceği.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat