More info see: https://msdn.microsoft.com/en-us/library/hh916383.aspx

Simply stated, SAL is an inexpensive way to let the compiler check your code for you.

The Microsoft source-code annotation language (SAL) provides a set of annotations that you can use to describe how a function uses its parameters, the assumptions that it makes about them, and the guarantees that it makes when it finishes. The annotations are defined in the header file <sal.h>. Visual Studio code analysis for C++ uses SAL annotations to modify its analysis of functions. For more information about SAL 2.0 for Windows driver development, see SAL 2.0 Annotations for Windows Drivers.

Natively, C and C++ provide only limited ways for developers to consistently express intent and invariance. By using SAL annotations, you can describe your functions in greater detail so that developers who are consuming them can better understand how to use them.

What Is SAL and Why Should You Use It?

 

Simply stated, SAL is an inexpensive way to let the compiler check your code for you.

SAL Makes Code More Valuable

SAL can help you make your code design more understandable, both for humans and for code analysis tools. Consider this example that shows the C runtime function memcpy:

 
  1. void * memcpy(
  2. void *dest,
  3. const void *src,
  4. size_t count
  5. );

Can you tell what this function does? When a function is implemented or called, certain properties must be maintained to ensure program correctness. Just by looking at a declaration such as the one in the example, you don't know what they are. Without SAL annotations, you'd have to rely on documentation or code comments. Here’s what the MSDN documentation for memcpy says:

"Copies count bytes of src to dest. If the source and destination overlap, the behavior of memcpy is undefined. Use memmove to handle overlapping regions. 
Security Note: Make sure that the destination buffer is the same size or larger than the source buffer. For more information, see Avoiding Buffer Overruns."

The documentation contains a couple of bits of information that suggest that your code has to maintain certain properties to ensure program correctness:

  • memcpy copies the count of bytes from the source buffer to the destination buffer.

  • The destination buffer must be at least as large as the source buffer.

However, the compiler can't read the documentation or informal comments. It doesn't know that there is a relationship between the two buffers andcount, and it also can't effectively guess about a relationship. SAL could provide more clarity about the properties and implementation of the function, as shown here:

 
  1. void * memcpy(
  2. _Out_writes_bytes_all_(count) void *dest,
  3. _In_reads_bytes_(count) const void *src,
  4. size_t count
  5. );

Notice that these annotations resemble the information in the MSDN documentation, but they are more concise and they follow a semantic pattern. When you read this code, you can quickly understand the properties of this function and how to avoid buffer overrun security issues. Even better, the semantic patterns that SAL provides can improve the efficiency and effectiveness of automated code analysis tools in the early discovery of potential bugs. Imagine that someone writes this buggy implementation of wmemcpy:

 
  1. wchar_t * wmemcpy(
  2. _Out_writes_all_(count) wchar_t *dest,
  3. _In_reads_(count) const wchar_t *src,
  4. size_t count)
  5. {
  6. size_t i;
  7. for (i = 0; i <= count; i++) { // BUG: off-by-one error
  8. dest[i] = src[i];
  9. }
  10. return dest;
  11. }

This implementation contains a common off-by-one error. Fortunately, the code author included the SAL buffer size annotation—a code analysis tool could catch the bug by analyzing this function alone.

SAL Basics

SAL defines four basic kinds of parameters, which are categorized by usage pattern.

Category

Parameter Annotation

Description

Input to called function

_In_

Data is passed to the called function, and is treated as read-only.

Input to called function, and output to caller

_Inout_

Usable data is passed into the function and potentially is modified.

Output to caller

_Out_

The caller only provides space for the called function to write to. The called function writes data into that space.

Output of pointer to caller

_Outptr_

Like Output to caller. The value that's returned by the called function is a pointer.

These four basic annotations can be made more explicit in various ways. By default, annotated pointer parameters are assumed to be required—they must be non-NULL for the function to succeed. The most commonly used variation of the basic annotations indicates that a pointer parameter is optional—if it's NULL, the function can still succeed in doing its work.

This table shows how to distinguish between required and optional parameters:

 

Parameters are required

Parameters are optional

Input to called function

_In_

_In_opt_

Input to called function, and output to caller

_Inout_

_Inout_opt_

Output to caller

_Out_

_Out_opt_

Output of pointer to caller

_Outptr_

_Outptr_opt_

These annotations help identify possible uninitialized values and invalid null pointer uses in a formal and accurate manner. Passing NULL to a required parameter might cause a crash, or it might cause a "failed" error code to be returned. Either way, the function cannot succeed in doing its job.

SAL Examples

 

This section shows code examples for the basic SAL annotations.

Using the Visual Studio Code Analysis Tool to Find Defects

In the examples, the Visual Studio Code Analysis tool is used together with SAL annotations to find code defects. Here's how to do that.

To use Visual Studio code analysis tools and SAL

  1. In Visual Studio, open a C++ project that contains SAL annotations.

  2. On the menu bar, choose BuildRun Code Analysis on Solution.

    Consider the _In_ example in this section. If you run code analysis on it, this warning is displayed:

    C6387 Invalid Parameter Value
    'pInt' could be '0': this does not adhere to the specification for the function 'InCallee'.

Example: The _In_ Annotation

The _In_ annotation indicates that:

  • The parameter must be valid and will not be modified.

  • The function will only read from the single-element buffer.

  • The caller must provide the buffer and initialize it.

  • _In_ specifies "read-only". A common mistake is to apply _In_ to a parameter that should have the _Inout_ annotation instead.

  • _In_ is allowed but ignored by the analyzer on non-pointer scalars.

 
  1. void InCallee(_In_ int *pInt)
  2. {
  3. int i = *pInt;
  4. }
  5.  
  6. void GoodInCaller()
  7. {
  8. int *pInt = new int;
  9. *pInt = 5;
  10.  
  11. InCallee(pInt);
  12. delete pInt;
  13. }
  14.  
  15. void BadInCaller()
  16. {
  17. int *pInt = NULL;
  18. InCallee(pInt); // pInt should not be NULL
  19. }

If you use Visual Studio Code Analysis on this example, it validates that the callers pass a non-Null pointer to an initialized buffer for pInt. In this case, pInt pointer cannot be NULL.

Example: The _In_opt_ Annotation

_In_opt_ is the same as _In_, except that the input parameter is allowed to be NULL and, therefore, the function should check for this.

 
  1. void GoodInOptCallee(_In_opt_ int *pInt)
  2. {
  3. if(pInt != NULL) {
  4. int i = *pInt;
  5. }
  6. }
  7.  
  8. void BadInOptCallee(_In_opt_ int *pInt)
  9. {
  10. int i = *pInt; // Dereferencing NULL pointer ‘pInt’
  11. }
  12.  
  13. void InOptCaller()
  14. {
  15. int *pInt = NULL;
  16. GoodInOptCallee(pInt);
  17. BadInOptCallee(pInt);
  18. }

Visual Studio Code Analysis validates that the function checks for NULL before it accesses the buffer.

Example: The _Out_ Annotation

_Out_ supports a common scenario in which a non-NULL pointer that points to an element buffer is passed in and the function initializes the element. The caller doesn’t have to initialize the buffer before the call; the called function promises to initialize it before it returns.

 
  1. void GoodOutCallee(_Out_ int *pInt)
  2. {
  3. *pInt = 5;
  4. }
  5.  
  6. void BadOutCallee(_Out_ int *pInt)
  7. {
  8. // Did not initialize pInt buffer before returning!
  9. }
  10.  
  11. void OutCaller()
  12. {
  13. int *pInt = new int;
  14. GoodOutCallee(pInt);
  15. BadOutCallee(pInt);
  16. delete pInt;
  17. }

Visual Studio Code Analysis Tool validates that the caller passes a non-NULL pointer to a buffer for pInt and that the buffer is initialized by the function before it returns.

Example: The _Out_opt_ Annotation

_Out_opt_ is the same as _Out_, except that the parameter is allowed to be NULL and, therefore, the function should check for this.

 
  1. void GoodOutOptCallee(_Out_opt_ int *pInt)
  2. {
  3. if (pInt != NULL) {
  4. *pInt = 5;
  5. }
  6. }
  7.  
  8. void BadOutOptCallee(_Out_opt_ int *pInt)
  9. {
  10. *pInt = 5; // Dereferencing NULL pointer ‘pInt’
  11. }
  12.  
  13. void OutOptCaller()
  14. {
  15. int *pInt = NULL;
  16. GoodOutOptCallee(pInt);
  17. BadOutOptCallee(pInt);
  18. }

Visual Studio Code Analysis validates that this function checks for NULL before pInt is dereferenced, and if pInt is not NULL, that the buffer is initialized by the function before it returns.

Example: The _Inout_ Annotation

_Inout_ is used to annotate a pointer parameter that may be changed by the function. The pointer must point to valid initialized data before the call, and even if it changes, it must still have a valid value on return. The annotation specifies that the function may freely read from and write to the one-element buffer. The caller must provide the buffer and initialize it.

Note

Like _Out_, _Inout_ must apply to a modifiable value.

 
  1. void InOutCallee(_Inout_ int *pInt)
  2. {
  3. int i = *pInt;
  4. *pInt = 6;
  5. }
  6.  
  7. void InOutCaller()
  8. {
  9. int *pInt = new int;
  10. *pInt = 5;
  11. InOutCallee(pInt);
  12. delete pInt;
  13. }
  14.  
  15. void BadInOutCaller()
  16. {
  17. int *pInt = NULL;
  18. InOutCallee(pInt); // ‘pInt’ should not be NULL
  19. }

Visual Studio Code Analysis validates that callers pass a non-NULL pointer to an initialized buffer for pInt, and that, before return, pInt is still non-NULL and the buffer is initialized.

Example: The _Inout_opt_ Annotation

_Inout_opt_ is the same as _Inout_, except that the input parameter is allowed to be NULL and, therefore, the function should check for this.

 
  1. void GoodInOutOptCallee(_Inout_opt_ int *pInt)
  2. {
  3. if(pInt != NULL) {
  4. int i = *pInt;
  5. *pInt = 6;
  6. }
  7. }
  8.  
  9. void BadInOutOptCallee(_Inout_opt_ int *pInt)
  10. {
  11. int i = *pInt; // Dereferencing NULL pointer ‘pInt’
  12. *pInt = 6;
  13. }
  14.  
  15. void InOutOptCaller()
  16. {
  17. int *pInt = NULL;
  18. GoodInOutOptCallee(pInt);
  19. BadInOutOptCallee(pInt);
  20. }

Visual Studio Code Analysis validates that this function checks for NULL before it accesses the buffer, and if pInt is not NULL, that the buffer is initialized by the function before it returns.

Example: The _Outptr_ Annotation

_Outptr_ is used to annotate a parameter that's intended to return a pointer. The parameter itself should not be NULL, and the called function returns a non-NULL pointer in it and that pointer points to initialized data.

 
  1. void GoodOutPtrCallee(_Outptr_ int **pInt)
  2. {
  3. int *pInt2 = new int;
  4. *pInt2 = 5;
  5.  
  6. *pInt = pInt2;
  7. }
  8.  
  9. void BadOutPtrCallee(_Outptr_ int **pInt)
  10. {
  11. int *pInt2 = new int;
  12. // Did not initialize pInt buffer before returning!
  13. *pInt = pInt2;
  14. }
  15.  
  16. void OutPtrCaller()
  17. {
  18. int *pInt = NULL;
  19. GoodOutPtrCallee(&pInt);
  20. BadOutPtrCallee(&pInt);
  21. }

Visual Studio Code Analysis validates that the caller passes a non-NULL pointer for *pInt, and that the buffer is initialized by the function before it returns.

Example: The _Outptr_opt_ Annotation

_Outptr_opt_ is the same as _Outptr_, except that the parameter is optional—the caller can pass in a NULL pointer for the parameter.

 
  1. void GoodOutPtrOptCallee(_Outptr_opt_ int **pInt)
  2. {
  3. int *pInt2 = new int;
  4. *pInt2 = 6;
  5.  
  6. if(pInt != NULL) {
  7. *pInt = pInt2;
  8. }
  9. }
  10.  
  11. void BadOutPtrOptCallee(_Outptr_opt_ int **pInt)
  12. {
  13. int *pInt2 = new int;
  14. *pInt2 = 6;
  15. *pInt = pInt2; // Dereferencing NULL pointer ‘pInt’
  16. }
  17.  
  18. void OutPtrOptCaller()
  19. {
  20. int **ppInt = NULL;
  21. GoodOutPtrOptCallee(ppInt);
  22. BadOutPtrOptCallee(ppInt);
  23. }

Visual Studio Code Analysis validates that this function checks for NULL before *pInt is dereferenced, and that the buffer is initialized by the function before it returns.

Example: The _Success_ Annotation in Combination with _Out_

Annotations can be applied to most objects. In particular, you can annotate a whole function. One of the most obvious characteristics of a function is that it can succeed or fail. But like the association between a buffer and its size, C/C++ cannot express function success or failure. By using the_Success_ annotation, you can say what success for a function looks like. The parameter to the _Success_ annotation is just an expression that when it is true indicates that the function has succeeded. The expression can be anything that the annotation parser can handle. The effects of the annotations after the function returns are only applicable when the function succeeds. This example shows how _Success_ interacts with _Out_ to do the right thing. You can use the keyword return to represent the return value.

 
  1. _Success_(return != false) // Can also be stated as _Success_(return)
  2. bool GetValue(_Out_ int *pInt, bool flag)
  3. {
  4. if(flag) {
  5. *pInt = 5;
  6. return true;
  7. } else {
  8. return false;
  9. }
  10. }

The _Out_ annotation causes Visual Studio Code Analysis to validate that the caller passes a non-NULL pointer to a buffer for pInt, and that the buffer is initialized by the function before it returns.

SAL Best Practice

 
 

Adding Annotations to Existing Code

SAL is a powerful technology that can help you improve the security and reliability of your code. After you learn SAL, you can apply the new skill to your daily work. In new code, you can use SAL-based specifications by design throughout; in older code, you can add annotations incrementally and thereby increase the benefits every time you update.

Microsoft public headers are already annotated. Therefore, we suggest that in your projects you first annotate leaf node functions and functions that call Win32 APIs to get the most benefit.

When Do I Annotate?

Here are some guidelines:

  • Annotate all pointer parameters.

  • Annotate value-range annotations so that Code Analysis can ensure buffer and pointer safety.

  • Annotate locking rules and locking side effects. For more information, see Annotating Locking Behavior.

  • Annotate driver properties and other domain-specific properties.

Or you can annotate all parameters to make your intent clear throughout and to make it easy to check that annotations have been done.

Microsoft source-code annotation language (SAL) 相关的更多相关文章

  1. [解决]ASP.NET MVC 4/5 源码调试(source code debug)

    ========================ASP.NET MVC 4============================ ASP.NET MVC 4 source code download ...

  2. Tips for newbie to read source code

    This post is first posted on my WeChat public account: GeekArtT Reading source code is always one bi ...

  3. Xcode开发中 Code Snippets Library 的相关用法

    当在进行项目的时候,总会遇到很多相同的写法.因此,我们可以使用Code Snippets Library 来进行代码小片段的“封装”: 以Xcode中常用的属性为例: 使用步骤如下: 1.在Xcode ...

  4. How to build windows azure PowerShell Source Code

    Download any version source code of Windows Azure Powershell from https://github.com/Azure/azure-sdk ...

  5. Classic Source Code Collected

    收藏一些经典的源码,持续更新!!! 1.深度学习框架(Deep Learning Framework). A:Caffe (Convolutional Architecture for Fast Fe ...

  6. 【FLYabroad 】微软内部代码检查工具 (Microsoft Source Analysis for C#)[转]

    SourceAnalysis (StyleCop)的终极目标是让所有人都能写出优雅和一致的代码,因此这些代码具有很高的可读性. 早就听说了微软内部的静态代码检查和代码强制格式美化工具 StyleCop ...

  7. XML.ObjTree -- XML source code from/to JavaScript object like E4X

    转载于:http://www.kawa.net/works/js/xml/objtree-try-e.html // ========================================= ...

  8. Source Code Review

    1.berfore we talking abnout the Source Code review,here's what we want to know about the most popula ...

  9. Spring 4 MVC example with Maven - [Source Code Download]

    In this tutorial, we show you a Spring 4 MVC example, using Maven build tool. Technologies used : Sp ...

随机推荐

  1. 网页版电子表格控件tmlxSpreadsheet免费下载地址

    tmlxSpreadsheet 是一个由JavaScript 和 PHP 写成的电子表格控件(包含WP插件, Joomla插件等等).. 程序员可以容易的添加一个类似Excel功能的,可编辑的表格功能 ...

  2. (spring-第19回【AOP基础篇】)基于AspectJ和Schema的AOP

    基于AspectJ就是基于@AspectJ注解,基于Schema就是全部依靠配置文件.那么首先要了解Java注解. Java注解初探 在JDK5.0中,我们可以自定义标签,并通过Java语言的反射机制 ...

  3. UE4 Tutorial - Custom Mesh Component 用于绘制自定义网格的插件CustomMeshComponent

    UE4 中用于绘制自定义网格的插件CustomMeshComponent. 转载: UE4 Tutorial - Custom Mesh Component   Over the last few w ...

  4. activity 和 生命周期: 消息通信

    实际上关于activity大概流程已经了解了,在深入的话方向应该是ams的处理操作和界面创建和view绘制.这些话题之后再谈,activity是一个gui程序,其中离不开的就是消息通讯,也就是在消息循 ...

  5. Scala初探:新潮的函数式面向对象语言

    Scala的基本概念 先讲讲Scala里头几个概念Classes, Traits, Objects and Packages. Class和Java中的很像,只不过Scala中Class不能有stat ...

  6. Java 部分注意160530

    1.1 变量的名字不可以重复 1.2 标识符命名规则:必须以字母_下划线货比富豪开头,余下的字符可以是下划线,货币符号,任何字母或数字,长度不限.不能用Java中的关键字或保留字做标识符. 1.3 l ...

  7. Tomcat 安装--小白教程

    因为要进行微信公众号的开发模式,所以需要安装Tomcat Web服务器,现在就把我的安装过程写下来,希望可以帮到有需要的人~首先,我们需要下载tomcat的安装包,直接去官网就好啦,http://to ...

  8. 神奇的盒模型(BFC)

    上一篇我提到每一个元素都有自己的display属性,其属性值可以改变.其改变的方式,可以利用神奇的css盒模型(BFC). 盒模型,是css可视化格式化系统的基础,可以用于元素定位和网页布局.一个盒模 ...

  9. 代理模式 (Proxy Pattern)

    代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.而对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化.在某些情况下,一个对象不适合或者不能直接引用另 ...

  10. passing argument 3 of ‘wtk_hlv_rec_init’ discards ‘const’ qualifier from pointer target type

    -Werror,编译出现如下错误: src/wtk/exam/wtk_ndx.c:154:6: error: passing argument 3 of ‘wtk_hlv_rec_init’ disc ...