1 | /******************************************************************************* |
---|---|
2 | * Copyright (c) 2006, 2020 IBM Corporation and others. |
3 | * |
4 | * This program and the accompanying materials |
5 | * are made available under the terms of the Eclipse Public License 2.0 |
6 | * which accompanies this distribution, and is available at |
7 | * https://www.eclipse.org/legal/epl-2.0/ |
8 | * |
9 | * SPDX-License-Identifier: EPL-2.0 |
10 | * |
11 | * Contributors: |
12 | * IBM Corporation - initial API and implementation |
13 | *******************************************************************************/ |
14 | |
15 | package org.eclipse.jdt.core.dom; |
16 | |
17 | import java.util.ArrayList; |
18 | import java.util.List; |
19 | |
20 | import org.eclipse.jdt.core.compiler.CategorizedProblem; |
21 | import org.eclipse.jdt.core.compiler.CharOperation; |
22 | import org.eclipse.jdt.core.compiler.IProblem; |
23 | import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; |
24 | import org.eclipse.jdt.internal.compiler.parser.RecoveryScannerData; |
25 | import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; |
26 | import org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToIntArray; |
27 | |
28 | /** |
29 | * Internal AST visitor for propagating syntax errors. |
30 | */ |
31 | @SuppressWarnings({"rawtypes"}) |
32 | class ASTRecoveryPropagator extends DefaultASTVisitor { |
33 | private static final int NOTHING = -1; |
34 | HashtableOfObjectToIntArray endingTokens = new HashtableOfObjectToIntArray(); |
35 | { |
36 | this.endingTokens.put(AnonymousClassDeclaration.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
37 | this.endingTokens.put(ArrayAccess.class, new int[]{TerminalTokens.TokenNameRBRACKET}); |
38 | this.endingTokens.put(ArrayCreation.class, new int[]{NOTHING, TerminalTokens.TokenNameRBRACKET}); |
39 | this.endingTokens.put(ArrayInitializer.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
40 | this.endingTokens.put(ArrayType.class, new int[]{TerminalTokens.TokenNameRBRACKET}); |
41 | this.endingTokens.put(AssertStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
42 | this.endingTokens.put(Block.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
43 | this.endingTokens.put(BooleanLiteral.class, new int[]{TerminalTokens.TokenNamefalse, TerminalTokens.TokenNametrue}); |
44 | this.endingTokens.put(BreakStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
45 | this.endingTokens.put(CharacterLiteral.class, new int[]{TerminalTokens.TokenNameCharacterLiteral}); |
46 | this.endingTokens.put(ClassInstanceCreation.class, new int[]{TerminalTokens.TokenNameRBRACE, TerminalTokens.TokenNameRPAREN}); |
47 | this.endingTokens.put(ConstructorInvocation.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
48 | this.endingTokens.put(ContinueStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
49 | this.endingTokens.put(DoStatement.class, new int[]{TerminalTokens.TokenNameRPAREN}); |
50 | this.endingTokens.put(EmptyStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
51 | this.endingTokens.put(ExpressionStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
52 | this.endingTokens.put(FieldDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
53 | this.endingTokens.put(ImportDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
54 | this.endingTokens.put(Initializer.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
55 | this.endingTokens.put(MethodDeclaration.class, new int[]{NOTHING, TerminalTokens.TokenNameSEMICOLON}); |
56 | this.endingTokens.put(MethodInvocation.class, new int[]{TerminalTokens.TokenNameRPAREN}); |
57 | this.endingTokens.put(ModuleDeclaration.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
58 | this.endingTokens.put(ModuleDirective.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
59 | this.endingTokens.put(NullLiteral.class, new int[]{TerminalTokens.TokenNamenull}); |
60 | this.endingTokens.put(NumberLiteral.class, new int[]{TerminalTokens.TokenNameIntegerLiteral, TerminalTokens.TokenNameLongLiteral, TerminalTokens.TokenNameFloatingPointLiteral, TerminalTokens.TokenNameDoubleLiteral}); |
61 | this.endingTokens.put(PackageDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
62 | this.endingTokens.put(ParenthesizedExpression.class, new int[]{TerminalTokens.TokenNameRPAREN}); |
63 | this.endingTokens.put(PostfixExpression.class, new int[]{TerminalTokens.TokenNamePLUS_PLUS, TerminalTokens.TokenNameMINUS_MINUS}); |
64 | this.endingTokens.put(PrimitiveType.class, new int[]{TerminalTokens.TokenNamebyte, TerminalTokens.TokenNameshort, TerminalTokens.TokenNamechar, TerminalTokens.TokenNameint, TerminalTokens.TokenNamelong, TerminalTokens.TokenNamefloat, TerminalTokens.TokenNameboolean, TerminalTokens.TokenNamedouble, TerminalTokens.TokenNamevoid}); |
65 | this.endingTokens.put(ReturnStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
66 | this.endingTokens.put(SimpleName.class, new int[]{TerminalTokens.TokenNameIdentifier}); |
67 | this.endingTokens.put(SingleVariableDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
68 | this.endingTokens.put(StringLiteral.class, new int[]{TerminalTokens.TokenNameStringLiteral}); |
69 | this.endingTokens.put(SuperConstructorInvocation.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
70 | this.endingTokens.put(SuperMethodInvocation.class, new int[]{TerminalTokens.TokenNameRPAREN}); |
71 | this.endingTokens.put(SwitchCase.class, new int[]{TerminalTokens.TokenNameCOLON}); |
72 | this.endingTokens.put(SwitchStatement.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
73 | this.endingTokens.put(SynchronizedStatement.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
74 | this.endingTokens.put(ThisExpression.class, new int[]{TerminalTokens.TokenNamethis}); |
75 | this.endingTokens.put(ThrowStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
76 | this.endingTokens.put(TypeDeclaration.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
77 | this.endingTokens.put(TypeLiteral.class, new int[]{TerminalTokens.TokenNameclass}); |
78 | this.endingTokens.put(VariableDeclarationStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
79 | } |
80 | |
81 | private CategorizedProblem[] problems; |
82 | private boolean[] usedOrIrrelevantProblems; |
83 | |
84 | private RecoveryScannerData data; |
85 | private int blockDepth = 0; |
86 | private int lastEnd; |
87 | |
88 | private int[] insertedTokensKind; |
89 | private int[] insertedTokensPosition; |
90 | private boolean[] insertedTokensFlagged; |
91 | |
92 | private boolean[] removedTokensFlagged; |
93 | private boolean[] replacedTokensFlagged; |
94 | |
95 | private ArrayList<ASTNode> stack = new ArrayList<>(); |
96 | |
97 | ASTRecoveryPropagator(CategorizedProblem[] problems, RecoveryScannerData data) { |
98 | // visit Javadoc.tags() as well |
99 | this.problems = problems; |
100 | this.usedOrIrrelevantProblems = new boolean[problems.length]; |
101 | |
102 | this.data = data; |
103 | |
104 | if(this.data != null) { |
105 | |
106 | int length = 0; |
107 | for (int i = 0; i < data.insertedTokensPtr + 1; i++) { |
108 | length += data.insertedTokens[i].length; |
109 | } |
110 | this.insertedTokensKind = new int[length]; |
111 | this.insertedTokensPosition = new int[length]; |
112 | this.insertedTokensFlagged = new boolean[length]; |
113 | int tokenCount = 0; |
114 | for (int i = 0; i < data.insertedTokensPtr + 1; i++) { |
115 | for (int j = 0; j < data.insertedTokens[i].length; j++) { |
116 | this.insertedTokensKind[tokenCount] = data.insertedTokens[i][j]; |
117 | this.insertedTokensPosition[tokenCount] = data.insertedTokensPosition[i]; |
118 | tokenCount++; |
119 | } |
120 | } |
121 | |
122 | if(data.removedTokensPtr != -1) { |
123 | this.removedTokensFlagged = new boolean[data.removedTokensPtr + 1]; |
124 | } |
125 | if(data.replacedTokensPtr != -1) { |
126 | this.replacedTokensFlagged = new boolean[data.replacedTokensPtr + 1]; |
127 | } |
128 | } |
129 | } |
130 | |
131 | @Override |
132 | public void endVisit(Block node) { |
133 | this.blockDepth--; |
134 | if(this.blockDepth <= 0) { |
135 | flagNodeWithInsertedTokens(); |
136 | } |
137 | super.endVisit(node); |
138 | } |
139 | |
140 | |
141 | |
142 | @Override |
143 | public boolean visit(Block node) { |
144 | boolean visitChildren = super.visit(node); |
145 | this.blockDepth++; |
146 | return visitChildren; |
147 | } |
148 | |
149 | @Override |
150 | protected boolean visitNode(ASTNode node) { |
151 | if(this.blockDepth > 0) { |
152 | int start = node.getStartPosition(); |
153 | int end = start + node.getLength() - 1; |
154 | |
155 | // continue to visit the node only if it contains tokens modifications |
156 | |
157 | if(this.insertedTokensFlagged != null) { |
158 | for (int i = 0; i < this.insertedTokensFlagged.length; i++) { |
159 | if(this.insertedTokensPosition[i] >= start && |
160 | this.insertedTokensPosition[i] <= end) { |
161 | return true; |
162 | } |
163 | } |
164 | } |
165 | |
166 | if(this.removedTokensFlagged != null) { |
167 | for (int i = 0; i <= this.data.removedTokensPtr; i++) { |
168 | if(this.data.removedTokensStart[i] >= start && |
169 | this.data.removedTokensEnd[i] <= end) { |
170 | return true; |
171 | } |
172 | } |
173 | } |
174 | |
175 | if(this.replacedTokensFlagged != null) { |
176 | for (int i = 0; i <= this.data.replacedTokensPtr; i++) { |
177 | if(this.data.replacedTokensStart[i] >= start && |
178 | this.data.replacedTokensEnd[i] <= end) { |
179 | return true; |
180 | } |
181 | } |
182 | } |
183 | |
184 | return false; |
185 | } |
186 | return true; |
187 | } |
188 | |
189 | @Override |
190 | protected void endVisitNode(ASTNode node) { |
191 | int start = node.getStartPosition(); |
192 | int end = start + node.getLength() - 1; |
193 | |
194 | // is inside diet part of the ast |
195 | if(this.blockDepth < 1) { |
196 | switch (node.getNodeType()) { |
197 | case ASTNode.ANNOTATION_TYPE_DECLARATION: |
198 | case ASTNode.COMPILATION_UNIT: |
199 | case ASTNode.ENUM_DECLARATION: |
200 | case ASTNode.FIELD_DECLARATION: |
201 | case ASTNode.IMPORT_DECLARATION: |
202 | case ASTNode.INITIALIZER: |
203 | case ASTNode.METHOD_DECLARATION: |
204 | case ASTNode.MODULE_DECLARATION: |
205 | case ASTNode.PACKAGE_DECLARATION: |
206 | case ASTNode.TYPE_DECLARATION: |
207 | case ASTNode.MARKER_ANNOTATION: |
208 | case ASTNode.NORMAL_ANNOTATION: |
209 | case ASTNode.SINGLE_MEMBER_ANNOTATION: |
210 | case ASTNode.BLOCK: |
211 | if(markIncludedProblems(start, end)) { |
212 | node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
213 | } |
214 | break; |
215 | } |
216 | } else { |
217 | markIncludedProblems(start, end); |
218 | |
219 | if(this.insertedTokensFlagged != null) { |
220 | if(this.lastEnd != end) { |
221 | flagNodeWithInsertedTokens(); |
222 | } |
223 | this.stack.add(node); |
224 | } |
225 | |
226 | if(this.removedTokensFlagged != null) { |
227 | for (int i = 0; i <= this.data.removedTokensPtr; i++) { |
228 | if(!this.removedTokensFlagged[i] && |
229 | this.data.removedTokensStart[i] >= start && |
230 | this.data.removedTokensEnd[i] <= end) { |
231 | node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
232 | this.removedTokensFlagged[i] = true; |
233 | } |
234 | } |
235 | } |
236 | |
237 | if(this.replacedTokensFlagged != null) { |
238 | for (int i = 0; i <= this.data.replacedTokensPtr; i++) { |
239 | if(!this.replacedTokensFlagged[i] && |
240 | this.data.replacedTokensStart[i] >= start && |
241 | this.data.replacedTokensEnd[i] <= end) { |
242 | node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
243 | this.replacedTokensFlagged[i] = true; |
244 | } |
245 | } |
246 | } |
247 | } |
248 | this.lastEnd = end; |
249 | } |
250 | |
251 | private void flagNodeWithInsertedTokens() { |
252 | if(this.insertedTokensKind != null && this.insertedTokensKind.length > 0) { |
253 | int s = this.stack.size(); |
254 | for (int i = s - 1; i > -1; i--) { |
255 | flagNodesWithInsertedTokensAtEnd(this.stack.get(i)); |
256 | } |
257 | for (int i = 0; i < s; i++) { |
258 | flagNodesWithInsertedTokensInside(this.stack.get(i)); |
259 | } |
260 | this.stack = new ArrayList<>(); |
261 | } |
262 | } |
263 | |
264 | private boolean flagNodesWithInsertedTokensAtEnd(ASTNode node) { |
265 | int[] expectedEndingToken = this.endingTokens.get(node.getClass()); |
266 | if (expectedEndingToken != null) { |
267 | int start = node.getStartPosition(); |
268 | int end = start + node.getLength() - 1; |
269 | |
270 | boolean flagParent = false; |
271 | done : for (int i = this.insertedTokensKind.length - 1; i > -1 ; i--) { |
272 | if(!this.insertedTokensFlagged[i] && |
273 | this.insertedTokensPosition[i] == end){ |
274 | this.insertedTokensFlagged[i] = true; |
275 | for (int j = 0; j < expectedEndingToken.length; j++) { |
276 | if(expectedEndingToken[j] == this.insertedTokensKind[i]) { |
277 | node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
278 | break done; |
279 | } |
280 | } |
281 | flagParent = true; |
282 | } |
283 | } |
284 | |
285 | if(flagParent) { |
286 | ASTNode parent = node.getParent(); |
287 | while (parent != null) { |
288 | parent.setFlags(node.getFlags() | ASTNode.RECOVERED); |
289 | if((parent.getStartPosition() + parent.getLength() - 1) != end) { |
290 | parent = null; |
291 | } else { |
292 | parent = parent.getParent(); |
293 | } |
294 | } |
295 | } |
296 | } |
297 | return true; |
298 | } |
299 | |
300 | private boolean flagNodesWithInsertedTokensInside(ASTNode node) { |
301 | int start = node.getStartPosition(); |
302 | int end = start + node.getLength() - 1; |
303 | for (int i = 0; i < this.insertedTokensKind.length; i++) { |
304 | if(!this.insertedTokensFlagged[i] && |
305 | start <= this.insertedTokensPosition[i] && |
306 | this.insertedTokensPosition[i] < end){ |
307 | node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
308 | this.insertedTokensFlagged[i] = true; |
309 | } |
310 | } |
311 | return true; |
312 | } |
313 | |
314 | private boolean markIncludedProblems(int start, int end) { |
315 | boolean foundProblems = false; |
316 | next: for (int i = 0, max = this.problems.length; i < max; i++) { |
317 | CategorizedProblem problem = this.problems[i]; |
318 | |
319 | if(this.usedOrIrrelevantProblems[i]) continue next; |
320 | |
321 | switch(problem.getID()) { |
322 | case IProblem.ParsingErrorOnKeywordNoSuggestion : |
323 | case IProblem.ParsingErrorOnKeyword : |
324 | case IProblem.ParsingError : |
325 | case IProblem.ParsingErrorNoSuggestion : |
326 | case IProblem.ParsingErrorInsertTokenBefore : |
327 | case IProblem.ParsingErrorInsertTokenAfter : |
328 | case IProblem.ParsingErrorDeleteToken : |
329 | case IProblem.ParsingErrorDeleteTokens : |
330 | case IProblem.ParsingErrorMergeTokens : |
331 | case IProblem.ParsingErrorInvalidToken : |
332 | case IProblem.ParsingErrorMisplacedConstruct : |
333 | case IProblem.ParsingErrorReplaceTokens : |
334 | case IProblem.ParsingErrorNoSuggestionForTokens : |
335 | case IProblem.ParsingErrorUnexpectedEOF : |
336 | case IProblem.ParsingErrorInsertToComplete : |
337 | case IProblem.ParsingErrorInsertToCompleteScope : |
338 | case IProblem.ParsingErrorInsertToCompletePhrase : |
339 | case IProblem.EndOfSource : |
340 | case IProblem.InvalidHexa : |
341 | case IProblem.InvalidOctal : |
342 | case IProblem.InvalidCharacterConstant : |
343 | case IProblem.InvalidEscape : |
344 | case IProblem.InvalidInput : |
345 | case IProblem.InvalidUnicodeEscape : |
346 | case IProblem.InvalidFloat : |
347 | case IProblem.NullSourceString : |
348 | case IProblem.UnterminatedString : |
349 | case IProblem.UnterminatedComment : |
350 | case IProblem.InvalidDigit : |
351 | break; |
352 | default: |
353 | this.usedOrIrrelevantProblems[i] = true; |
354 | continue next; |
355 | |
356 | } |
357 | |
358 | int problemStart = problem.getSourceStart(); |
359 | int problemEnd = problem.getSourceEnd(); |
360 | if ((start <= problemStart) && (problemStart <= end) || |
361 | (start <= problemEnd) && (problemEnd <= end)) { |
362 | this.usedOrIrrelevantProblems[i] = true; |
363 | foundProblems = true; |
364 | } |
365 | } |
366 | return foundProblems; |
367 | } |
368 | |
369 | @Override |
370 | public void endVisit(ExpressionStatement node) { |
371 | endVisitNode(node); |
372 | if ((node.getFlags() & ASTNode.RECOVERED) == 0) return; |
373 | Expression expression = node.getExpression(); |
374 | if (expression.getNodeType() == ASTNode.ASSIGNMENT) { |
375 | Assignment assignment = (Assignment) expression; |
376 | Expression rightHandSide = assignment.getRightHandSide(); |
377 | if (rightHandSide.getNodeType() == ASTNode.SIMPLE_NAME) { |
378 | SimpleName simpleName = (SimpleName) rightHandSide; |
379 | if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) { |
380 | Expression expression2 = assignment.getLeftHandSide(); |
381 | // unparent the expression to add it in the expression stateemnt |
382 | expression2.setParent(null, null); |
383 | expression2.setFlags(expression2.getFlags() | ASTNode.RECOVERED); |
384 | node.setExpression(expression2); |
385 | } |
386 | } |
387 | } |
388 | } |
389 | |
390 | @Override |
391 | public void endVisit(ForStatement node) { |
392 | endVisitNode(node); |
393 | List initializers = node.initializers(); |
394 | if (initializers.size() == 1) { |
395 | Expression expression = (Expression) initializers.get(0); |
396 | if (expression.getNodeType() == ASTNode.VARIABLE_DECLARATION_EXPRESSION) { |
397 | VariableDeclarationExpression variableDeclarationExpression = (VariableDeclarationExpression) expression; |
398 | List fragments = variableDeclarationExpression.fragments(); |
399 | for (int i = 0, max = fragments.size(); i <max; i++) { |
400 | VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragments.get(i); |
401 | SimpleName simpleName = fragment.getName(); |
402 | if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) { |
403 | fragments.remove(fragment); |
404 | variableDeclarationExpression.setFlags(variableDeclarationExpression.getFlags() | ASTNode.RECOVERED); |
405 | } |
406 | } |
407 | } |
408 | } |
409 | } |
410 | |
411 | @Override |
412 | public void endVisit(VariableDeclarationStatement node) { |
413 | endVisitNode(node); |
414 | List fragments = node.fragments(); |
415 | for (int i = 0, max = fragments.size(); i <max; i++) { |
416 | VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragments.get(i); |
417 | Expression expression = fragment.getInitializer(); |
418 | if (expression == null) continue; |
419 | if ((expression.getFlags() & ASTNode.RECOVERED) == 0) continue; |
420 | if (expression.getNodeType() == ASTNode.SIMPLE_NAME) { |
421 | SimpleName simpleName = (SimpleName) expression; |
422 | if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) { |
423 | fragment.setInitializer(null); |
424 | fragment.setFlags(fragment.getFlags() | ASTNode.RECOVERED); |
425 | } |
426 | } |
427 | } |
428 | } |
429 | |
430 | @Override |
431 | public void endVisit(NormalAnnotation node) { |
432 | endVisitNode(node); |
433 | // is inside diet part of the ast |
434 | if(this.blockDepth < 1) { |
435 | List values = node.values(); |
436 | int size = values.size(); |
437 | if (size > 0) { |
438 | MemberValuePair lastMemberValuePair = (MemberValuePair)values.get(size - 1); |
439 | |
440 | int annotationEnd = node.getStartPosition() + node.getLength(); |
441 | int lastMemberValuePairEnd = lastMemberValuePair.getStartPosition() + lastMemberValuePair.getLength(); |
442 | if (annotationEnd == lastMemberValuePairEnd) { |
443 | node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
444 | } |
445 | } |
446 | } |
447 | } |
448 | |
449 | @Override |
450 | public void endVisit(SingleMemberAnnotation node) { |
451 | endVisitNode(node); |
452 | // is inside diet part of the ast |
453 | if(this.blockDepth < 1) { |
454 | Expression value = node.getValue(); |
455 | int annotationEnd = node.getStartPosition() + node.getLength(); |
456 | int valueEnd = value.getStartPosition() + value.getLength(); |
457 | if (annotationEnd == valueEnd) { |
458 | node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
459 | } |
460 | } |
461 | } |
462 | } |
463 |
Members