77import com .intellij .execution .configurations .GeneralCommandLine ;
88import com .intellij .execution .process .CapturingProcessHandler ;
99import com .intellij .execution .process .ProcessOutput ;
10+ import com .intellij .notification .Notification ;
11+ import com .intellij .notification .NotificationType ;
12+ import com .intellij .notification .Notifications ;
1013import com .intellij .openapi .editor .Document ;
1114import com .intellij .openapi .progress .EmptyProgressIndicator ;
1215import com .intellij .openapi .progress .ProcessCanceledException ;
1720import com .intellij .util .DocumentUtil ;
1821import com .intellij .util .execution .ParametersListUtil ;
1922import org .jetbrains .annotations .NotNull ;
20-
21- import java .nio .file .Paths ;
23+ import org .w3c .dom .NamedNodeMap ;
24+ import org .w3c .dom .Node ;
25+ import org .w3c .dom .NodeList ;
26+ import org .xml .sax .InputSource ;
27+ import org .xml .sax .SAXException ;
28+
29+ import javax .xml .parsers .DocumentBuilder ;
30+ import javax .xml .parsers .DocumentBuilderFactory ;
31+ import javax .xml .parsers .ParserConfigurationException ;
32+ import java .io .File ;
33+ import java .io .IOException ;
34+ import java .io .StringReader ;
2235import java .util .ArrayList ;
2336import java .util .List ;
24- import java .util .Scanner ;
25- import java .util .regex .Matcher ;
26- import java .util .regex .Pattern ;
2737
2838public class CppCheckInspectionImpl {
2939 private static ProblemHighlightType severityToHighlightType (@ NotNull final String severity ) {
@@ -45,72 +55,118 @@ private static ProblemHighlightType severityToHighlightType(@NotNull final Strin
4555 }
4656 }
4757
58+ // TODO: make configurable
59+ private static final boolean VERBOSE_LOG = false ;
4860 private static final String INCONCLUSIVE_TEXT = ":inconclusive" ;
4961
5062 @ NotNull
51- public static List <ProblemDescriptor > parseOutput (@ NotNull PsiFile psiFile ,
52- @ NotNull InspectionManager manager ,
53- @ NotNull Document document ,
54- @ NotNull String cppcheckOutput ,
55- @ NotNull String sourceFileName ) {
56- List <ProblemDescriptor > descriptors = new ArrayList <>();
57- Scanner scanner = new Scanner (cppcheckOutput );
58-
59- //Notifications.Bus.notify(new Notification("cppcheck",
60- // "Info",
61- // psiFile.getVirtualFile().getCanonicalPath() + "\n" +
62- // cppcheckOutput,
63- // NotificationType.INFORMATION));
64-
65- // Example output line:
66- // [C:\Users\John Hagen\ClionProjects\test\main.cpp:12]: (style) Variable 'a' is not assigned a value.
67- // [main.cpp:12] -> [main.cpp:14]: (performance) Variable 'a' is reassigned a value before the old one has been used.
68- // One line:
69- // [C:\Users\User\AppData\Local\Temp\___1main.cpp:14]: (warning:inconclusive) accessMoved: Access of moved variable 'a'.
70- // [C:\Users\User\AppData\Local\Temp\___1main.cpp:14]: (style) unreadVariable: Variable 'name' is assigned a value that is never used.
71- Pattern pattern = Pattern .compile ("^\\ [(.+?):(\\ d+)](?:\\ s+->\\ s+\\ [.+])?:\\ s+\\ ((\\ w+:?\\ w+)\\ )\\ s+(.+)" );
72-
73- String line ;
74- while (scanner .hasNext ()) {
75- line = scanner .nextLine ();
76- Matcher matcher = pattern .matcher (line );
77-
78- if (!matcher .matches ()) {
63+ public static List <ProblemDescriptor > parseOutput (@ NotNull final PsiFile psiFile ,
64+ @ NotNull final InspectionManager manager ,
65+ @ NotNull final Document document ,
66+ @ NotNull final String cppcheckOutput ,
67+ @ NotNull final String sourceFileName ) throws IOException , SAXException , ParserConfigurationException {
68+
69+ if (VERBOSE_LOG ) {
70+ // TODO: provide XML output via a "Show Cppcheck output" action - event log messages are truncated
71+ Notifications .Bus .notify (new Notification ("Cppcheck" ,
72+ "Cppcheck execution output for " + psiFile .getName (),
73+ cppcheckOutput ,
74+ NotificationType .INFORMATION ));
75+ }
76+
77+ final List <ProblemDescriptor > descriptors = new ArrayList <>();
78+
79+ DocumentBuilderFactory dbf = DocumentBuilderFactory .newInstance ();
80+ DocumentBuilder db = dbf .newDocumentBuilder ();
81+ final org .w3c .dom .Document doc = db .parse (new InputSource (new StringReader (cppcheckOutput )));
82+
83+ final NodeList errors = doc .getElementsByTagName ("error" );
84+ for (int i = 0 ; i < errors .getLength (); ++i ) {
85+ /*
86+ <error id="accessMoved" severity="warning" msg="Access of moved variable 'a'." verbose="Access of moved variable 'a'." cwe="672" hash="6576707224072251515" inconclusive="true">
87+ <location file="/mnt/s/clion/example_lite_2/test.cpp" line="14" column="18" info="Access of moved variable 'a'."/>
88+ <location file="/mnt/s/clion/example_lite_2/test.cpp" line="13" column="7" info="Calling std::move(a)"/>
89+ <symbol>a</symbol>
90+ </error>
91+ */
92+
93+ final Node error = errors .item (i );
94+ final NamedNodeMap attributes = error .getAttributes ();
95+
96+ final String id = attributes .getNamedItem ("id" ).getNodeValue ();
97+
98+ // Skip the "toomanyconfigs" error
99+ /*
100+ <error id="toomanyconfigs" severity="information" msg="Too many #ifdef configurations - cppcheck only checks 1 of 12 configurations. Use --force to check all configurations." verbose="The checking of the file will be interrupted because there are too many #ifdef configurations. Checking of all #ifdef configurations can be forced by --force command line option or from GUI preferences. However that may increase the checking time." cwe="398">
101+ <location file="C:\Users\Name\AppData\Local\Temp\___valueflow.cpp" line="0" column="0"/>
102+ </error>
103+ */
104+ if (id .equals ("toomanyconfigs" )) {
105+ continue ;
106+ }
107+
108+ // Skip the "missingIncludeSystem" error
109+ /*
110+ <error id="missingIncludeSystem" severity="information" msg="Cppcheck cannot find all the include files (use --check-config for details)" verbose="Cppcheck cannot find all the include files. Cppcheck can check the code without the include files found. But the results will probably be more accurate if all the include files are found. Please check your project's include directories and add all of them as include directories for Cppcheck. To see what files Cppcheck cannot find use --check-config."/>
111+ */
112+ if (id .equals ("missingIncludeSystem" )) {
113+ continue ;
114+ }
115+
116+ final String severity = attributes .getNamedItem ("severity" ).getNodeValue ();
117+ final String errorMessage = attributes .getNamedItem ("msg" ).getNodeValue ();
118+ final Node inconclusiveNode = attributes .getNamedItem ("inconclusive" );
119+ final boolean inconclusive = inconclusiveNode != null && inconclusiveNode .getNodeValue ().equals ("true" );
120+
121+ Node location = null ;
122+
123+ // look for the first "location" child name
124+ final NodeList children = error .getChildNodes ();
125+ for (int j = 0 ; j < children .getLength (); ++j ) {
126+ final Node child = children .item (j );
127+ if (child .getNodeName ().equals ("location" )) {
128+ location = child ;
129+ break ;
130+ }
131+ }
132+
133+ // ignore entries without location e.g. missingIncludeSystem
134+ if (location == null ) {
79135 continue ;
80136 }
81137
82- final String fileName = Paths . get ( matcher . group ( 1 )). getFileName (). toString ();
83- int lineNumber = Integer . parseInt ( matcher . group ( 2 ), 10 );
84- final String severity = matcher . group ( 3 );
85- final String errorMessage = matcher . group ( 4 );
138+ final NamedNodeMap locationAttributes = location . getAttributes ();
139+ final String fileName = new File ( locationAttributes . getNamedItem ( "file" ). getNodeValue ()). getName ( );
140+ int lineNumber = Integer . parseInt ( locationAttributes . getNamedItem ( "line" ). getNodeValue () );
141+ final int column = Integer . parseInt ( locationAttributes . getNamedItem ( "column" ). getNodeValue ()); // TODO
86142
87- // If a .c or .cpp file #include's header files, Cppcheck will also run on the header files and print
88- // any errors. These errors don't apply to the current .cpp field and should not be drawn. They can
143+ // If a file #include's header files, Cppcheck will also run on the header files and print
144+ // any errors. These errors don't apply to the current file and should not be drawn. They can
89145 // be distinguished by checking the file name.
90- // Example:
91- // Checking Test.cpp ...
92- // [Test.h:2]: (style) Unused variable: x
93- // [Test.cpp:3]: (style) Unused variable: y
94146 if (!fileName .equals (sourceFileName )) {
95147 continue ;
96148 }
97149
98- // Cppcheck error or parsing error.
150+ // Cppcheck error
99151 if (lineNumber <= 0 || lineNumber > document .getLineCount ()) {
152+ Notifications .Bus .notify (new Notification ("Cppcheck" ,
153+ "Cppcheck line number out-of-bounds " + i ,
154+ id + " " + severity + " " + inconclusive + " " + errorMessage + " " + fileName + " " + lineNumber + " " + column ,
155+ NotificationType .ERROR ));
100156 continue ;
101157 }
102158
103159 // Document counts lines starting at 0, rather than 1 like in cppcheck.
104160 lineNumber -= 1 ;
105161
106162 final int lineStartOffset = DocumentUtil .getFirstNonSpaceCharOffset (document , lineNumber );
107- final int lintEndOffset = document .getLineEndOffset (lineNumber );
163+ final int lineEndOffset = document .getLineEndOffset (lineNumber );
108164
109165 final ProblemDescriptor problemDescriptor = manager .createProblemDescriptor (
110166 psiFile ,
111- TextRange .create (lineStartOffset , lintEndOffset ),
112- "Cppcheck: (" + severity + ") " + errorMessage ,
113- severityToHighlightType (severity . replace ( INCONCLUSIVE_TEXT , "" ) ),
167+ TextRange .create (lineStartOffset , lineEndOffset ),
168+ "Cppcheck: (" + severity + ( inconclusive ? INCONCLUSIVE_TEXT : "" ) + ") " + id + ": " + errorMessage ,
169+ severityToHighlightType (severity ),
114170 true );
115171 descriptors .add (problemDescriptor );
116172 }
@@ -120,20 +176,11 @@ public static List<ProblemDescriptor> parseOutput(@NotNull PsiFile psiFile,
120176 private static final int TIMEOUT_MS = 60 * 1000 ;
121177
122178 public static String executeCommandOnFile (@ NotNull final String command ,
123- @ NotNull final String options ,
124- @ NotNull final String filePath ,
125- final String cppcheckMisraPath ) throws CppcheckError , ExecutionException {
126-
127- if (options .contains ("--template" )) {
128- throw new CppcheckError ("Cppcheck options contain --template field. " +
129- "Please remove this, the plugin defines its own." );
130- }
131-
179+ @ NotNull final String options ,
180+ @ NotNull final String filePath ,
181+ final String cppcheckMisraPath ) throws CppcheckError , ExecutionException {
132182 final GeneralCommandLine cmd = new GeneralCommandLine ()
133183 .withExePath (command )
134- .withParameters (ParametersListUtil .parse (
135- "--template=\" [{file}:{line}]: ({severity}{inconclusive:" + INCONCLUSIVE_TEXT +
136- "}) {id}: {message}\" " ))
137184 .withParameters (ParametersListUtil .parse (options ))
138185 .withParameters (ParametersListUtil .parse (filePath ));
139186
@@ -168,7 +215,7 @@ public static String executeCommandOnFile(@NotNull final String command,
168215 // MISRA Mode and something went wrong with the misra addon
169216 throw new CppcheckError ("MISRA Bail\n " +
170217 cmd .getCommandLineString () + "\n " +
171- "stdout: " + output .getStdout () + "\n " +
218+ "stdout: " + output .getStdout () + "\n " +
172219 "stderr: " + output .getStderr ());
173220 }
174221 }
0 commit comments