Quali sono i vantaggi in termini di sicurezza di un sistema di tipi?


47

In JavaScript: The Good Parts di Douglas Crockford, menziona il suo capitolo sull'eredità,

L'altro vantaggio dell'eredità classica è che include la specifica di un sistema di tipi. Questo in gran parte libera il programmatore dal dover scrivere operazioni di casting esplicite, il che è un'ottima cosa perché durante il casting, i vantaggi di sicurezza di un sistema di tipo vengono persi.

Quindi, prima di tutto, cos'è veramente la sicurezza? protezione da corruzione dei dati, hacker o malfunzionamenti del sistema, ecc.?

Quali sono i vantaggi in termini di sicurezza di un sistema di tipi? Cosa rende diverso un sistema di tipi che gli consente di offrire questi vantaggi in termini di sicurezza?


Non sono sicuro che i sistemi di tipi offrano alcun vantaggio per il linguaggio non compilato, ma come utente a lungo termine di linguaggi compilati, trovo che i linguaggi compilati con un attento controllo dei tipi siano efficaci nel prevenire molti tipi di codice ambiguo, indefinito o incompleto da superare la fase di "compilazione". Immagino che potresti dire che i suggerimenti sul tipo e un sistema Lint sono utili per gli script Web (JavaScript) e, in tal caso, sono sicuro che ne vedremo abbastanza. Dardeggia qualcuno? Linguaggi dinamici come Python non sembrano peggiorare per la mancanza di un sistema di tipo statico.
Warren P

1
Oggi comprendiamo che la digitazione dovrebbe essere comportamentale e non strutturale. Purtroppo, la maggior parte dei linguaggi di programmazione moderni non ha modo di affermare il comportamento di un tipo ( vedi questa domanda per una buona lettura). Ciò rende il sistema di tipi piuttosto inutile nella maggior parte dei casi, soprattutto perché semplici errori di tipo che rispondono alle citazioni qui possono essere colti da una linter intelligente che verifica i problemi comuni.
Benjamin Gruenbaum,

4
@BenjaminGruenbaum Quello che la tua descrizione esiste già in lingue come OCaml staticamente. Si chiama tipizzazione strutturale, in realtà è piuttosto vecchia, la tipizzazione nominale è più recente.
jozefg,

2
@BenjaminGruenbaum: ... What !? Ovviamente non è indecidibile nelle lingue tipicamente statiche, altrimenti sarebbe impossibile scrivere un compilatore per quelle lingue.
BlueRaja - Danny Pflughoeft,

6
@BenjaminGruenbaum: I tuoi commenti sono preziosi e quel documento è interessante, ma non conferma la tua affermazione che "di solito è indecidibile anche in linguaggi statici come Java", dal momento che dimostra che è decidibile in C # e lascia aperta la domanda se è indecidibile in Java. (E comunque, IME, quando un compilatore per un linguaggio tipizzato staticamente non può decidere che qualcosa è ben digitato, lo rifiuta (o non riesce a compilarlo), quindi l'indecidibilità è un fastidio piuttosto che un buco nel tipo- sicurezza.)
ruakh,

Risposte:


82

I sistemi di tipo impediscono errori

I sistemi di tipo eliminano i programmi illegali. Considera il seguente codice Python.

 a = 'foo'
 b = True
 c = a / b

In Python, questo programma fallisce; genera un'eccezione. In un linguaggio come Java, C #, Haskell , qualunque cosa, questo non è nemmeno un programma legale. Si evitano del tutto questi errori perché semplicemente non sono possibili nel set di programmi di input.

Allo stesso modo, un sistema di tipo migliore esclude più errori. Se passiamo ai sistemi di tipo super avanzato possiamo dire cose del genere:

 Definition divide x (y : {x : integer | x /= 0}) = x / y

Ora il sistema dei tipi garantisce che non vi siano errori di divisione per 0.

Che tipo di errori

Ecco un breve elenco di quali tipi di errori possono impedire i sistemi

  1. Errori fuori portata
  2. SQL Injection
  3. Generalizzando 2, molti problemi di sicurezza (a cosa serve il controllo dei contaminanti in Perl )
  4. Errori fuori sequenza (dimenticando di chiamare init)
  5. Forzare un sottoinsieme di valori da utilizzare (ad esempio, solo numeri interi maggiori di 0)
  6. Gattini malvagi (Sì, era uno scherzo)
  7. Errori di perdita di precisione
  8. Errori di memoria transazionale del software (STM) (ciò richiede purezza, che richiede anche tipi)
  9. Generalizzando 8, controllando gli effetti collaterali
  10. Invarianti su strutture di dati (un albero binario è bilanciato?)
  11. Dimenticare un'eccezione o lanciare quella sbagliata

E ricorda, questo è anche in fase di compilazione . Non è necessario scrivere test con una copertura del codice del 100% per verificare semplicemente la presenza di errori di tipo, il compilatore fa proprio per te :)

Caso di studio: calcolo lambda tipizzato

Bene, esaminiamo i più semplici sistemi di tutti i tipi, semplicemente digitati calcolo lambda .

Fondamentalmente ci sono due tipi,

Type = Unit | Type -> Type

E tutti i termini sono variabili, lambda o applicazione. Sulla base di questo, possiamo dimostrare che qualsiasi programma ben scritto termina. Non c'è mai una situazione in cui il programma rimarrà bloccato o in loop per sempre. Questo non è dimostrabile nel normale calcolo lambda perché non è vero.

Pensa a questo, possiamo usare i sistemi di tipi per garantire che il nostro programma non funziona in loop per sempre, piuttosto bello vero?

Deviazione in tipi dinamici

I sistemi di tipo dinamico possono offrire garanzie identiche ai sistemi di tipo statico, ma in fase di esecuzione anziché in fase di compilazione. In realtà, dal momento che è runtime, puoi effettivamente offrire ulteriori informazioni. Si perdono tuttavia alcune garanzie, in particolare su proprietà statiche come la risoluzione.

Quindi i tipi dinamici non escludono determinati programmi, ma indirizzano piuttosto programmi malformati ad azioni ben definite, come il lancio di eccezioni.

TLDR

Quindi, in sostanza, i sistemi di tipo escludono determinati programmi. Molti programmi sono in qualche modo interrotti, quindi con sistemi di tipo evitiamo questi programmi interrotti.


25
+1 per la compilazione come equivalente alla scrittura di molti test.
Dan Neely,

3
@DanNeely È solo per illustrare che in un linguaggio dinamico, è necessario esercitare tutte le parti del codice per rilevare gli errori che un sistema di tipo controlla gratuitamente. E in un linguaggio tipicamente dipendente, puoi effettivamente sostituire completamente i test con i tipi. Molte volte è necessario dimostrare ulteriori teoremi di correttezza
jozefg

3
Se il tuo sistema di tipi ha dimostrato che il tuo programma deve terminare, è (probabilmente) fatto dimostrando che sta calcolando una funzione primitiva-ricorsiva. Il che è bello suppongo, ma una classe di complessità significativamente meno interessante di quella che una vera macchina di Turing può risolvere. (Non significa che i valori intermedi non siano grandi; la funzione di Ackermann è primitiva-ricorsiva ...)
Donal Fellows

5
@DonalFellows La funzione Ackermann non è ricorsiva primitiva, sebbene sia una funzione calcolabile totale.
Taymon,

4
@sacundim Esattamente, lingue come agda consentono il controllo opzionale della totalità e nei rari casi in cui si desidera una ricorsione arbitraria che si può chiedere in modo piacevole, è un sistema piuttosto fluido.
jozefg,

17

La realtà stessa è digitata. Non è possibile aggiungere lunghezze ai pesi. E mentre puoi aggiungere piedi ai metri (entrambi sono unità di lunghezza), dovresti ridimensionare almeno uno dei due. In caso contrario, la tua missione su Marte può andare in crash, letteralmente.

In un sistema typesafe, aggiungere due lunghezze espresse in unità diverse sarebbe stato un errore o avrebbe causato un cast automatico.


15

Un sistema di tipi ti aiuta a evitare semplici errori di codifica o piuttosto consente al compilatore di rilevare tali errori per te.

Ad esempio, in JavaScript e Python, il seguente problema viene spesso rilevato solo in fase di esecuzione e, a seconda del test, la qualità / rarità della condizione può effettivamente arrivare alla produzione:

if (someRareCondition)
     a = 1
else
     a = {1, 2, 3}

// 10 lines below
k = a.length

Mentre un linguaggio fortemente tipizzato ti costringerà a dichiarare esplicitamente che aè un array e non ti permetterà di assegnare un numero intero. In questo modo, non c'è alcuna possibilità ache non ci sia length, anche nei casi più rari.


5
E una linter intelligente in un IDE come WebStorm JavaScript può dire "Possibile riferimento indefinito a a.length per il numero a". Questo non ci è dato da un sistema di tipo esplicito.
Benjamin Gruenbaum,

4
1. Staticamente non fortemente 2. @BenjaminGruenbaum Sì, ma questo viene fatto inseguendo un grafico dei compiti in background, pensalo come un mini interprete che cerca di capire dove stanno andando le cose. Molto più difficile di quando i tipi ti danno gratuitamente
jozefg,

6
@BenjaminGruenbaum: non confondere implicito / esplicito con forte / debole. Haskell, ad esempio, ha un sistema di tipi incredibilmente forte che fa vergognare la maggior parte delle altre lingue, ma a causa di determinate decisioni di progettazione linguistica prese è anche in grado di inferire il tipo quasi del tutto universale, rendendolo essenzialmente un linguaggio tipizzato fortemente implicito con supporto per la digitazione esplicita (Che dovresti usare, perché il tipo inferencer può solo dedurre da ciò che hai scritto, non da ciò che intendevi!)
Phoshi,

6
"Un linguaggio fortemente tipizzato ti costringerà a dichiarare esplicitamente che a è un array" È sbagliato. Python è fortemente tipizzato e non lo richiede. Anche le lingue tipicamente e fortemente tipizzate non richiedono che se supportano l'inferenza di tipo (e la maggior parte delle lingue tradizionali al giorno d'oggi lo fanno, almeno in parte).
Konrad Rudolph,

1
@BenjaminGruenbaum: Ah, abbastanza giusto. Anche così, ci saranno casi in cui nessun analizzatore statico JS è in grado di eseguire gli stessi tipi di controllo tipografico che un linguaggio fortemente tipizzato fornirebbe, risolvendo che nel caso generale è necessario risolvere il problema di arresto. Haskell ha dovuto prendere alcune giuste decisioni di progettazione per ottenere un'inferenza di tipo quasi al 100% e C # / Scala non può dedurre tutto. Naturalmente, in quei casi, non importa perché puoi semplicemente specificare esplicitamente i tipi: in Javascript, significa che anche il miglior analizzatore statico non può più controllare il tuo codice.
Phoshi,

5

Quanto prima nel ciclo di sviluppo del software è possibile rilevare un errore, tanto meno è costoso da correggere. Prendi in considerazione un errore che causa la perdita di dati da parte del tuo cliente più grande o di tutti i tuoi clienti. Un tale errore potrebbe essere la fine della tua azienda se viene rilevato solo dopo che i clienti reali hanno perso i dati! È chiaramente meno costoso trovare e correggere questo bug prima di spostarlo in produzione.

Anche per errori meno costosi, più tempo ed energia vengono spesi se i tester sono coinvolti che se i programmatori riescono a trovarli e risolverli. È più economico se non viene controllato nel controllo del codice sorgente in cui altri programmatori possono creare software che si basa su di esso. La sicurezza del tipo impedisce la compilazione uniforme di alcune classi di errori, eliminando così quasi l'intero costo potenziale di tali errori.

Ma questa non è tutta la storia. Come chiunque ti dirà in un linguaggio dinamico ti dirà, a volte è bello se il tuo programma si compila in modo da poter provare una parte di esso senza ottenere tutti i piccoli dettagli. C'è un compromesso tra sicurezza e convenienza. I test unitari possono mitigare alcuni dei rischi derivanti dall'uso di un linguaggio dinamico, ma la scrittura e il mantenimento di buoni test unitari ha un costo che potrebbe essere superiore a quello dell'uso di un linguaggio sicuro.

Se stai sperimentando, se il tuo codice verrà utilizzato solo una volta (come un rapporto una tantum), se ti trovi in ​​una situazione in cui non ti preoccuperesti di scrivere un test unitario, allora un linguaggio dinamico è probabilmente perfetto per te. Se si dispone di un'applicazione di grandi dimensioni e si desidera modificare una parte senza interromperne il resto, la sicurezza del tipo è un salvavita. I tipi di errori di tipo catture di sicurezza sono esattamente il tipo di errori che gli umani tendono a trascurare o sbagliare durante il refactoring.


Questo vende la tipizzazione dinamica in breve, senza menzionare i suoi vantaggi principali (quelli menzionati sono utili da relativamente poco importanti). Sembra anche implicare qualcosa di strano nei test unitari: sì, sono difficili da fare e hanno un costo, e questo vale anche per le lingue tipizzate staticamente. Cosa sta cercando di dire? Inoltre, non menziona i limiti (in base alla progettazione) degli attuali sistemi di tipo, sia in ciò che possono esprimere sia in quali errori possono rilevare.

@MattFenwick quali ritieni siano i principali vantaggi della digitazione dinamica?
GlenPeterson,

I sistemi di tipo statico tipico rifiutano molti programmi ben tipizzati in base alla progettazione. ( un'alternativa ) (A proposito, la mia critica era diretta solo al 3 ° e 4 ° paragrafo.)

4

introduzione

La sicurezza del tipo può essere ottenuta con linguaggi di tipo statico (compilato, controllo statico del tipo) e / o di runtime (valutato, controllo dinamico del tipo). Secondo Wikipedia un '... sistema di tipo forte è descritto come un sistema in cui non esiste la possibilità di un errore di tipo runtime non controllato (ed Luca Cardelli). In altri termini, l'assenza di errori di runtime non controllati viene definita sicurezza o tipo di sicurezza ... "

Sicurezza - Controllo statico del tipo

Classicamente, la sicurezza dei tipi è stata sinonimo di tipizzazione statica, in linguaggi come C, C ++ e Haskell, progettati per rilevare errori di corrispondenza dei tipi quando vengono compilati. Ciò ha il vantaggio di evitare condizioni potenzialmente indefinite o soggette a errori durante l'esecuzione del programma. Ciò può avere un valore inestimabile quando esiste il rischio che i tipi di puntatori possano essere erroneamente abbinati, ad esempio una situazione che potrebbe portare a conseguenze catastrofiche se non rilevata. In questo senso la digitazione statica è considerata sinonimo di sicurezza della memoria.

La digitazione statica non è completamente sicura ma migliora la sicurezza , tuttavia. Anche i sistemi di tipo statico possono avere conseguenze catastrofiche. Molti esperti ritengono che il tipo statico possa essere utilizzato per scrivere sistemi più robusti e meno soggetti a errori (mission-critical).

I linguaggi tipizzati staticamente possono aiutare a ridurre il rischio di perdita di dati o perdita di accuratezza nel lavoro numerico, che può verificarsi a causa di errata corrispondenza o troncamento dei tipi di integrale e float double to float.

Vi è un vantaggio nell'utilizzo di linguaggi di tipo statico per efficienza e velocità di esecuzione. Il runtime beneficia del fatto di non dover determinare i tipi durante l'esecuzione.

Sicurezza - Verifica del tipo di runtime

Erlang, ad esempio, è un linguaggio dichiarativo di tipo, controllato dinamicamente dal tipo di carattere che gira su una macchina virtuale. Il codice Erlang può essere compilato in byte. Erlang è considerato forse il più importante linguaggio mission-critical e tollerante ai guasti disponibile, ed è stato riferito che Erlang ha un'affidabilità di nove 9 (99.9999999% o non più di 31,5 msecs all'anno).

Alcune lingue, come Common Lisp, non sono tipizzate staticamente, ma i tipi possono essere dichiarati se lo si desidera, il che può aiutare a migliorare la velocità e l'efficienza. Va anche notato che molti dei linguaggi interpretati più usati, come Python, sono, sotto il ciclo di valutazione, scritti in linguaggi tipicamente statici come C o C ++. Sia Commom Lisp che Python sono considerati di tipo sicuro dalla definizione sopra.


2
Mi oppongo a "fortemente tipizzato". Vuoi dire digitati staticamente. Il tipo forte non ha praticamente alcun significato, è praticamente usato per dire "Mi piace questo sistema di tipi"
jozefg

@ jozefg Un buon punto. Modificherò il post.
AsymLabs

3
Inoltre, non è utile dire una lingua interpretata ... su un'implementazione della lingua sì, ma non la lingua stessa. Qualsiasi lingua può essere interpretata o compilata. E anche dopo la modifica stai usando i termini tipizzazione forte e debole.
Esailija,

3
@jozefg: ho sempre pensato che tipicamente fortemente significava che ogni valore ha un tipo fisso (es. intero, stringa, ecc.), mentre digitato debolmente significa che un valore può essere costretto a un valore di un altro tipo, se è ritenuto conveniente farlo così. Ad esempio, in Python (fortemente tipizzato), 1 + "1"genera un'eccezione, mentre in PHP (tipicamente debole) 1 + "1"produce 2(la stringa "1"viene automaticamente convertita in intero 1).
Giorgio,

1
@Giorgio con tale definizione, ad esempio, Java non è fortemente tipizzato. Ma in molti casi si afferma di esserlo. Non c'è proprio significato per queste parole. I caratteri forti / deboli hanno una definizione molto più accurata come "mi piace / non uso questa lingua" come dice jozefg.
Esailija,

1

i vantaggi in termini di sicurezza di un sistema di tipi vengono persi.

Quindi, prima di tutto, cos'è veramente la sicurezza? protezione da corruzione dei dati, hacker o malfunzionamenti del sistema, ecc.?

Quali sono i vantaggi in termini di sicurezza di un sistema di tipi? Cosa rende diverso un sistema di tipi che gli consente di offrire questi vantaggi in termini di sicurezza?

Sento che i sistemi di tipo hanno una visione così negativa. Un sistema di tipo riguarda più la garanzia che la prova dell'assenza di errori. Quest'ultima è una conseguenza del sistema dei tipi. Un sistema di tipi per un linguaggio di programmazione è un modo per produrre, in fase di compilazione, una prova che un programma soddisfa un qualche tipo di specifica.

Il tipo di specifica che si può codificare come tipo dipende dalla lingua, o più direttamente, dalla forza del sistema di tipi di lingua.

Il tipo più elementare di specifica è una garanzia sul comportamento di input / output delle funzioni e sulla validità dell'interno di un corpo di funzione. Considera un'intestazione di funzione

f : (Int,Int) -> String

Un buon sistema di tipo farà in modo che f sia applicato solo agli oggetti che produrranno una coppia di Int durante la valutazione e garantirà che f produrrà sempre una stringa.

Alcune istruzioni in una lingua, come i blocchi if-then, non hanno un comportamento input / output; qui il sistema di tipi garantisce che ogni dichiarazione o dichiarazione nel blocco sia valida; vale a dire operazioni per oggetti del tipo corretto. Queste garanzie sono componibili.

Inoltre, ciò fornisce una sorta di condizione di sicurezza della memoria. La citazione con cui hai a che fare riguarda il casting. In alcuni casi, il casting va bene, come il cast di un Int a 32 bit in un Int a 64 bit. Tuttavia, in genere, si blocca il sistema di tipi.

Tener conto di

Foo x = new Foo(3,4,5,6);
f((Int)x,(Int)x);

A causa del casting, x viene trasformato in Int, quindi tecnicamente quanto sopra digita il controllo; tuttavia, sconfigge davvero lo scopo del typechecking.

Una cosa che potrebbe creare un sistema di tipo diverso e migliore è di non consentire i cast (A) x dove x prima che il caso sia di tipo B, a meno che B non sia un sottotipo (o oggetto secondario) di A. Le idee della teoria dei sottotipi sono state utilizzate nella sicurezza per rimuovere la possibilità di attacchi di overflow / underflow di numeri interi.

Sommario

Un sistema di tipi è un modo per dimostrare che un programma soddisfa un tipo di specifica. I vantaggi che un sistema di tipi può offrire dipendono dalla forza del sistema di tipi utilizzato.


1

Un vantaggio non ancora menzionato per un sistema di tipi si basa sul fatto che molti programmi vengono letti più di quanto non siano scritti, e in molti casi un sistema di tipo può consentire di specificare molte informazioni in modo conciso e può essere facilmente digerito da qualcuno che legge il codice. Mentre i tipi di parametro non sostituiscono i commenti descrittivi, la maggior parte delle persone troverà più velocemente la lettura: "int Distance;" oDistance As Int32che leggere "La distanza deve essere un numero intero +/- 2147483647"; le frazioni di passaggio possono produrre risultati incoerenti. "Inoltre, i tipi di parametri possono aiutare a ridurre il divario tra ciò che accade a una particolare implementazione di un'API, rispetto a ciò su cui i chiamanti hanno il diritto di fare affidamento. Ad esempio, se una particolare implementazione Javascript di un'API utilizza i parametri in modo che costringere qualsiasi stringa in forma numerica, può essere chiaro se i chiamanti possono fare affidamento su tale comportamento, o se altre implementazioni del malfunzionamento API potenza se dato stringhe. Avere un metodo cui parametro è specificato come Doublesarebbe chiarire che qualsiasi valore di stringa deve essere forzato dal chiamante prima di essere passato; avere un metodo con un sovraccarico che accetta Doublee un altro che accettaString renderebbe in qualche modo più chiaro che i chiamanti in possesso di stringhe sarebbero autorizzati a passarli come tali.


0

Quindi, prima di tutto, cos'è veramente la sicurezza? Protezione dalla corruzione dei dati, da hacker o malfunzionamenti del sistema, ecc.?

Tutte le altre risposte e altro ancora. In generale, "sicurezza del tipo" significa semplicemente che nessuno dei programmi che un compilatore compila correttamente conterrà errori di tipo.

Ora, cos'è un errore di tipo? In linea di principio, è possibile specificare qualsiasi proprietà indesiderata come errore di tipo e alcuni sistemi di tipo saranno in grado di garantire staticamente che nessun programma presenti tale errore.

Per "proprietà" sopra, intendo un tipo di proposizione logica che si applica al tuo programma, ad esempio "tutti gli indici rientrano nei limiti dell'array". Altri tipi di proprietà includono "tutti i puntatori differiti sono validi", "questo programma non esegue alcun I / O" o "questo programma esegue I / O solo su / dev / null", ecc. Quasi ogni tipo di la proprietà può essere specificata e il tipo controllato in questo modo, a seconda dell'espressività del sistema di tipi.

I sistemi di tipo dipendenti sono tra i più generali di sistemi di tipo, tramite i quali è possibile applicare praticamente qualsiasi proprietà che ti piace. Non è necessariamente facile farlo, poiché le proprietà sofisticate sono soggette a incompletezza per gentile concessione di Gödel .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.