51
오류 연역 Deducing Errors http://ohyecloudy.com http://cafe.naver.com/architect1 2009-08-08

[Why Programs Fail] Deducing Errors, 오류 연역

  • Upload
    -

  • View
    1.189

  • Download
    3

Embed Size (px)

Citation preview

Page 1: [Why Programs Fail] Deducing Errors, 오류 연역

오류 연역Deducing Errors

http://ohyecloudy.com

http://cafe.naver.com/architect1 2009-08-08

Page 2: [Why Programs Fail] Deducing Errors, 오류 연역

연역(Deduction)

어떤 명제로부터 추론 규칙에 따라 결론을 이끌

어 냄. 또는 그런 과정. 일반적인 사실이나 원리를 전제로 하여 개별적인 사실이나 보다 특수한 다른 원리를 이끌어 내는 추

리를 이른다. 경험을 필요로 하지 않는 순수한 사유에 의하여 이루어지며 그 전형은 삼단 논법이다.

-네이버 국어 사전

Page 3: [Why Programs Fail] Deducing Errors, 오류 연역

프로그램 분석에서 연역은

추상적인 프로그램 코드에서 구체적인 프로그램 실행으로의 추론 기법

정적 분석 실제로 프로그램을 수행할 필요 없다.

Page 4: [Why Programs Fail] Deducing Errors, 오류 연역

코드만으로 연역해

낼 수 있는 일반적인

오류들은 무엇인가?

이 장의 주제

Page 5: [Why Programs Fail] Deducing Errors, 오류 연역

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

Page 6: [Why Programs Fail] Deducing Errors, 오류 연역

뭔가 불가능한 일이 일어났다면, 그리고 그에 대한 확실한 정보는 그것이 실제로 일어났다는 것뿐이라면, 결과로부터 거슬러 생각해서thinking backward 이유들을 찾아내야 한다.

- Kernighan & Pike(1999)

Page 7: [Why Programs Fail] Deducing Errors, 오류 연역

10 INPUT X

20 Y = 0

30 X = Y

40 PRINT “X = “, X

항상 X = 0 출력

어디에서 비롯되었을까?

거슬러 생각하기 실패 원인에 영향을 주는 문장을 찾는다.

Page 8: [Why Programs Fail] Deducing Errors, 오류 연역

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

Page 9: [Why Programs Fail] Deducing Errors, 오류 연역

$ gcc –o fibonacci fibonacci.c

$ ./fibonacci

fib(9) = 55

fib(8) = 34

fib(7) = 21

fib(6) = 13

fib(5) = 8

fib(4) = 5

fib(3) = 3

fib(2) = 2

fib(1) = 1291239

어디에서 비롯되었을까?

Page 10: [Why Programs Fail] Deducing Errors, 오류 연역

#include <stdio.h>

int fib( int n )

{

int f, f0 = 1, f1 = 1;

while( n > 1 ) {

n = n - 1;

f = f0 + f1;

f0 = f1;

f1 = f;

}

return f;

}

int main()

{

int n = 9;

while( n > 0 ) {

printf( "fib(%d) = %d\n", n, fib(n) );

n = n - 1;

}

return 0;

}

값에 영향을 미칠 수 있는 코드 영역을 식별 문장들이 수행되는 순서를 조사

Page 11: [Why Programs Fail] Deducing Errors, 오류 연역

짂입 : fib(n)

int f

int f0 = 1

int f1 = 1

while( n > 1 )

n = n - 1

f = f0 + f1

f0 = f1

f1 = f

return f

종료

문장 == 노드 갂선 == 제어흐름 A B A는 B에 앞선다. B는 A를 따른다.

Page 12: [Why Programs Fail] Deducing Errors, 오류 연역

제어 흐름의 난점들

점프와 goto goto 50

갂접 점프 goto x, 함수 포인터

동적 분배 shape.draw()

rectangle::draw()

circle::draw()

예외 throw new Exception(“제기랄”)

Page 13: [Why Programs Fail] Deducing Errors, 오류 연역

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

Page 14: [Why Programs Fail] Deducing Errors, 오류 연역

문장이 주는 영향

쓰기 : 프로그램 상태를 변경

v1 = 1

제어 : 프로그램 카운터 변경

while(n>1), if(n>1)

Page 15: [Why Programs Fail] Deducing Errors, 오류 연역

문장이 받는 영향

읽기 : 프로그램 상태를 읽는다.

v2 = v1 + 1

변수 v1이 가리키는 주소에서 값을 읽는다.

수행 : 프로그램 카운터가 현재 문장 을 가리키는가.

Page 16: [Why Programs Fail] Deducing Errors, 오류 연역

문장 읽기 쓰기 제어

0 fib( n ) n 1-9

1 int f f

2 f0 = 1 f0

3 f1 = 1 f1

4 while( n > 1 ) n 5-8

5 n = n – 1 n n

6 f = f0 + f1 f0, f1 f

7 f0 = f1 f1 f0

8 f1 = f f f1

9 return f f <반홖값>

Page 17: [Why Programs Fail] Deducing Errors, 오류 연역

종료

짂입 : fib(n)

int f

int f0 = 1

int f1 = 1

while( n > 1 )

n = n - 1

f = f0 + f1

f0 = f1

f1 = f

return f

제어 의존성 A는 B의 수행을 제어한다.

자료 의존성 A의 자료가 B에서 쓰인다.

Page 18: [Why Programs Fail] Deducing Errors, 오류 연역

짂입 : fib(n)

int f

int f0 = 1

int f1 = 1

while( n > 1 )

n = n - 1

f = f0 + f1

f0 = f1

f1 = f

return f

종료

이 값은 어디로 가는가?

n의 자료 의존성 n의 자료 의존성이 있는

while 문장의 제어 의존성

결국 n의 값은 f, f0, f1의 값을 결정

궁극적으로 f 값도 결정

Page 19: [Why Programs Fail] Deducing Errors, 오류 연역

이 값은 어디에서 왔는가? 짂입 : fib(n)

int f

int f0 = 1

int f1 = 1

while( n > 1 )

n = n - 1

f = f0 + f1

f0 = f1

f1 = f

return f

종료

f의 자료 의존성

결국 f(1)의 값은 초기화 되지 않은

임의의 값이 사용됐다.

Page 20: [Why Programs Fail] Deducing Errors, 오류 연역

의존성은 코드를 따라 항해하는데

중요한 지침

프로그래머들은 코드를 읽으면서

암묵적으로 의존성을 판단.

각 문장의 효과를 추정하는 것은

프로그램 이해의 일부.

의존성 활용

Page 21: [Why Programs Fail] Deducing Errors, 오류 연역

CodeSurfer의 의존성 추적

Page 22: [Why Programs Fail] Deducing Errors, 오류 연역

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

Page 23: [Why Programs Fail] Deducing Errors, 오류 연역

슬라이스slice

프로그램의 부분집합

슬라이싱slicing

슬라이스에 관한 연산

Page 24: [Why Programs Fail] Deducing Errors, 오류 연역

전방 슬라이스forward slice

A에 의해 변경된 변수를 읽거나

수행 여부가 A에 의해 결정되는

모든 문장

후방 슬라이스backward slice

B에 영향을 주는 모든 문장

BABASF

)(

BAABS B )(

A : 슬라이스 기죾

B : 슬라이스 기죾

Page 25: [Why Programs Fail] Deducing Errors, 오류 연역

0.짂입 : fib(n)

1.int f

2.int f0 = 1

3.int f1 = 1

4.while(n > 1)

5.n = n - 1

6.f = f0 + f1

7.f0 = f1

8.f1 = f

9.return f

종료

9,8,7,6,2)2( FS

Page 26: [Why Programs Fail] Deducing Errors, 오류 연역

9,8,7,6,5,4,3,2,1,0)9( BS0.짂입 : fib(n)

1.int f

2.int f0 = 1

3.int f1 = 1

4.while(n > 1)

5.n = n - 1

6.f = f0 + f1

7.f0 = f1

8.f1 = f

9.return f

종료

Page 27: [Why Programs Fail] Deducing Errors, 오류 연역

A전방 슬라이스 기죾가 B후방 슬라이스 기죾에

어떻게 영향을 주는가?

)()( BSAS BF 슬라이싱slicing

절단chop

중추backbone

입방체dice

0.짂입 : fib(n)

1.int f

2.int f0 = 1

3.int f1 = 1

4.while(n > 1)

5.n = n - 1

6.f = f0 + f1

7.f0 = f1

8.f1 = f

9.return f

종료

}8,6{)7()3( BF SS

f1의 초기값이 f0에 영향을 미칠 수 있는 모든 경로

Page 28: [Why Programs Fail] Deducing Errors, 오류 연역

슬라이싱slicing

절단chop

중추backbone

입방체dice 여러 값들의 계산에 기여하는

프로그램 부분들을 찾을 때

1. int main() { 2. int a, b, sum, mul; 3. sum = 0; 4. mul = 1; 5. a = read(); 6. b = read(); 7. while (a <= b) { 8. sum = sum + a; 9. mul = mul * a; 10. a = a + 1; 11. } 12. write(sum); 13. write(mul); 14.}

1. int main() { 2. 3. 4. 5. a = read(); 6. b = read(); 7. while (a <= b) { 8. 9. 10. a = a + 1; 11. } 12. 13. 14.}

)()( or or BSAS BFBF

)13()12( BB SS

Page 29: [Why Programs Fail] Deducing Errors, 오류 연역

슬라이싱slicing

절단chop

중추backbone

입방체dice 계산된 값들 대부분이 정확하지맊

일부만 그렇지 않을 때

)(\)( or or BSAS BFBF

1. int main() { 2. 3. 4. mul = 1; 5. 6. 7. 8. 9. mul = mul * a; 10. 11. 12. 13. write(mul); 14.}

)12(\)13( BB SS

정확한 변수의 후방 슬라이스 – 감염된 변수의 후방 슬라이스 = 감염된 값에 기여하는 문장들

Page 30: [Why Programs Fail] Deducing Errors, 오류 연역

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

Page 31: [Why Programs Fail] Deducing Errors, 오류 연역

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

int go; switch( color ) { case RED: case AMBER: go = 0; break; case GREEN: go = 1; break; } if( go ) { … }

color가 RED or AMBER or GREEN 이 아닐때는?

VS : Warning Level 4에서 default가 없다고 경고

Page 32: [Why Programs Fail] Deducing Errors, 오류 연역

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

int main(int argc, wchar_t* argv[]) { ... // argc, argv 사용되지 않음 return 0; }

VS : Warning Level 4에서 참조되지 않는다고 경고

Page 33: [Why Programs Fail] Deducing Errors, 오류 연역

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

if( w >= 0 ) printf(“w is non-negative\n”); else if( w > 0 ) printf(“w is positive\n”);

Page 34: [Why Programs Fail] Deducing Errors, 오류 연역

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

int* readbuf( int size ) { int* p = malloc(size*sizeof(int)); for( int i = 0; i < size; ++i ) { p[i] = readint(); if( p[i] == 0 ) return 0; } return p; }

스마트 포인터, RAII가 널 구원해죿지니…

Page 35: [Why Programs Fail] Deducing Errors, 오류 연역

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

void readfile() { int fp = open(file); int size = readint(file); if (size <= 0) return; ... close(fp); }

열었으면 닫아야지.

RAII가 널 구원해죿지니…

Page 36: [Why Programs Fail] Deducing Errors, 오류 연역

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

int *readbuf(int size) { int *p = malloc(size * sizeof(int)); for (int i = 0; i < size; i++) { p[i] = readint(); if (p[i] == 0) return 0; // end-of-file } return p; }

null일수도 있다.

Page 37: [Why Programs Fail] Deducing Errors, 오류 연역

정적 코드 분석 도구

Page 38: [Why Programs Fail] Deducing Errors, 오류 연역

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

Page 39: [Why Programs Fail] Deducing Errors, 오류 연역

x가 초기화되지 않고 사용되는가?

홀수 완전수가 존재해야 한다.

존재하는지 아는 사람은 아직 없음

따라서, 보수적 귺사에 의존해야 한다.

int x; for(i=j=k=1;--j||k;k=j?i%j?k:k-j:(j=i+=2)); write(x);

Page 40: [Why Programs Fail] Deducing Errors, 오류 연역

보수적 근사conservative approximation

배열 a[]의 임의의 변경이 그 배열 a[]의 임의의 읽기에 영향이 미칠 수 있다고 말하는것.

필요 이상으로 많은 자료 의존성을 맊들어내겠지맊 그래도 실제 자료 의존성들이 잘못 갂과되는 일은 막아죾다.

Page 41: [Why Programs Fail] Deducing Errors, 오류 연역

보수적 근사가 부정확한 원인들

갂접 접귺

변수가 실행시점에 결정된다면?

포인터

포인터가 가리키는 주소를 정확히 알 수 있는가?

함수

호출 지점에 직접 삽입하면 정확한 의존성을 알 수 있다.

함수가 아주 맋다면 비현실적

객체지향

병렧성

Page 42: [Why Programs Fail] Deducing Errors, 오류 연역

연역 자체의 위험

자체가 추상화에 의한 위험을 앆고 있다. 코드 불일치의 위험 정밀한 구성 관리, 버전 제어를 통한 극복

과도한 추상화의 위험 어쩔수 없지 뭐

부정확함의 위험 검증과 관찰을 혼합

Page 43: [Why Programs Fail] Deducing Errors, 오류 연역

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

Page 44: [Why Programs Fail] Deducing Errors, 오류 연역

값 귺원들을 격리하려면, 문제의 문장으로부터 의존성들을 거꾸로 추적한다.

의존성들을 통해서 코드 악취를 발견할 수 있다. 특히, 초기화되지 않은 변수 사용, 사용되지 않는 값들, 도달할 수 없는 코드 같은 일반적인 오류들을 찾을 수 있다.

디버깅을 시작하기 전에 자동적인 검출 도구(컴파일러 등)가 보고한 코드 악취를 제거할 것

Page 45: [Why Programs Fail] Deducing Errors, 오류 연역

프로그램을 슬라이싱할 때에는, 문장 S로부터 의존성을 따라가면서 다음과 같은 문장들을 모두 찾는다. – S에 영향을 받을 수 있는 문장들(전방 슬라이스)

– S에 영향을 죿 수 있는 문장들(후방 슬라이스)

연역맊을 사용한다면 코드 불일치, 관렦 세부사항의 과도한 추상화, 부정확 같은 위험이 생긴다.

어떤 종류의 연역도 멈춤 문제에 제한을 받으며, 그러면 보수적 근사에 의지해야 한다.

Page 46: [Why Programs Fail] Deducing Errors, 오류 연역

기억에 남는 디버깅

부록

Page 47: [Why Programs Fail] Deducing Errors, 오류 연역

디버그 모드에서는 제대로 작동.

릴리즈 모드에서맊 작업의 오차가 생긴다.

차라리 프로그램이 죽으면 좋으렦맊.

추적 결과 오류 문장을 찾음.

Page 48: [Why Programs Fail] Deducing Errors, 오류 연역

float f;

int a = static_cast<int>(f);

Page 49: [Why Programs Fail] Deducing Errors, 오류 연역

왜! float int 캐스팅인데

소수 부분fractional part 을

버리는 게 아니고

반올림을 하지???

Page 50: [Why Programs Fail] Deducing Errors, 오류 연역

컴파일러 옵션에 /QIfist 가 있었다.

_ftol()를 사용해서 변홖하는게 아니라

FPU(Floating-Point Unit)의

기본 rounding 모델인 반올림을 수행

Page 51: [Why Programs Fail] Deducing Errors, 오류 연역