임의 코드의 안전성을 프로그래밍 방식으로 평가할 수 있습니까?


10

안전한 코드에 대해 최근 많이 생각하고 있습니다. 스레드 안전. 메모리 안전. Segfault 안전 장치를 사용하여 폭발하지 마십시오. 그러나 문제의 명확성을 위해 Rust의 안전 모델을 진행 정의로 사용합시다.

Rust의 필요성에 의해 입증 된 바와 unsafe같이 키워드 를 사용하지 않고 Rust에서 구현할 수없는 매우 합리적인 프로그래밍 아이디어가 있기 때문에 안전을 보장하는 것은 종종 가장 큰 문제입니다. unsafe. 심지어 동시성 잠금, 뮤텍스, 채널 메모리 분리 또는 무엇을 가지고 완벽하게 안전 할 수 있지만,이 작업이 필요 외부 와 녹의 안전 모델을 unsafe수동으로하는 컴파일러를 확보 한 후, 그리고 그래, 내가 뭘하는지 알고있다 " 안전하지 않은 것처럼 보이지만 수학적으로 완벽하게 안전하다는 것이 입증되었습니다. "

그러나 일반적으로 이러한 것은 모델을 수동으로 만들고 정리 프로 버를 통해 안전하다는 것을 증명 합니다. 컴퓨터 과학의 관점 (가능한가)과 실용성의 관점 (우주의 생명을 취할 것인가)에서, 임의의 언어로 임의의 코드를 취하는 프로그램을 상상하는 것이 합리적입니까? 녹 안전 "?

주의 사항 :

  • 이것에 대한 쉬운 방법은 프로그램이 중단 되지 않을 수 있으므로 중단 문제 가 실패 한다는 것을 지적하는 것입니다. 독자에게 제공되는 모든 프로그램이 중지되었다고 가정 해 봅시다.
  • "임의의 언어로 된 임의의 코드"가 목표이지만, 물론 이것이 선택한 언어에 대한 프로그램의 친숙성에 달려 있음을 알고 있습니다.

2
임의 코드? 아니요. I / O 및 하드웨어 예외로 인해 가장 유용한 코드 의 안전성을 입증 할 수 없다고 생각합니다 .
Telastyn

7
왜 정지 문제를 무시하고 있습니까? 언급 한 예 중 하나 이상이 중단 문제, 기능 문제, 쌀 정리 또는 기타 많은 결정 불가능한 정리 (포인터 안전성, 메모리 안전성, 스레드)를 해결하는 것과 동등한 것으로 입증되었습니다. - 안전, 예외 안전성, 순도, I / O 안전 잠금 안전, 진행 보장 등 중단 문제 중 하나입니다 가장 간단한 정적 속성 당신이 가능하게 알고 자 수, 다른 모든 사용자 목록입니다 더 힘들어 .
Jörg W Mittag 10

3
오 탐지에만 관심이 있고 오탐을 기꺼이 받아들이
려는

당신은 절대적으로 하지 않습니다 사용할 필요가 unsafe동시 코드를 작성 녹. 동기화 기본 요소부터 배우 영감 채널에 이르기까지 다양한 메커니즘이 있습니다.
RubberDuck

답변:


8

우리가 궁극적으로 이야기하는 것은 컴파일 시간과 런타임입니다.

컴파일 시간 오류는 생각하면 결국 컴파일러가 프로그램을 실행하기 전에 프로그램에 어떤 문제가 있는지 확인할 수 있습니다. 분명히 "임의 언어"컴파일러는 아니지만 곧 다시 돌아올 것입니다. 그러나 컴파일러는 무한한 지혜로 컴파일러가 결정할 수있는 모든 문제를 나열하지는 않습니다 . 이것은 컴파일러가 얼마나 잘 작성되었는지에 부분적으로 의존하지만, 그 주된 이유는 런타임에 많은 것들이 결정되기 때문 입니다.

잘 알고 있듯이 런타임 오류는 프로그램 자체를 실행하는 동안 발생하는 모든 유형의 오류입니다. 여기에는 0, 널 포인터 예외, 하드웨어 문제 및 기타 여러 요인으로 나누는 것이 포함됩니다.

런타임 오류의 특성은 컴파일 타임에 해당 오류를 예상 할 수 없음을 의미합니다. 가능하다면 컴파일 타임에 거의 확실하게 확인됩니다. 컴파일 타임에 숫자가 0임을 보장 할 수 있으면 숫자를 숫자로 나누는 것과 같은 특정 논리적 결론을 수행 할 수 있습니다 .0으로 나누면 산술 오류가 발생합니다.

따라서 프로그램의 올바른 기능을 프로그래밍 방식으로 보장하는 적은 컴파일 시간 검사와 달리 런타임 검사를 수행하는 것입니다. 이에 대한 예는 다른 유형으로 동적 캐스트를 수행하는 것일 수 있습니다. 이것이 허용되면 프로그래머는 본질적으로 컴파일러가 안전한지 알 수있는 컴파일러의 능력을 무시합니다. 일부 프로그래밍 언어는 이것이 수용 가능하다고 결정한 반면, 다른 프로그래밍 언어는 적어도 컴파일 타임에 경고합니다.

또 다른 좋은 예는 널을 허용하면 널 포인터 예외가 발생할 수 있기 때문에 널을 언어의 일부로 허용하는 것입니다. 일부 언어에서는 명시 적으로 선언되지 않은 변수가 값을 즉시 할당하지 않고 선언 될 널 값을 보유 할 수 없도록하여이 문제를 완전히 제거했습니다 (예 : Kotlin 사용). 널 포인터 예외 런타임 오류를 제거 할 수는 없지만 언어의 동적 특성을 제거하여 오류가 발생하지 않도록 할 수 있습니다. Kotlin에서는 null 값을 보유 할 수 있는 가능성을 강요 할 수 있지만, 명시 적으로 명시해야하므로 이것은 은유 적 "구매자"라는 것은 말할 것도 없습니다.

개념적으로 모든 언어에서 오류를 확인할 수있는 컴파일러가 있습니까? 예, 그러나 미리 컴파일되는 언어를 반드시 제공해야하는 어색하고 불안정한 컴파일러 일 것입니다. 또한 프로그램에 대해 많은 것을 알 수 없었습니다. 특정 언어의 컴파일러는 언급 한 중지 문제와 같이 특정 언어에 대한 특정 내용을 알고 있습니다. 알다시피, 프로그램에 대해 배우는 데 흥미로울만한 많은 정보는 수집하기가 불가능합니다. 이것은 입증되었으므로 조만간 변경되지 않을 것입니다.

기본 지점으로 돌아 가기 메소드는 자동으로 스레드 안전하지 않습니다. 이에 대한 실질적인 이유가 있는데, 이는 스레드를 사용하지 않는 경우에도 스레드 안전 방법이 느려지기 때문입니다. Rust는 기본적으로 메소드를 스레드로부터 안전하게 만들어 런타임 문제를 제거 할 수 있다고 결정합니다. 그래도 비용이 듭니다.

수학적으로 프로그램의 정확성을 수학적으로 증명하는 것이 가능할 수 있지만, 언어에서 런타임 기능이 문자 그대로 0이라는 경고가 있습니다. 이 언어를 읽고 놀라지 않고 그 언어가 무엇을하는지 알 수있을 것입니다. 이 언어는 아마도 수학적으로 매우 수학적으로 보일 것이며, 우연의 일치는 아닐 것입니다. 두 번째 경고는 런타임 오류가 여전히 발생하며 프로그램 자체와 관련이 없을 수 있다는 것입니다. 따라서 프로그램이가 실행중인 컴퓨터에 대한 가정의 일련의 가정, 올바른 입증 할 수는 정확 물론 항상의 변화하지 않는 어쨌든 종종 발생합니다.


3

타입 시스템은 정확성의 일부 측면을 자동으로 검증 할 수있는 증거입니다. 예를 들어 Rust의 유형 시스템은 참조가 참조 된 객체보다 오래 지속되지 않았거나 참조 된 객체가 다른 스레드에 의해 수정되지 않았 음을 증명할 수 있습니다.

그러나 유형 시스템은 상당히 제한적입니다.

  • 그들은 결정 성 문제에 빠르게 부딪칩니다. 특히, 타입 시스템 자체는 결정 가능해야하지만, 많은 실용적인 타입 시스템이 실수로 Turing Complete입니다 (템플릿으로 인한 C ++ 및 특성으로 인한 녹 포함). 또한 확인하는 프로그램의 특정 속성은 일반적으로 일부 프로그램이 중지되거나 분기되는지 여부를 결정할 수없는 경우가 많습니다.

  • 또한 타입 시스템은 선형 시간에 이상적으로 빠르게 실행되어야합니다. 가능한 모든 증명이 유형 시스템에 포함되어야하는 것은 아닙니다. 예를 들어 전체 프로그램 분석은 일반적으로 피할 수 있으며 증명은 단일 모듈 또는 기능에 적용됩니다.

이러한 제한으로 인해 유형 시스템은 증명하기 쉬운 상당히 약한 속성 만 확인하는 경향이 있습니다 (예 : 올바른 유형의 값으로 함수가 호출 됨). 그럼에도 불구하고 표현력이 실질적으로 제한되기 때문에 해결 방법 (예 interface{}: Go, dynamicC #, ObjectJava, void*C) 또는 정적 입력을 완전히 피하는 언어를 사용 하는 것이 일반적 입니다.

우리가 검증하는 속성이 강할수록 언어의 표현력이 떨어집니다. Rust를 작성했다면, 컴파일러가 정확성을 증명할 수 없었기 때문에 컴파일러가 겉보기에는 올바른 코드를 거부하는 이러한 "컴파일러와의 싸움"순간을 알게 될 것입니다. 어떤 경우에는, 우리가 정확성을 입증 할 수 있다고 믿는 경우에도 특정 프로그램을 Rust에 표현할 수 없습니다 . unsafeRust 또는 C # 의 메커니즘을 사용하면 유형 시스템의 한계를 벗어날 수 있습니다. 경우에 따라 런타임 검사 지연은 다른 옵션이 될 수 있습니다. 그러나 일부 유효하지 않은 프로그램은 거부 할 수 없습니다. 이것은 정의의 문제입니다. 타입 시스템에 관한 한 패닉이 안전하지만 반드시 프로그래머 나 사용자의 관점에서 볼 필요는 없습니다.

언어는 유형 시스템과 함께 설계됩니다. 새로운 유형의 시스템이 기존 언어에 적용되는 경우는 드물다 (예 : MyPy, Flow 또는 TypeScript 참조). 이 언어는 형식 주석을 제공하거나 쉽게 입증 할 수있는 제어 흐름 구조를 도입하여 형식 시스템에 맞는 코드를 쉽게 작성할 수 있도록 노력할 것입니다. 다른 언어는 다른 솔루션으로 끝날 수 있습니다. 예를 들어 Java에는 finalRust의 비 mut변수 와 유사하게 정확히 한 번만 할당 되는 변수 개념이 있습니다 .

final int x;
if (...) { ... }
else     { ... }
doSomethingWith(x);

Java에는 변수에 액세스하기 전에 모든 경로에 변수를 할당할지 또는 함수를 종료 할지를 결정하는 유형 시스템 규칙이 있습니다. 반대로 Rust는 선언되었지만 설계되지 않은 변수를 사용하지 않고 이러한 증명을 단순화하지만 제어 흐름 명령문에서 값을 반환 할 수 있습니다.

let x = if ... { ... } else { ... };
do_something_with(x)

이것은 과제를 알아낼 때 아주 작은 점처럼 보이지만 명확한 범위 지정은 평생 관련 증거에 매우 중요합니다.

Rust 스타일의 타입 시스템을 Java에 적용한다면, 그보다 훨씬 큰 문제가있을 것입니다. Java 객체에는 수명이 달지 않기 때문에 &'static SomeClass또는 로 취급해야합니다 Arc<dyn SomeClass>. 결과적인 증거가 약해질 것입니다. Java에는 유형 수준의 불변성 개념이 없으므로 유형 &&mut유형을 구별 할 수 없습니다 . 우리는 객체를 셀 또는 뮤텍스로 취급해야하지만, 이는 실제로 Java가 제공하는 것보다 더 강력한 보장을 가정 할 수 있습니다 (Java 필드 변경은 동기화되고 휘발성이 아닌 한 스레드 안전하지 않음). 마지막으로 Rust에는 Java 스타일 구현 상속 개념이 없습니다.

TL; DR : 타입 시스템은 정리가 잘되어 있습니다. 그러나 결정 가능성 문제 및 성능 문제로 인해 제한됩니다. 대상의 언어 구문이 필요한 정보를 제공하지 않거나 시맨틱이 호환되지 않을 수 있으므로 한 유형 시스템을 사용하여 다른 언어에 적용 할 수는 없습니다.


3

안전은 얼마나 안전합니까?

예, 그러한 검증자를 작성하는 것이 거의 가능합니다. 프로그램은 상수 UNSAFE를 반환해야합니다. 시간의 99 %가 맞을 것입니다

안전한 Rust 프로그램을 실행하더라도 누군가 실행 중에도 플러그를 뽑을 수 있기 때문에 이론적으로 의도하지 않아도 프로그램이 중단 될 수 있습니다.

그리고 서버가 벙커의 패러데이 케이지에서 실행 되더라도 이웃 프로세스는 행 해머 익스플로잇을 실행하고 안전한 Rust 프로그램 중 하나를 약간 뒤집을 수 있습니다.

내가 말하려는 것은 소프트웨어가 비 결정적 환경에서 실행되며 많은 외부 요인이 실행에 영향을 줄 수 있다는 것입니다.

농담, 자동 검증

위험한 프로그래밍 구조 (초기화되지 않은 변수, 버퍼 오버 플로우 등)를 발견 할 수있는 정적 코드 분석기 가 이미 있습니다. 이는 프로그램의 그래프를 작성하고 제약 조건 전파 (유형, 값 범위, 시퀀싱)를 분석하여 작동합니다.

이런 종류의 분석은 최적화를 위해 일부 컴파일러에서도 수행됩니다.

한 걸음 더 나아가 동시성을 분석하고 여러 스레드, 동기화 및 경주 조건에서 제약 조건 전파에 대해 추론 할 수 있습니다. 그러나 매우 빠르게 실행 경로 사이의 조합 폭발 문제와 알려진 제약 조건을 노출시킬 수있는 많은 미지 (I / O, OS 스케줄링, 사용자 입력, 외부 프로그램의 동작, 중단 등)에 빠질 수 있습니다. 임의의 코드에 대해 유용한 자동 결론을 내리기가 매우 어렵습니다.


1

튜링은 1936 년에 멈춤 문제에 관한 논문을 통해이를 해결했다. 결과 중 하나는 시간의 100 %가 코드를 분석하고 코드의 정지 여부를 정확하게 결정할 수있는 알고리즘을 작성하는 것이 불가능하다는 것입니다. 시간의 100 %를 올바르게 할 수있는 알고리즘을 작성하는 것은 불가능합니다 "안전성"을 포함하여 코드에 특정 속성이 있는지 여부를 결정하십시오.

그러나 튜링의 결과는 (1) 코드가 안전하다고 판단하거나 (2) 코드가 안전하지 않다고 판단하거나 (3) 의인화하여 손을 내밀어 말할 수있는 프로그램의 가능성을 배제하지 않습니다. "헥크, 모르겠다." Rust의 컴파일러는 일반적으로이 범주에 속합니다.


"확실하지 않은"옵션이 있다면, 맞습니까?
TheEnvironmentalist

1
테이크 아웃은 프로그램 분석 프로그램을 혼동 할 수있는 프로그램을 항상 작성할 수 있다는 것입니다. 완벽은 불가능합니다. 실용성이 가능할 수 있습니다.
NovaDenizen

1

프로그램이 전체 (정지 된 프로그램의 기술적 이름) 인 경우 이론적으로 충분한 리소스가 제공되는 프로그램에 대한 임의의 속성을 증명할 수 있습니다. 프로그램이 입력 할 수있는 모든 잠재적 상태를 탐색하고 그 중 어느 것이 귀하의 재산을 위반하는지 확인할 수 있습니다. TLA + 모델 검사 언어는 오히려 모든 상태를 계산하는 것보다, 잠재적 인 프로그램 상태의 세트에 대한 귀하의 속성을 확인하기 위해 집합 이론을 사용하여,이 방법의 변형을 사용합니다.

기술적으로 실제 실제 하드웨어에서 실행되는 모든 프로그램은 전체 저장 공간이거나 사용 가능한 저장 공간이 제한되어 있기 때문에 가능한 루프이므로 컴퓨터가 사용할 수있는 상태는 유한합니다. 컴퓨터는 실제로 튜링 완료가 아닌 유한 상태 머신이지만 상태 공간이 너무 커서 회전이 완료되는 것처럼 가장 쉽습니다).

이 접근 방식의 문제점은 프로그램의 저장 용량과 크기에 기하 급수적으로 복잡하여 알고리즘의 핵심 이외의 다른 것에 비실용적이며 중요한 코드 기반에는 전체적으로 적용 할 수 없다는 것입니다.

따라서 대부분의 연구는 증거에 중점을 둡니다. Curry–Howard 서신은 정확성 증명과 유형 시스템이 하나이고 같은 것이므로 대부분의 실제 연구는 유형 시스템의 이름으로 진행됩니다. 이 논의 와 특히 관련이있는 것은 CoqIdriss이미 언급 한 녹 외에 Coq는 다른 방향에서 근본적인 엔지니어링 문제에 접근합니다. Coq 언어에서 임의 코드의 정확성을 증명하면 입증 된 프로그램을 실행하는 코드를 생성 할 수 있습니다. 한편 Idriss는 종속 유형 시스템을 사용하여 순수한 Haskell과 같은 언어로 임의 코드를 증명합니다. 이 두 언어의 기능은 작동 가능한 증명을 작성기에 작성하는 어려운 문제를 작성기에 전달하여 형식 검사기가 증명을 확인하는 데 집중할 수 있도록하는 것입니다. 증명을 확인하는 것은 훨씬 간단한 문제이지만 언어를 다루기가 훨씬 더 어려워집니다.

순도를 사용하여 프로그램의 어떤 부분과 관련된 상태를 제어하기 위해 증거를 쉽게 만들 수 있도록 특별히 설계된 두 언어 많은 주류 언어의 경우, 프로그램의 일부 증명과 관련이없는 상태를 입증하는 것만으로 부작용 및 변경 가능한 값의 특성으로 인해 복잡한 문제가 될 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.