Clang调试诊断信息Expressive Diagnostics

Clang调试诊断信息Expressive Diagnostics

除了快速和实用,目标是使 Clang用户友好。命令行编译器,使编译器生成的诊断(错误和警告消息)尽可能有用。有几种方法可以做到这一点。依据命令行编译器经验,将 Clang 输出与 GCC 4.9 的输出进行对比。

Column Numbers and Caret Diagnostics

首先,clang 生成的所有诊断,都包含完整的列号信息。clang 命令行编译器驱动程序使用此信息打印“点诊断”。(IDE 可以使用这些信息,显示内嵌错误标记)。可以很容易地准确理解特定代码段中的错误。

即使在字符串内部,点(绿色的“^”字符)准确地显示了问题。使得跳转到问题变得非常容易,当同一字符的多个实例,出现在一行上时,有所帮助。

  $ clang -fsyntax-only format-strings.c

  format-strings.c:91:13: warning: '.*' specified field precision is missing a matching 'int' argument

printf("%.*d");

 

GCC 遵循 Clang 的做法,现在能够提供诊断列,在结果中包含源文本片段。但是,Clang 的列号要准确得多,指向有问题的格式说明符,不是解析器在检测到问题时,到达的)字符。默认情况下,Clang 的诊断是彩色的,更容易与附近的文本区分开来。

Range Highlighting for Related Text

Clang 捕获准确跟踪程序中表达式、语句和其它结构的范围信息,这些信息使诊断突出显示相关信息。下面有点荒谬的示例中,不需要查看原始源代码,可了解基于 Clang 错误的错误。clang 打印了一个点,确切地知道说明哪个加号。范围信息突出显示加号的左侧和右侧,编译器正在执行的内容一目了然。范围信息对于涉及优先级问题和许多其它情况非常有用。

  $ gcc-4.9 -fsyntax-only t.c

  t.c: In function 'int f(int, int)':

  t.c:7:39: error: invalid operands to binary + (have 'int' and 'struct A')

     return y + func(y ? ((SomeA.X + 40) + SomeA) / 42 + SomeA.X : SomeA.X);

                                         ^

  $ clang -fsyntax-only t.c

  t.c:7:39: error: invalid operands to binary expression ('int' and 'struct A')

    return y + func(y ? ((SomeA.X + 40) + SomeA) / 42 + SomeA.X : SomeA.X);

                         ~~~~~~~~~~~~~~ ^ ~~~~~

Precision in Wording

一个细节,尽力使clang 的诊断,准确包含有关错误和原因的相关信息。左边和右边的推断类型是什么,不会重复显而易见的内容(例如,一个“二进制+”)。

下面示例中,不仅会告诉有问题,还会确切地说明原因,告诉类型是什么(如果是一个复杂的子表达式,如调用重载函数)。这种对细节的关注,使得更容易理解和快速解决问题。

  $ gcc-4.9 -fsyntax-only t.c

  t.c:5:11: error: invalid type argument of unary '*' (have 'int')

    return *SomeA.X;

           ^

  $ clang -fsyntax-only t.c

  t.c:5:11: error: indirection requires pointer operand ('int' invalid)

    int y = *SomeA.X;

            ^~~~~~~~

Typedef Preservation and Selective Unwrapping

许多程序员使用高级用户定义类型、typedef 和其它语法,引用程序中的类型。这可以缩写非常长的类型,在诊断中保留类型名很有用。然而,有时非常简单的 typedef,可以包装琐碎的类型,剥离 typedef,了解正在发生的事情。Clang 旨在很好地处理这两种情况。

以下示例显示在 C 中,保留 typedef 的重要性。

  $ clang -fsyntax-only t.c

  t.c:15:11: error: can't convert between vector values of different size ('__m128' and 'int const *')

    myvec[1]/P;

    ~~~~~~~~^~

下例显示了编译器公开 typedef 的底层详细信息。如果用户对系统“pid_t”typedef 的定义方式感到困惑,Clang 会用“aka”帮助显示。

  $ clang -fsyntax-only t.c

  t.c:13:9: error: member reference base type 'pid_t' (aka 'int') is not a structure or union

    myvar = myvar.x;

            ~~~~~ ^

在 C++ 中,类型保留包括保留写入类型名称的任何限定。如果采用一小段代码,例如:

namespace services {

  struct WebService {  };

}

namespace myapp {

  namespace servers {

    struct Server {  };

  }

}

 

 

using namespace myapp;

void addHTTPService(servers::Server const &server, ::services::WebService const *http) {

  server += http;

}

and then compile it, we see that Clang is both providing accurate information and is retaining the types as written by the user (e.g., "servers::Server", "::services::WebService"):

编译后,看到 Clang 既提供了准确的信息,保留了用户编写的类型(例如,“servers::Server”、“::services::WebService”):

  $ clang -fsyntax-only t.cpp

  t.cpp:9:10: error: invalid operands to binary expression ('servers::Server const' and '::services::WebService const *')

    server += http;

    ~~~~~~ ^  ~~~~

自然地,类型保留扩展到模板的使用,在源代码中,Clang 保留有关特定模板特化(如std::vector<Real>),如何拼写的信息。例如:

  $ clang -fsyntax-only t.cpp

  t.cpp:12:7: error: incompatible type assigning 'vector<Real>', expected 'std::string' (aka 'class std::basic_string<char>')

    str = vec;

        ^ ~~~

Fix-it Hints

“Fix-it”提示为Fix-it源代码中的小、本地化问题提供建议。当 Clang 生成可以解决的特定问题的诊断时(例如,非标准或冗余语法、缺少关键字、常见错误等),可能以代码转换的形式提供特定指导,纠正错误问题。以下示例中,Clang 警告使用自 1993 年以来已被视为过时的 GCC 扩展。应删除带下划线的代码,然后替换为点线下方的代码(".x =" or ".y =", respectively)。

  $ clang t.c

  t.c:5:28: warning: use of GNU old-style field designator extension

  struct point origin = { x: 0.0, y: 0.0 };

                          ~~ ^

                          .x =

  t.c:5:36: warning: use of GNU old-style field designator extension

  struct point origin = { x: 0.0, y: 0.0 };

                                  ~~ ^

                                  .y =

“Fix-it”提示,对于解决常见的用户错误和误解最有用。例如,C++ 用户通常会忘记显式特化类模板的语法,如下例中的错误所示。同样,在描述问题后,Clang 提供了修复--添加 --template<>作为诊断的一部分。

  $ clang t.cpp

  t.cpp:9:3: error: template specialization requires 'template<>'

    struct iterator_traits<file_iterator> {

    ^

    template<>

Template Type Diffing

模板类型可能很长且难以阅读。当错误消息的一部分时更是如此。Clang 不只是打印出类型名称,而是有足够的信息来删除公共元素并突出显示差异。为了更清楚地显示模板结构,模板类型也可以打印为缩进的文本树。

默认值:带有类型省略的模板差异

Default: template diff with type elision

t.cc:4:5: note: candidate function not viable: no known conversion from 'vector<map<[...], float>>' to 'vector<map<[...], double>>' for 1st argument;

-fno-elide-type: template diff without elision

t.cc:4:5: note: candidate function not viable: no known conversion from 'vector<map<int, float>>' to 'vector<map<int, double>>' for 1st argument;

-fdiagnostics-show-template-tree: template tree printing with elision

t.cc:4:5: note: candidate function not viable: no known conversion for 1st argument;

  vector<

    map<

      [...],

      [float != double]>>

-fdiagnostics-show-template-tree -fno-elide-type: template tree printing with no elision

t.cc:4:5: note: candidate function not viable: no known conversion for 1st argument;

  vector<

    map<

      int,

      [float != double]>>

Automatic Macro Expansion

许多错误,有时发生在深度嵌套的宏中。对于传统的编译器,需要深入研究宏的定义,了解是如何陷入困境的。下面简单示例,展示了 Clang 如何通过自动打印诊断信息和嵌套范围信息,通过宏实例化的,展示了其它一些部分,如何在更大的示例中工作。

  $ clang -fsyntax-only t.c

  t.c:80:3: error: invalid operands to binary expression ('typeof(P)' (aka 'struct mystruct') and 'typeof(F)' (aka 'float'))

    X = MYMAX(P, F);

        ^~~~~~~~~~~

  t.c:76:94: note: expanded from:

  #define MYMAX(A,B)    __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })

                                                                                           ~~~ ^ ~~~

Here's another real world warning that occurs in the "window" Unix package (which implements the "wwopen" class of APIs):

这是在“window” Unix 包(实现了“wwopen”类 API)中出现的,另一个真实世界警告:

  $ clang -fsyntax-only t.c

  t.c:22:2: warning: type specifier missing, defaults to 'int'

          ILPAD();

          ^

  t.c:17:17: note: expanded from:

  #define ILPAD() PAD((NROW - tt.tt_row) * 10)    /* 1 ms per char */

                  ^

  t.c:14:2: note: expanded from:

          register i; \

          ^

在实践中,发现 Clang 对宏的处理,在多嵌套宏中比在简单宏中更有用。

Quality of Implementation and Attention to Detail

最后,投入了大量工作打磨小事,随着时间的推移累积起来,有助于提供出色的用户体验。

以下示例显示,从forgetting a 的简单情况中恢复;经过一个结构体定义,比GCC好得多。

  $ cat t.cc

  template<class T>

  class a {};

  struct b {}

  a<int> c;

  $ gcc-4.9 t.cc

  t.cc:4:8: error: invalid declarator before 'c'

   a<int> c;

           ^

  $ clang t.cc

  t.cc:3:12: error: expected ';' after struct

  struct b {}

             ^

             ;

下面的例子表明,在 GCC 无法应对的复杂情况下,能很好地诊断和恢复缺少的 typename关键字。

  $ cat t.cc

  template<class T> void f(T::type) { }

  struct A { };

  void g()

  {

      A a;

      f<A>(a);

  }

  $ gcc-4.9 t.cc

  t.cc:1:33: error: variable or field 'f' declared void

   template<class T> void f(T::type) { }

                                   ^

  t.cc: In function 'void g()':

  t.cc:6:5: error: 'f' was not declared in this scope

       f<A>(a);

       ^

  t.cc:6:8: error: expected primary-expression before '>' token

       f<A>(a);

          ^

  $ clang t.cc

  t.cc:1:26: error: missing 'typename' prior to dependent type name 'T::type'

  template<class T> void f(T::type) { }

                           ^~~~~~~

                           typename

  t.cc:6:5: error: no matching function for call to 'f'

      f<A>(a);

      ^~~~

  t.cc:1:24: note: candidate template ignored: substitution failure [with T = A]: no type named 'type' in 'A'

  template<class T> void f(T::type) { }

                         ^    ~~~~

这些细节中的每一个都是次要的,加起来可以提供更加精致的体验。

 

参考链接:

https://clang.llvm.org/diagnostics.html

posted @ 2021-10-16 18:06  吴建明wujianming  阅读(585)  评论(0编辑  收藏  举报