Hämmastav feats Ükskõik Error Recovery

Link: http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html

lisaks parsing ja teeniva masin kood oma allikas faile, kui kehtiv, tõlkija frontend töö on ka avastada vale kood ja annan teile vihje, mis selgitab, mis on valesti, nii, et saate probleemi lahendada. Viga võib olla kas sirge-up kehtetu (viga) või võib lihtsalt olla midagi, mis on seaduslik, kuid tundub tõesti kahtlane (hoiatus). Need vead ja hoiatused on tuntud kui kompilaator ‘diagnostika’, ja Ükskõik eesmärk on minna üle ja kaugemale call of duty pakkuda tõeliselt hämmastav kogemus.

Pärast vaheaega, näitame mõned näited valdkondadest, kus Ükskõik üritab eriti raske. Muud näited, Ükskõik veebileht on ka lehe diagnostika ja Doug näitas, kuidas Ükskõik diagnoosid kahe etapi nimi lookup küsimustes enne blogi postitus.

Uuenda: Teised inimesed on hakanud võrrelda oma lemmik kompilaator. Siin on OpenVMS Koostaja. E-posti Chris, kui sul on võrdlus soovite postitatud.

Need näited kasutada Apple GCC 4.2 kui võrrelda neid näiteid, kuid see ei ole tähendanud, et bash (vana versioon) PÄRSIA lahe koostöönõukogu. Paljud koostajad on neid erinevaid küsimusi ja me soovitame tungivalt, et te proovige näiteid oma lemmik kompilaator, et näha, kuidas ta seda teeb. Näited kõik näidanud, on paratamatult väike (vähendatud) näidet, mis näitavad probleem, kui sa näed neid reaalses elus, nad on sageli palju rohkem veenev :).

Tundmatu Typenames

Üks tüütu asi on parsing C ja C++ on, et sa pead teadma, mis on typename, et sõeluda kood. Näiteks “(x)(y)” võib olla loo sõna “(y)” tüüp “x” või see võib olla kõne “x” funktsioon “(y)” argument nimekirja, sõltuvalt sellest, kas x on teatud tüüpi või mitte. Kahjuks levinud viga on, et unusta lisada päis faili, mis tähendab, et kompilaator tõesti ei tea, kas midagi on teatud tüüpi või mitte, ja seetõttu on teha väga haritud arvan põhineb kontekstis. Siin on paar näidet:

$ cat t.m
NSString *P = @"foo";
$ clang t.m
t.m:4:1: error: unknown type name 'NSString'
NSString *P = @"foo";
^
$ gcc t.m
t.m:4: error: expected '=', ',', ';', 'asm' or '__attribute__' before '*' token

ja:

$ cat t.c
int foo(int x, pid_t y) {
  return x+y;
}
$ clang t.c
t.c:1:16: error: unknown type name 'pid_t'
int foo(int x, pid_t y) {
               ^
$ gcc t.c
t.c:1: error: expected declaration specifiers or '...' before 'pid_t'
t.c: In function 'foo':
t.c:2: error: 'y' undeclared (first use in this function)
t.c:2: error: (Each undeclared identifier is reported only once
t.c:2: error: for each function it appears in.)

Selline asi ka juhtub, C kui te unustate kasutada “struct stat’ asemel ‘stat’. Kui on üks ühine teema postitus, hästi taastumas poolt eeldamine, mida programmeerija, vaid aitab Ükskõik vältida kiirgav võlts järgi-vigu nagu kolm rida GCC kiirgab real 2.

Õigekirjakontrolli

One of the more visible things that Clang includes is a spell checker (also on reddit). The spell checker kicks in when you use an identifier that Clang doesn’t know: it checks against other close identifiers and suggests what you probably meant. Here are a few examples:

$ cat t.c
#include <inttypes.h>
int64 x;
$ clang t.c
t.c:2:1: error: unknown type name 'int64'; did you mean 'int64_t'?
int64 x;
^~~~~
int64_t
$ gcc t.c
t.c:2: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'x'

veel üks näide:

$ cat t.c
#include <sys/stat.h>
int foo(int x, struct stat *P) { 
  return P->st_blocksize*2;
}
$ clang t.c
t.c:4:13: error: no member named 'st_blocksize' in 'struct stat'; did you mean 'st_blksize'?
  return P->st_blocksize*2;
            ^~~~~~~~~~~~
            st_blksize
$ gcc t.c
t.c: In function ‘foo’:
t.c:4: error: 'struct stat' has no member named 'st_blocksize'

Suur asi õigekirja kontrollija on, et see saak mitmesuguseid ühiseid vigu, ja see ka aitab hiljem taastumist. Kood, mida hiljem kasutatakse “x”, näiteks, teab, et see on deklareeritud kui int64_t, nii et see ei viiks muid imelik jälgida vigu, et ei ole mingit mõtet. Ükskõik kasutab hästi teada, Levenshtein vahemaa funktsioon compute parim vaste välja võimalikud kandidaadid.

Typedef Jälgimine

Ükskõik jälgib typedefs kirjutada oma koodi hoolikalt, nii et see võib olla seotud vigade tüübid, mida kasutate oma kood. See võimaldab printida veateateid oma tingimustel, mis ei ole täielikult lahendatud ja mall instantiated kompilaator tingimustel. Samuti kasutab oma valikut info-ja caret, et näidata teile, mida sa kirjutasid, selle asemel, et proovida prindi see tagasi viia sind. On mitmeid näiteid selle kohta, Ükskõik diagnostika lehe, aga veel üks näide ei saa haiget:

$ cat t.cc
namespace foo {
  struct x { int y; };
}
namespace bar {
  typedef int y;
}
void test() {
  foo::x a;
  bar::y b;
  a + b;
}
$ clang t.cc
t.cc:10:5: error: invalid operands to binary expression ('foo::x' and 'bar::y' (aka 'int'))
  a + b;
  ~ ^ ~
$ gcc t.cc
t.cc: In function 'void test()':
t.cc:10: error: no match for 'operator+' in 'a + b'

See näitab, et ükskõik annab teile allikas nimesid, kui sa neid sõnu (“foo::x” ja “baar::y”, vastavalt), kuid see ka unwraps y-tüüp “aka” juhul, kui esindamise aluseks on tähtis. Muud koostajad tavaliselt annavad täiesti kasutu info, mis tegelikult ei ütle teile, mida probleem on. See on üllatavalt sisutihe nt GCC, kuid see tundub ka et puudu on mõned kriitilised andmed (nt miks ei ole matši). Samuti, kui väljend oli rohkem kui üks “a+b”, võite ette kujutada, et päris trüki see tagasi sind ei ole kõige kasulikum.

Kõige Keerukamatele Sõeluda

Üks viga, mida paljud algajad programmeerijad viga on, et nad kogemata määrata funktsioonid, selle asemel, et objektide kohta stack. Põhjuseks on see, et ebaselgus C++ keele grammatika, mis on lahendatud suvaline viisil. See on vältimatu osa C++, kuid vähemalt kompilaator peaks aitama teil mõista, mis on valesti. Siin on triviaalne näide:

$ cat t.cc
#include <vector>

int foo() {
  std::vector<std::vector<int> > X();
  return X.size();
}
$ clang t.cc
t.cc:5:11: error: base of member reference has function type       'std::vector<std::vector<int> > ()'; perhaps you meant to call this function with '()'?
  return X.size();
          ^
          ()
$ gcc t.cc
t.cc: In function ‘int foo()’:
t.cc:5: error: request for member ‘size’ in ‘X’, which is of non-class type ‘std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > ()()’

Ma joosta see asi, kui ma algselt deklareeritud vektor, võttes mõned argumendid (nt “10”, et täpsustada esialgset suurust), kuid refactor kood ja kõrvaldada. Muidugi, kui te ei eemalda sulgudes kood on tegelikult kuulutatakse funktsioon, mitte muutuja.

Siin te näete, et Ükskõik osutab üsna selgelt, et me oleme läinud ja deklareeritakse funktsiooni (see pakub isegi, et aidata teil seda kutsuda, kui sa unustasid (on)’s). GCC, teiselt poolt, on nii lootusetult segaduses, mida sa teed, vaid ka spews välja suur typename, et sa ei kirjuta (kui ei std::jaotaja pärit?). See on kurb, kuid tõsi, et on kogenud C++ programmeerija tõesti tähendab, et oled osav dešifreerimine veateated, mis oma kompilaator spews sind.

Kui sa lähed, et proovida rohkem klassikaline näide, kus see hammustab inimest, siis võib näha Ükskõik proovida isegi raskem:

$ cat t.cc
#include <fstream>
#include <vector>
#include <iterator>

int main() {
   std::ifstream ifs("file.txt");
   std::vector<char> v(std::istream_iterator<char>(ifs),
                       std::istream_iterator<char>());
        
   std::vector<char>::const_iterator it = v.begin();
   return 0;                   
}
$ clang t.cc
t.cc:8:23: warning: parentheses were disambiguated as a function declarator
   std::vector<char> v(std::istream_iterator<char>(ifs),
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
t.cc:11:45: error: member reference base type 'std::vector<char> (*)(std::istream_iterator<char>, std::istream_iterator<char> (*)())' is not a structure or union
   std::vector<char>::const_iterator it = v.begin();
                                          ~ ^
$ gcc t.cc
t.cc: In function ‘int main()’:
t.cc:11: error: request for member ‘begin’ in ‘v’, which is of non-class type
‘std::vector<char, std::allocator<char> > ()(std::istream_iterator<char, char, std::char_traits<char>, long int>, std::istream_iterator<char, char, std::char_traits<char>, long int> (*)())’

Sel juhul, Ükskõik suuruselt teine viga ei ole eriti suur (kuigi see ei anna palju rohkem kokkuvõtliku tüüp nimed), kuid see annab tõesti kriitiline hoiatus, ütlen teile, et parens näiteks kuulutavad funktsiooni ei kasutata, kuna parens jaoks argument.

Puudu Semikooloniga

Üks viga, mida ma tihti teha (võib-olla tänu metsikult vastuoluline grammatika C++, või võib-olla sellepärast, et ma olen lohakas ja on lühike tähelepanu span…) kukub semikoolon. Õnneks need on päris triviaalne lahendus, kui sa tead, mis toimub, kuid nad võivad põhjustada mõned tõesti segane veateadetes mõned koostajad. See juhtub isegi juhul, kui see on kohe selge, mis toimub inimese (kui nad ei pööra tähelepanu!). Näiteks:

$ cat t.c
struct foo { int x; }

typedef int bar;
$ clang t.c
t.c:1:22: error: expected ';' after struct
struct foo { int x; }
                     ^
                     ;
$ gcc t.c
t.c:3: error: two or more data types in declaration specifiers

Pane tähele, et GCC kiirgab viga asi, et järgmine probleem. Kui struct oli viimane asi, lõpus on päis, see tähendab, et saate lõpuks saada tõrketeate täiesti erinevat faili peale, kus probleem on. See probleem ka ühendite ise C++ (nagu seda teevad paljud teised), näiteks:

$ cat t2.cc
template<class t>
class a{}

class temp{};
a<temp> b;

class b {
}
$ clang t2.cc
t2.cc:2:10: error: expected ';' after class
class a{}
         ^
         ;
t2.cc:8:2: error: expected ';' after class
}
 ^
 ;
$ gcc t2.c
t2.cc:4: error: multiple types in one declaration
t2.cc:5: error: non-template type ‘a’ used as a template
t2.cc:5: error: invalid type in declaration before ‘;’ token
t2.cc:8: error: expected unqualified-id at end of input

Lisaks kiirgavad segane viga “mitu tüüpi ühe deklaratsiooni”, GCC läheb segadusse ise ka muul moel.

. vs -> Thinko

C++ koodi, vihjed ja viited sageli saad kasutada üsna läbisegi ja see on levinud kasutada . kui sa mõtled ->. Ükskõik tunnistab käesoleva ühise omamoodi viga, mis aitab teil välja:

$ cat t.cc
#include <map>

int bar(std::map<int, float> *X) {
  return X.empty();
}
$ clang t.cc
t.cc:4:11: error: member reference type 'std::map<int, float> *' is a pointer; maybe you meant to use '->'?
  return X.empty();
         ~^
          ->
$ gcc t.cc
t.cc: In function ‘int bar(std::map<int, float, std::less<int>, std::allocator<std::pair<const int, float> > >*)’:
t.cc:4: error: request for member ‘empty’ in ‘X’, which is of non-class type ‘std::map<int, float, std::less<int>, std::allocator<std::pair<const int, float> > >*’

Lisaks abivalmilt milles teatatakse, et teie osuti on “mitte-klassi tüüp”, see läheb välja oma viis õigekirja täielik määratlus std::map välja, mis ei ole kindlasti kasulik.

:: vs : Typo

Võib-olla see on ainult mulle, vaid ma pigem teha seda viga üsna natuke, uuesti samas kiire. C++ :: ettevõtja on kasutada eraldi pesastatud nimi specifiers, aga millegipärast ma tippima : selle asemel. Siin on väike näide, mis näitab, et idee:

$ cat t.cc
namespace x {
  struct a { };
}

x:a a2;
x::a a3 = a2;
$ clang t.cc
t.cc:5:2: error: unexpected ':' in nested name specifier
x:a a2;
 ^
 ::
$ gcc t.cc
t.cc:5: error: function definition does not declare parameters
t.cc:6: error: ‘a2’ was not declared in this scope

Lisaks saada tõrketeate õigus (ja oletada, lahenda asendamine “::”), Ükskõik “teab, mida sa mõtled” nii et see tegeleb järgnevate kasutab a2 õigesti. GCC seevastu saab segaduses, mis viga on, mis viib ta kiirgama võltsi vigu, igal kasutamine 2. Selles saab näha pisut täiendatud näide:

$ cat t2.cc
namespace x {
  struct a { };
}

template <typename t>
class foo {
};

foo<x::a> a1;
foo<x:a> a2;

x::a a3 = a2;
$ clang t2.cc
t2.cc:10:6: error: unexpected ':' in nested name specifier
foo<x:a> a2;
     ^
     ::
t2.cc:12:6: error: no viable conversion from 'foo<x::a>' to 'x::a'
x::a a3 = a2;
     ^    ~~
t2.cc:2:10: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'foo<x::a>' to 'x::a const' for 1st argument
  struct a { };
         ^
$ gcc t2.cc
t2.cc:10: error: template argument 1 is invalid
t2.cc:10: error: invalid type in declaration before ‘;’ token
t2.cc:12: error: conversion from ‘int’ to non-scalar type ‘x::a’ requested

Siin te näete, et Ükskõik suuruselt teine veateade on täpselt õige (ja on selgitatud). PÄRSIA lahe koostöönõukogu annab segane jälgida sõnumit ümber “int” x::. Kui tegime “seda” on pärit?

Aidates läbi peaaegu lootusetu olukordades

C++ on võimu vahend, mis annab teile palju trossi tulistada ise suu, samuti segada oma multi-paradigmad ja metafoorid. Kahjuks see võimsus annab teile palju võimalusi, et leida ennast peaaegu lootusetu olukord, kus sa tead, et “midagi on valesti”, kuid ei tea, mis tegelik probleem on või kuidas seda parandada. Õnneks, Ükskõik üritab olla teie jaoks olemas, isegi rasketes korda. Näiteks, siin on üks juhtum, mis hõlmab ebamäärane otsing:

$ cat t.cc
struct B1 { void f(); };
struct B2 { void f(double); };

struct I1 : B1 { };
struct I2 : B1 { };

struct D: I1, I2, B2 {
  using B1::f;  using B2::f;
  void g() {
    f(); 
  }
};
$ clang t.cc
t.cc:10:5: error: ambiguous conversion from derived class 'D' to base class 'B1':
    struct D -> struct I1 -> struct B1
    struct D -> struct I2 -> struct B1
    f(); 
    ^
$ gcc t.cc
t.cc: In member function ‘void D::g()’:
t.cc:10: error: ‘B1’ is an ambiguous base of ‘D’

Sel juhul näete, et mitte ainult ükskõik öelda, et seal on kahtlev, see ütleb teile täpselt, teed kaudu pärandi hierarhiat, mis on probleemid. Kui olete tegelevad mitte-triviaalne hierarhiat, ja kõik klassid ei ole ühte faili jõllis teile, see võib olla tõeline elu saver.

Et olla õiglane, GCC aeg-ajalt proovib aidata välja. Kahjuks, kui see ei nii see ei ole selge, kas see aitab rohkem, kui see valutab. Näiteks, kui teil kommentaar läbi kahe kasutades deklaratsioonid eespool toodud näide saad:

$ clang t.cc
t.cc:10:5: error: non-static member 'f' found in multiple base-class subobjects of type 'B1':
    struct D -> struct I1 -> struct B1
    struct D -> struct I2 -> struct B1
    f(); 
    ^
t.cc:1:18: note: member found by ambiguous name lookup
struct B1 { void f(); };
                 ^
$ gcc t.cc
t.cc: In member function ‘void D::g()’:
t.cc:10: error: reference to ‘f’ is ambiguous
t.cc:2: error: candidates are: void B2::f(double)
t.cc:1: error:                 void B1::f()
t.cc:1: error:                 void B1::f()
t.cc:10: error: reference to ‘f’ is ambiguous
t.cc:2: error: candidates are: void B2::f(double)
t.cc:1: error:                 void B1::f()
t.cc:1: error:                 void B1::f()

Tundub, et GCC on püüdnud siin, kuid miks on see kiirgav kaks vigade rida 10 ja miks on see trükkimine B1::f kaks korda iga? Kui ma saan need sorti vigu (mis on üsna haruldased, kuna ma ei kasuta mitut pärisosa, nagu seda sageli) ma tõesti väärtus selgus, kui hargnemist, mis toimub.

Veel üks asi… Merge Konflikte

Okei, see võib-olla läheb natuke palju, kuid kuidas muidu sa lähed, et jäävad täiesti armunud kompilaatorit?

$ cat t.c
void f0() {
<<<<<<< HEAD
    int x;
=======
    int y;
>>>>>>> whatever
}
$ clang t.c
t.c:2:1: error: version control conflict marker in file
<<<<<<< HEAD
^
$ gcc t.c
t.c: In function ‘f0’:
t.c:2: error: expected expression before ‘<<’ token
t.c:4: error: expected expression before ‘==’ token
t.c:6: error: expected expression before ‘>>’ token

Jep, ükskõik tegelikult tuvastab ühendamise konflikt ja korrastas üks pool konflikti. Sa ei taha saada tonni jama oma kompilaatori kohta selline lihtne viga, kas pole?

Ükskõik: meisterdatud jaoks tõelised programmeerijad, kes teevad võib teha aeg-ajalt viga. Miks settida vähem?

-Chris
Postitas Chris Lattner kell 11:20 Pärast hommikul

Leave a Reply