Smart Contract/Security Tools

스마트 컨트랙트 보안 도구 - Slither

dev_swan 2023. 11. 7. 21:00

✏️ Slither

Slither은 스마트 컨트랙트 보안 정적 분석을 위한 오픈 소스 도구로, Solidity와 Vyper와 같은 이더리움 스마트 컨트랙트 언어로 작성된 스마트 컨트랙트의 취약점을 검사하고 보안 문제를 발견하는 데 사용됩니다.

 

Slither은 다양한 보안 문제를 식별하고, 스마트 컨트랙트의 코드 품질을 분석하여 보다 안전하고 견고한 스마트 컨트랙트를 개발하는 데 도움을 줍니다. 이를 통해 개발자는 스마트 컨트랙트를 배포하기 전에 잠재적인 취약성을 미리 파악하고 조치를 취할 수 있습니다.

 

Slither - Slither

 

🤔 Slither 기능

1️⃣ 정적 분석

스마트 컨트랙트 코드를 분석하여 정적으로 취약점을 식별합니다. 예를 들어, 무한 루프, 잠재적인 오버플로우, 사용자 입력의 제대로 된 검증 여부 등을 확인합니다.

 

2️⃣ 자동 보안 검사

Slither는 자동으로 스마트 컨트랙트 코드를 검사하고 일반적인 보안 문제를 탐지합니다.

 

3️⃣ 심볼릭 실행

Slither는 스마트 컨트랙트의 상태를 분석하기 위해 심볼릭 실행을 사용합니다. 이를 통해 더 깊은 수준의 분석과 취약성 탐지가 가능합니다.

더보기

💡 심볼릭 실행 (Symbolic Execution)

심볼릭 실행을 이용하여 프로그램의 모든 실행 경로를 탐색하고, 가능한 모든 입력값에 대해 테스트 케이스를 생성합니다. 이렇게 생성된 테스트 케이스를 사용하여 프로그램의 버그를 찾거나 정확성을 검증할 수 있습니다.

 

4️⃣ 플러그인 시스템

Slither는 플러그인 시스템을 지원하여 사용자가 커스텀 보안 검사를 추가할 수 있습니다.


❗️ Slither 사용 가이드 (Docker)

1️⃣ Slither 설치

$ pip3 install solc-select

$ pip3 install slither-analyzer

 

2️⃣ solc-select 컴파일 버전 설치 및 설정

$ solc-select install [사용할 버전]

$ solc-select use [사용할 버전]

// 컴파일 버전 확인
$ solc --version

 

3️⃣ Slither 컨트랙트 실행

$ slither <path_to_your_contract>

$ slither <path_to_your_contract> --solc-remaps @openzeppelin=node_modules/@openzeppelin

 

4️⃣ 결과 보고서 확인

Slither 사용가이드 (1)

 

5️⃣ 스마트 컨트랙트 내부의 기능 상호 작용을 시각화 한 그래프 작성

$ slither <path_to_your_contract> sol --print call-graph

$ slither <path_to_your_contract> sol --print call-graph --solc-remaps @openzeppelin=node_modules/@openzeppelin

 

👉 시각화된 그래프 예시

더보기
INFO:Printers:
Contract Payable
Contract vars: []
Inheritance:: ['ReentrancyGuard', 'Ownable', 'Context']
 
+-----------------------------+------------+-------------------------------+-----------------------------+-------------+---------------------------------------+------------------------------------------+-----------------------+
|           Function          | Visibility |           Modifiers           |             Read            |    Write    |             Internal Calls            |              External Calls              | Cyclomatic Complexity |
+-----------------------------+------------+-------------------------------+-----------------------------+-------------+---------------------------------------+------------------------------------------+-----------------------+
|        constructor()        |  internal  |               []              |       ['_NOT_ENTERED']      | ['_status'] |                   []                  |                    []                    |           1           |
|    _nonReentrantBefore()    |  private   |               []              |   ['_ENTERED', '_status']   | ['_status'] |        ['require(bool,string)']       |                    []                    |           1           |
|     _nonReentrantAfter()    |  private   |               []              |       ['_NOT_ENTERED']      | ['_status'] |                   []                  |                    []                    |           1           |
|  _reentrancyGuardEntered()  |  internal  |               []              |   ['_ENTERED', '_status']   |      []     |                   []                  |                    []                    |           1           |
|        constructor()        |  internal  |               []              |              []             |      []     |  ['_msgSender', '_transferOwnership'] |                    []                    |           1           |
|           owner()           |   public   |               []              |          ['_owner']         |      []     |                   []                  |                    []                    |           1           |
|        _checkOwner()        |  internal  |               []              |              []             |      []     |        ['_msgSender', 'owner']        |                    []                    |           1           |
|                             |            |                               |                             |             |        ['require(bool,string)']       |                                          |                       |
|     renounceOwnership()     |   public   |         ['onlyOwner']         |              []             |      []     |  ['_transferOwnership', 'onlyOwner']  |                    []                    |           1           |
|  transferOwnership(address) |   public   |         ['onlyOwner']         |              []             |      []     |  ['_transferOwnership', 'onlyOwner']  |                    []                    |           1           |
|                             |            |                               |                             |             |        ['require(bool,string)']       |                                          |                       |
| _transferOwnership(address) |  internal  |               []              |          ['_owner']         |  ['_owner'] |                   []                  |                    []                    |           1           |
|         _msgSender()        |  internal  |               []              |        ['msg.sender']       |      []     |                   []                  |                    []                    |           1           |
|          _msgData()         |  internal  |               []              |         ['msg.data']        |      []     |                   []                  |                    []                    |           1           |
|          receive()          |  external  |               []              | ['msg.sender', 'msg.value'] |      []     |                   []                  |                    []                    |           1           |
|      withdraw(uint256)      |  external  | ['nonReentrant', 'onlyOwner'] |    ['msg.sender', 'this']   |      []     |  ['balance(address)', 'nonReentrant'] | ['address(msg.sender).transfer(amount)'] |           1           |
|                             |            |                               |                             |             | ['onlyOwner', 'require(bool,string)'] |                                          |                       |
+-----------------------------+------------+-------------------------------+-----------------------------+-------------+---------------------------------------+------------------------------------------+-----------------------+

+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
|   Modifiers    | Visibility | Read | Write |                 Internal Calls                | External Calls | Cyclomatic Complexity |
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
| nonReentrant() |  internal  |  []  |   []  | ['_nonReentrantAfter', '_nonReentrantBefore'] |       []       |           1           |
|  onlyOwner()   |  internal  |  []  |   []  |                ['_checkOwner']                |       []       |           1           |
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+

INFO:Printers:
Contract Governance
Contract vars: ['totalVotes', 'eligibleVotersLength', 'eligibleVoters', 'eligibleVotersAddress', 'votes', 'votersType']
Inheritance:: ['Payable', 'ReentrancyGuard', 'Ownable', 'Context']
 
+----------------------------------------------+------------+------------------------------------------------------------------+----------------------------------------------+---------------------------------------------+-------------------------------------------------+--------------------------------------------------+-----------------------+
|                   Function                   | Visibility |                            Modifiers                             |                     Read                     |                    Write                    |                  Internal Calls                 |                  External Calls                  | Cyclomatic Complexity |
+----------------------------------------------+------------+------------------------------------------------------------------+----------------------------------------------+---------------------------------------------+-------------------------------------------------+--------------------------------------------------+-----------------------+
|                  receive()                   |  external  |                                []                                |         ['msg.sender', 'msg.value']          |                      []                     |                        []                       |                        []                        |           1           |
|              withdraw(uint256)               |  external  |                  ['nonReentrant', 'onlyOwner']                   |            ['msg.sender', 'this']            |                      []                     |       ['balance(address)', 'nonReentrant']      |     ['address(msg.sender).transfer(amount)']     |           1           |
|                                              |            |                                                                  |                                              |                                             |      ['onlyOwner', 'require(bool,string)']      |                                                  |                       |
|                constructor()                 |  internal  |                                []                                |               ['_NOT_ENTERED']               |                 ['_status']                 |                        []                       |                        []                        |           1           |
|            _nonReentrantBefore()             |  private   |                                []                                |           ['_ENTERED', '_status']            |                 ['_status']                 |             ['require(bool,string)']            |                        []                        |           1           |
|             _nonReentrantAfter()             |  private   |                                []                                |               ['_NOT_ENTERED']               |                 ['_status']                 |                        []                       |                        []                        |           1           |
|          _reentrancyGuardEntered()           |  internal  |                                []                                |           ['_ENTERED', '_status']            |                      []                     |                        []                       |                        []                        |           1           |
|                constructor()                 |  internal  |                                []                                |                      []                      |                      []                     |       ['_msgSender', '_transferOwnership']      |                        []                        |           1           |
|                   owner()                    |   public   |                                []                                |                  ['_owner']                  |                      []                     |                        []                       |                        []                        |           1           |
|                _checkOwner()                 |  internal  |                                []                                |                      []                      |                      []                     |             ['_msgSender', 'owner']             |                        []                        |           1           |
|                                              |            |                                                                  |                                              |                                             |             ['require(bool,string)']            |                                                  |                       |
|             renounceOwnership()              |   public   |                          ['onlyOwner']                           |                      []                      |                      []                     |       ['_transferOwnership', 'onlyOwner']       |                        []                        |           1           |
|          transferOwnership(address)          |   public   |                          ['onlyOwner']                           |                      []                      |                      []                     |       ['_transferOwnership', 'onlyOwner']       |                        []                        |           1           |
|                                              |            |                                                                  |                                              |                                             |             ['require(bool,string)']            |                                                  |                       |
|         _transferOwnership(address)          |  internal  |                                []                                |                  ['_owner']                  |                  ['_owner']                 |                        []                       |                        []                        |           1           |
|                 _msgSender()                 |  internal  |                                []                                |                ['msg.sender']                |                      []                     |                        []                       |                        []                        |           1           |
|                  _msgData()                  |  internal  |                                []                                |                 ['msg.data']                 |                      []                     |                        []                       |                        []                        |           1           |
|           isAllowedVoter(address)            |   public   |                                []                                |              ['eligibleVoters']              |                      []                     |                        []                       |                        []                        |           1           |
|       isEligibleVoters(uint32,address)       |   public   |                                []                                |                  ['votes']                   |                      []                     |                        []                       |                        []                        |           1           |
|    addVoter(address,Governance.VoterType)    |   public   |                          ['onlyOwner']                           |           ['eligibleVotersLength']           | ['eligibleVoters', 'eligibleVotersAddress'] |         ['isAllowedVoter', 'onlyOwner']         |                        []                        |           1           |
|                                              |            |                                                                  |                                              |    ['eligibleVotersLength', 'votersType']   |             ['require(bool,string)']            |                                                  |                       |
|             removeVoter(address)             |   public   |                          ['onlyOwner']                           | ['eligibleVoters', 'eligibleVotersAddress']  | ['eligibleVoters', 'eligibleVotersAddress'] |         ['isAllowedVoter', 'onlyOwner']         |                        []                        |           1           |
|                                              |            |                                                                  |    ['eligibleVotersLength', 'votersType']    |    ['eligibleVotersLength', 'votersType']   |             ['require(bool,string)']            |                                                  |                       |
|   createVote(uint256,Governance.VoterType)   |   public   |                          ['onlyOwner']                           | ['block.timestamp', 'eligibleVotersAddress'] |           ['totalVotes', 'votes']           |                  ['onlyOwner']                  |                        []                        |           2           |
|                                              |            |                                                                  |    ['eligibleVotersLength', 'totalVotes']    |                                             |                                                 |                                                  |                       |
|                                              |            |                                                                  |                  ['votes']                   |                                             |                                                 |                                                  |                       |
|      vote(uint32,Governance.VoteChoice)      |   public   | ['isValidVoteId', 'isVotingPeriodExpired', 'onlyEligibleVoters'] |           ['votersType', 'votes']            |                  ['votes']                  |         ['_msgSender', 'isValidVoteId']         |                        []                        |           8           |
|                                              |            |                                                                  |                                              |                                             | ['isVotingPeriodExpired', 'onlyEligibleVoters'] |                                                  |                       |
|                                              |            |                                                                  |                                              |                                             |             ['require(bool,string)']            |                                                  |                       |
|               endVote(uint32)                |   public   |                  ['isValidVoteId', 'onlyOwner']                  |         ['block.timestamp', 'votes']         |                  ['votes']                  |          ['isValidVoteId', 'onlyOwner']         |                        []                        |           1           |
|                                              |            |                                                                  |                                              |                                             |             ['require(bool,string)']            |                                                  |                       |
|             forceEndVote(uint32)             |   public   |                  ['isValidVoteId', 'onlyOwner']                  |         ['block.timestamp', 'votes']         |                  ['votes']                  |          ['isValidVoteId', 'onlyOwner']         |                        []                        |           1           |
|           hasVoted(uint32,address)           |   public   |                        ['isValidVoteId']                         |                  ['votes']                   |                      []                     |                ['isValidVoteId']                |                        []                        |           1           |
|   totalVoters(uint32,Governance.VoterType)   |   public   |                        ['isValidVoteId']                         |                  ['votes']                   |                      []                     |                ['isValidVoteId']                |                        []                        |           1           |
|            getVoteCounts(uint32)             |   public   |                        ['isValidVoteId']                         |                  ['votes']                   |                      []                     |                ['isValidVoteId']                |                        []                        |           1           |
|          getMostVotedChoice(uint32)          |   public   |                                []                                |                  ['votes']                   |                      []                     |                        []                       |                        []                        |           1           |
| verifyVoteCount(uint32,uint32,uint32,uint32) |   public   |                        ['isValidVoteId']                         |                  ['votes']                   |                      []                     |                ['isValidVoteId']                |                        []                        |           1           |
|   getVoteSelectionAndCount(uint32,address)   |   public   |                        ['isValidVoteId']                         |                  ['votes']                   |                      []                     |    ['isValidVoteId', 'require(bool,string)']    |                        []                        |           1           |
|         transferVoteRewards(uint32)          |   public   |                ['isValidVoteId', 'nonReentrant']                 |           ['votersType', 'votes']            |                      []                     |       ['_msgSender', 'getMostVotedChoice']      | ['address(_msgSender()).transfer(rewardAmount)'] |           6           |
|                                              |            |                                                                  |                                              |                                             |        ['isValidVoteId', 'nonReentrant']        |                                                  |                       |
|                                              |            |                                                                  |                                              |                                             |             ['require(bool,string)']            |                                                  |                       |
|       setVotingPeriod(uint32,uint256)        |   public   |                  ['isValidVoteId', 'onlyOwner']                  |         ['block.timestamp', 'votes']         |                  ['votes']                  |          ['isValidVoteId', 'onlyOwner']         |                        []                        |           1           |
|        slitherConstructorVariables()         |  internal  |                                []                                |                      []                      |           ['eligibleVotersLength']          |                        []                       |                        []                        |           1           |
+----------------------------------------------+------------+------------------------------------------------------------------+----------------------------------------------+---------------------------------------------+-------------------------------------------------+--------------------------------------------------+-----------------------+

+-------------------------------+------------+------------------------------+-------+-----------------------------------------------+----------------+-----------------------+
|           Modifiers           | Visibility |             Read             | Write |                 Internal Calls                | External Calls | Cyclomatic Complexity |
+-------------------------------+------------+------------------------------+-------+-----------------------------------------------+----------------+-----------------------+
|         nonReentrant()        |  internal  |              []              |   []  | ['_nonReentrantAfter', '_nonReentrantBefore'] |       []       |           1           |
|          onlyOwner()          |  internal  |              []              |   []  |                ['_checkOwner']                |       []       |           1           |
|     isValidVoteId(uint32)     |  internal  |        ['totalVotes']        |   []  |            ['require(bool,string)']           |       []       |           1           |
| isVotingPeriodExpired(uint32) |  internal  | ['block.timestamp', 'votes'] |   []  |            ['require(bool,string)']           |       []       |           1           |
|   onlyEligibleVoters(uint32)  |  internal  |              []              |   []  |       ['_msgSender', 'isEligibleVoters']      |       []       |           1           |
|                               |            |                              |       |            ['require(bool,string)']           |                |                       |
+-------------------------------+------------+------------------------------+-------+-----------------------------------------------+----------------+-----------------------+

INFO:Printers:
Contract Ownable
Contract vars: ['_owner']
Inheritance:: ['Context']
 
+-----------------------------+------------+---------------+----------------+------------+--------------------------------------+----------------+-----------------------+
|           Function          | Visibility |   Modifiers   |      Read      |   Write    |            Internal Calls            | External Calls | Cyclomatic Complexity |
+-----------------------------+------------+---------------+----------------+------------+--------------------------------------+----------------+-----------------------+
|         _msgSender()        |  internal  |       []      | ['msg.sender'] |     []     |                  []                  |       []       |           1           |
|          _msgData()         |  internal  |       []      |  ['msg.data']  |     []     |                  []                  |       []       |           1           |
|        constructor()        |  internal  |       []      |       []       |     []     | ['_msgSender', '_transferOwnership'] |       []       |           1           |
|           owner()           |   public   |       []      |   ['_owner']   |     []     |                  []                  |       []       |           1           |
|        _checkOwner()        |  internal  |       []      |       []       |     []     |       ['_msgSender', 'owner']        |       []       |           1           |
|                             |            |               |                |            |       ['require(bool,string)']       |                |                       |
|     renounceOwnership()     |   public   | ['onlyOwner'] |       []       |     []     | ['_transferOwnership', 'onlyOwner']  |       []       |           1           |
|  transferOwnership(address) |   public   | ['onlyOwner'] |       []       |     []     | ['_transferOwnership', 'onlyOwner']  |       []       |           1           |
|                             |            |               |                |            |       ['require(bool,string)']       |                |                       |
| _transferOwnership(address) |  internal  |       []      |   ['_owner']   | ['_owner'] |                  []                  |       []       |           1           |
+-----------------------------+------------+---------------+----------------+------------+--------------------------------------+----------------+-----------------------+

+-------------+------------+------+-------+-----------------+----------------+-----------------------+
|  Modifiers  | Visibility | Read | Write |  Internal Calls | External Calls | Cyclomatic Complexity |
+-------------+------------+------+-------+-----------------+----------------+-----------------------+
| onlyOwner() |  internal  |  []  |   []  | ['_checkOwner'] |       []       |           1           |
+-------------+------------+------+-------+-----------------+----------------+-----------------------+

INFO:Printers:
Contract ReentrancyGuard
Contract vars: ['_NOT_ENTERED', '_ENTERED', '_status']
Inheritance:: []
 
+---------------------------------------+------------+-----------+-------------------------+------------------------------+--------------------------+----------------+-----------------------+
|                Function               | Visibility | Modifiers |           Read          |            Write             |      Internal Calls      | External Calls | Cyclomatic Complexity |
+---------------------------------------+------------+-----------+-------------------------+------------------------------+--------------------------+----------------+-----------------------+
|             constructor()             |  internal  |     []    |     ['_NOT_ENTERED']    |         ['_status']          |            []            |       []       |           1           |
|         _nonReentrantBefore()         |  private   |     []    | ['_ENTERED', '_status'] |         ['_status']          | ['require(bool,string)'] |       []       |           1           |
|          _nonReentrantAfter()         |  private   |     []    |     ['_NOT_ENTERED']    |         ['_status']          |            []            |       []       |           1           |
|       _reentrancyGuardEntered()       |  internal  |     []    | ['_ENTERED', '_status'] |              []              |            []            |       []       |           1           |
| slitherConstructorConstantVariables() |  internal  |     []    |            []           | ['_ENTERED', '_NOT_ENTERED'] |            []            |       []       |           1           |
+---------------------------------------+------------+-----------+-------------------------+------------------------------+--------------------------+----------------+-----------------------+

+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
|   Modifiers    | Visibility | Read | Write |                 Internal Calls                | External Calls | Cyclomatic Complexity |
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
| nonReentrant() |  internal  |  []  |   []  | ['_nonReentrantAfter', '_nonReentrantBefore'] |       []       |           1           |
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+

INFO:Printers:
Contract Context
Contract vars: []
Inheritance:: []
 
+--------------+------------+-----------+----------------+-------+----------------+----------------+-----------------------+
|   Function   | Visibility | Modifiers |      Read      | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+--------------+------------+-----------+----------------+-------+----------------+----------------+-----------------------+
| _msgSender() |  internal  |     []    | ['msg.sender'] |   []  |       []       |       []       |           1           |
|  _msgData()  |  internal  |     []    |  ['msg.data']  |   []  |       []       |       []       |           1           |
+--------------+------------+-----------+----------------+-------+----------------+----------------+-----------------------+

+-----------+------------+------+-------+----------------+----------------+-----------------------+
| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+-----------+------------+------+-------+----------------+----------------+-----------------------+
+-----------+------------+------+-------+----------------+----------------+-----------------------+

 

6️⃣ 이 외에 프린터 명령어 확인

$ slither –list-printers

Slither 사용가이드 (2)


📌 Slither 옵션

  • solc 경고 비활성화 : --solc-disable-warnings
  • 출력 색상화 비활성화 : --disable-color
  • 결과를 json 파일로 내보내기 : --json file.json
    • 파일 대신 standard output으로 내보내려면 파일 이름을 -로 입력
더보기

💡 standard output

standard output은 컴퓨터 프로그램이 실행되는 동안 데이터를 출력하는 표준 방법을 나타냅니다. 일반적으로 이 데이터는 터미널이나 명령 프롬포트 창에 표시됩니다.

따라서 결과를 별도의 파일로 저장하지 않고 사용자의 터미널 창에서 직접 출력할 때 사용합니다.

🔴 감지기 선택

  • 모든 감지기를 확인 : --list-detectors
$ slither --list-detectors
  • 특정 감지기만 실행 :--detect detector1,detector2
$ slither file.sol --detect arbitrary-send, pragma
  • 특정 감지기 제외 : --exclude detector1,detector2
$ slither file.sol --exclude naming-convention, unused-state, suicidal
  • 정보용 또는 심각도가 낮은 감지기 제외 : --exclude-informational 또는 --exclude-low
$ slither file.sol --exclude-informational

 

👉 공식문서 감지기 설명서

https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array

 

Detector Documentation

Static Analyzer for Solidity and Vyper. Contribute to crytic/slither development by creating an account on GitHub.

github.com

 

🔴 프린터 선택

  • 모든 프린터를 확인 : --list-printers
$ slither --list-printers
  • 특정 프린터 실행 : --print printer1,printer2
$ slither file.sol --print inheritance-graph

 

👉 공식문서 프린터 설명서 바로가기

https://github.com/crytic/slither/wiki/Printer-documentation#loc

 

Printer documentation

Static Analyzer for Solidity and Vyper. Contribute to crytic/slither development by creating an account on GitHub.

github.com

 

🔴 경로 필터링

  • 특정 경로 관련 결과 제외: --filter-paths path1
$ slither . --filter-paths "openzepellin"

 

🔴 구성 파일

  • 기본적으로 slither.config.json 파일이 사용됩니다. 파일 이름은 --config-file file.config.json 옵션을 통해 변경할 수 있습니다.
  • CLI를 통해 전달된 옵션은 구성 파일의 옵션보다 우선됩니다.
  • 지원되는 플래그
{
    "detectors_to_run": "detector1,detector2", // 실행할 감지기의 목록
    "printers_to_run": "printer1,printer2", // 실행할 프린터 목록
    "detectors_to_exclude": "detector1,detector2", // 제외할 감지기 목록
    "exclude_informational": false, // 정보용 감지기를 제외할지 여부
    "exclude_low": false, // 낮은 심각도의 감지기를 제외할지 여부
    "exclude_medium": false, // 중간 심각도의 감지기를 제외할지 여부
    "exclude_high": false, // 높은 심각도의 감지기를 제외할지 여부
    "json": "", // JSON 파일 저장 경로
    "disable_color": false, // 출력의 색상화를 비활성화할지 여부
    "filter_paths": "(mocks/|test/)", // 특정 경로를 필터링하는 정규식 표현
    "legacy_ast": false // 이전 버전의 AST를 사용할지 여부
}
더보기

💡 AST (Abstract Syntax Tree)

AST, 즉 추상 구문 트리는 소스 코드의 구문 구조를 나타내는 트리로서 컴파일러나 인터프리터 등의 프로그래밍 언어 처리 도구에서 널리 사용됩니다.