1   /**
2    * Copyright (C) 2006-2019 INRIA and contributors
3    *
4    * Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) of the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon.
5    */
6   package spoon;
7   
8   
9   import spoon.reflect.CtModelImpl;
10  import spoon.reflect.code.CtArrayWrite;
11  import spoon.reflect.code.CtAssignment;
12  import spoon.reflect.code.CtExpression;
13  import spoon.reflect.code.CtFieldWrite;
14  import spoon.reflect.code.CtLambda;
15  import spoon.reflect.code.CtVariableWrite;
16  import spoon.reflect.cu.CompilationUnit;
17  import spoon.reflect.cu.SourcePosition;
18  import spoon.reflect.declaration.CtAnonymousExecutable;
19  import spoon.reflect.declaration.CtConstructor;
20  import spoon.reflect.declaration.CtElement;
21  import spoon.reflect.declaration.CtExecutable;
22  import spoon.reflect.declaration.CtField;
23  import spoon.reflect.declaration.CtMethod;
24  import spoon.reflect.declaration.CtModifiable;
25  import spoon.reflect.declaration.CtPackage;
26  import spoon.reflect.declaration.CtShadowable;
27  import spoon.reflect.declaration.CtType;
28  import spoon.reflect.declaration.CtTypeParameter;
29  import spoon.reflect.declaration.ParentNotInitializedException;
30  import spoon.reflect.path.CtPath;
31  import spoon.reflect.path.CtPathException;
32  import spoon.reflect.path.CtPathStringBuilder;
33  import spoon.reflect.path.CtRole;
34  import spoon.reflect.reference.CtExecutableReference;
35  import spoon.reflect.reference.CtFieldReference;
36  import spoon.reflect.reference.CtReference;
37  import spoon.reflect.reference.CtTypeParameterReference;
38  import spoon.reflect.reference.CtTypeReference;
39  import spoon.reflect.visitor.CtBiScannerDefault;
40  import spoon.reflect.visitor.CtScanner;
41  import spoon.reflect.visitor.JavaIdentifiers;
42  import spoon.reflect.visitor.PrinterHelper;
43  import spoon.reflect.visitor.filter.TypeFilter;
44  import spoon.support.Experimental;
45  import spoon.support.Internal;
46  import spoon.support.reflect.CtExtendedModifier;
47  import spoon.support.sniper.internal.ElementSourceFragment;
48  import spoon.support.visitor.equals.EqualsVisitor;
49  
50  import java.io.PrintWriter;
51  import java.io.StringWriter;
52  import java.util.ArrayDeque;
53  import java.util.Collection;
54  import java.util.Deque;
55  import java.util.HashSet;
56  import java.util.IdentityHashMap;
57  import java.util.Map;
58  import java.util.Set;
59  
60  import static spoon.testing.utils.Check.assertNotNull;
61  
62  /**
63   * Verifies all contracts that should hold on any AST.
64   *
65   * Usage: `new ContractVerifier(pack).verifyAll();`
66   */
67  @Experimental
68  public class ContractVerifier {
69  
70  	private CtPackage _rootPackage;
71  
72  	public ContractVerifier(CtPackage rootPackage) {
73  		this._rootPackage = rootPackage;
74  	}
75  
76  	/** use at your own risk, not part of the public API */
77  	public ContractVerifier() {
78  	}
79  
80  
81  	/** verify all possible contracts in this class */
82  	public void verify() {
83  		checkShadow();
84  		checkParentContract();
85  		checkParentConsistency();
86  		checkModifiers();
87  		checkAssignmentContracts();
88  		checkContractCtScanner();
89  		checkBoundAndUnboundTypeReference();
90  		checkModelIsTree();
91  		checkContractCtScanner();
92  		checkElementIsContainedInAttributeOfItsParent();
93  		checkElementToPathToElementEquivalence();
94  		checkRoleInParent();
95  		checkJavaIdentifiers();
96  	}
97  
98  	/** verifies that the explicit modifier should be present in the original source code */
99  	public void checkModifiers() {
100 		for (CtModifiable modifiable : _rootPackage.getElements(new TypeFilter<>(CtModifiable.class))) {
101 			for (CtExtendedModifier modifier : modifiable.getExtendedModifiers()) {
102 				if (modifier.isImplicit()) {
103 					continue;
104 				}
105 				SourcePosition position = modifier.getPosition();
106 				CompilationUnit compilationUnit = position.getCompilationUnit();
107 				String originalSourceCode = compilationUnit.getOriginalSourceCode();
108 				assertEquals(modifier.getKind().toString(), originalSourceCode.substring(position.getSourceStart(), position.getSourceEnd() + 1));
109 			}
110 		}
111 	}
112 
113 	private static void assertTrue(String msg, boolean conditionThatMustHold) {
114 		if (!conditionThatMustHold) {
115 			throw new AssertionError(msg);
116 		}
117 	}
118 
119 	private void assertFalse(boolean condition) {
120 		assertTrue("", !condition);
121 	}
122 
123 
124 	private void assertEquals(Object expected, Object actual) {
125 		assertEquals("assertEquals violation", expected, actual);
126 	}
127 
128 
129 	private void assertEquals(String msg, Object expected, Object actual) {
130 		if (!expected.equals(actual)) {
131 			throw new AssertionError(msg);
132 		}
133 	}
134 
135 	private void assertNotSame(Object element, Object other) {
136 		if (element == other) {
137 			throw new AssertionError("assertSame violation");
138 		}
139 	}
140 
141 	private void assertSame(Object element, Object other) {
142 		assertSame("assertSame violation", element, other);
143 	}
144 
145 	private void assertSame(String msg, Object element, Object other) {
146 		if (element != other) {
147 			throw new AssertionError(msg);
148 		}
149 	}
150 
151 	private void fail(String msg) {
152 		throw new AssertionError(msg);
153 	}
154 
155 	/** checks that there is always one parent, corresponding to the scanning order */
156 	public void checkParentContract() {
157 		_rootPackage.filterChildren(null).forEach((CtElement elem) -> {
158 			// there is always one parent
159 			assertTrue("no parent for " + elem.getClass() + "-" + elem.getPosition(), elem.isParentInitialized());
160 		});
161 
162 		// the scanner and the parent are in correspondence
163 		new CtScanner() {
164 			Deque<CtElement> elementStack = new ArrayDeque<>();
165 
166 			@Override
167 			public void scan(CtElement e) {
168 				if (e == null) {
169 					return;
170 				}
171 				if (e instanceof CtReference) {
172 					return;
173 				}
174 				if (!elementStack.isEmpty()) {
175 					assertEquals(elementStack.peek(), e.getParent());
176 				}
177 				elementStack.push(e);
178 				e.accept(this);
179 				elementStack.pop();
180 			}
181 		}.scan(_rootPackage);
182 
183 	}
184 
185 
186 	public void checkBoundAndUnboundTypeReference() {
187 		new CtScanner() {
188 			@Override
189 			public void visitCtTypeParameterReference(CtTypeParameterReference ref) {
190 				CtTypeParameter declaration = ref.getDeclaration();
191 				if (declaration != null) {
192 					assertEquals(ref.getSimpleName(), declaration.getSimpleName());
193 				}
194 				super.visitCtTypeParameterReference(ref);
195 			}
196 		}.scan(_rootPackage);
197 	}
198 
199 	/** check that we have all shadow elements, and that they are correctly isShadow */
200 	public void checkShadow() {
201 		new CtScanner() {
202 			@Override
203 			public void scan(CtElement element) {
204 				if (element != null && CtShadowable.class.isAssignableFrom(element.getClass())) {
205 					assertFalse(((CtShadowable) element).isShadow());
206 				}
207 				super.scan(element);
208 			}
209 
210 			@Override
211 			public <T> void visitCtTypeReference(CtTypeReference<T> reference) {
212 				assertNotNull(reference);
213 				if (CtTypeReference.NULL_TYPE_NAME.equals(reference.getSimpleName()) || "?".equals(reference.getSimpleName())) {
214 					super.visitCtTypeReference(reference);
215 					return;
216 				}
217 				final CtType<T> typeDeclaration = reference.getTypeDeclaration();
218 				assertNotNull(reference.toString() + " cannot be found in ", typeDeclaration);
219 				assertEquals(reference.getSimpleName(), typeDeclaration.getSimpleName());
220 				assertEquals(reference.getQualifiedName(), typeDeclaration.getQualifiedName());
221 
222 				if (reference.getDeclaration() == null) {
223 					assertTrue("typeDeclaration must be shadow", typeDeclaration.isShadow());
224 				}
225 				super.visitCtTypeReference(reference);
226 			}
227 
228 			@Override
229 			public <T> void visitCtExecutableReference(CtExecutableReference<T> reference) {
230 				super.visitCtExecutableReference(reference);
231 				assertNotNull(reference);
232 				if (isLanguageExecutable(reference)) {
233 					return;
234 				}
235 				final CtExecutable<T> executableDeclaration = reference.getExecutableDeclaration();
236 				assertNotNull("cannot find decl for " + reference.toString(), executableDeclaration);
237 				assertEquals(reference.getSimpleName(), executableDeclaration.getSimpleName());
238 
239 				// when a generic type is used in a parameter and return type, the shadow type doesn't have these information.
240 				for (int i = 0; i < reference.getParameters().size(); i++) {
241 					//TODO assertions which are checking lambdas. Till then ignore lambdas.
242 					if (executableDeclaration instanceof CtLambda) {
243 						return;
244 					}
245 					CtTypeReference<?> methodParamTypeRef = executableDeclaration.getParameters().get(i).getType();
246 					assertEquals(reference.getParameters().get(i).getQualifiedName(), methodParamTypeRef.getTypeErasure().getQualifiedName());
247 				}
248 
249 				// contract: the reference and method signature are the same
250 				if (reference.getActualTypeArguments().isEmpty()
251 						&& executableDeclaration instanceof CtMethod
252 						&& !((CtMethod) executableDeclaration).getFormalCtTypeParameters().isEmpty()
253 						) {
254 					assertEquals(reference.getSignature(), executableDeclaration.getSignature());
255 				}
256 
257 				// contract: the reference and constructor signature are the same
258 				if (reference.getActualTypeArguments().isEmpty()
259 						&& executableDeclaration instanceof CtConstructor
260 						&& !((CtConstructor) executableDeclaration).getFormalCtTypeParameters().isEmpty()
261 						) {
262 					assertEquals(reference.getSignature(), executableDeclaration.getSignature());
263 				}
264 
265 				if (reference.getDeclaration() == null && CtShadowable.class.isAssignableFrom(executableDeclaration.getClass())) {
266 					assertTrue("execDecl at " + reference.toString() + " must be shadow ", ((CtShadowable) executableDeclaration).isShadow());
267 				}
268 
269 			}
270 
271 			private <T> boolean isLanguageExecutable(CtExecutableReference<T> reference) {
272 				return "values".equals(reference.getSimpleName());
273 			}
274 
275 			@Override
276 			public <T> void visitCtFieldReference(CtFieldReference<T> reference) {
277 				assertNotNull(reference);
278 				if (isLanguageField(reference) || isDeclaredInSuperClass(reference)) {
279 					super.visitCtFieldReference(reference);
280 					return;
281 				}
282 				final CtField<T> fieldDeclaration = reference.getFieldDeclaration();
283 				assertNotNull(fieldDeclaration);
284 				assertEquals(reference.getSimpleName(), fieldDeclaration.getSimpleName());
285 				assertEquals(reference.getType().getQualifiedName(), fieldDeclaration.getType().getQualifiedName());
286 
287 				if (reference.getDeclaration() == null) {
288 					assertTrue("fieldDecl must be shadow", fieldDeclaration.isShadow());
289 				}
290 				super.visitCtFieldReference(reference);
291 			}
292 
293 			private <T> boolean isLanguageField(CtFieldReference<T> reference) {
294 				return "class".equals(reference.getSimpleName()) || "length".equals(reference.getSimpleName());
295 			}
296 
297 			private <T> boolean isDeclaredInSuperClass(CtFieldReference<T> reference) {
298 				final CtType<?> typeDeclaration = reference.getDeclaringType().getTypeDeclaration();
299 				return typeDeclaration != null && typeDeclaration.getField(reference.getSimpleName()) == null;
300 			}
301 		}.visitCtPackage(_rootPackage);
302 	}
303 
304 	/** verifies the core scanning contracts (enter implies exit, etc) */
305 	public void checkContractCtScanner() {
306 		class Counter {
307 			int scan;
308 			int enter;
309 			int exit;
310 		}
311 
312 		final Counter counter = new Counter();
313 		final Counter counterInclNull = new Counter();
314 
315 		new CtScanner() {
316 
317 			@Override
318 			public void scan(CtElement element) {
319 				counterInclNull.scan++;
320 				if (element != null) {
321 					counter.scan++;
322 				}
323 				super.scan(element);
324 			}
325 
326 			@Override
327 			public void enter(CtElement element) {
328 				counter.enter++;
329 				super.enter(element);
330 			}
331 
332 			@Override
333 			public void exit(CtElement element) {
334 				counter.exit++;
335 				super.exit(element);
336 			}
337 
338 		}.scan(_rootPackage);
339 
340 		assertTrue("violated contract: when enter is called, exit is also called", counter.enter == counter.exit);
341 
342 		assertTrue(" violated contract: all scanned elements ust call enter", counter.enter == counter.scan);
343 
344 		Counter counterBiScan = new Counter();
345 		class ActualCounterScanner extends CtBiScannerDefault {
346 			@Override
347 			public void biScan(CtElement element, CtElement other) {
348 				super.biScan(element, other);
349 				counterBiScan.scan++;
350 				if (element == null) {
351 					if (other != null) {
352 						fail("element can't be null if other isn't null.");
353 					}
354 				} else if (other == null) {
355 					fail("other can't be null if element isn't null.");
356 				} else {
357 					// contract: all elements have been cloned and are still equal
358 					EqualsVisitor ev = new EqualsVisitor();
359 					boolean res = ev.checkEquals(element, other);
360 					Object notEqualOther = ev.getNotEqualOther();
361 					String pb = "";
362 					if (notEqualOther != null) {
363 						notEqualOther.toString();
364 					}
365 					if (notEqualOther instanceof CtElement) {
366 						pb += " " + ((CtElement) notEqualOther).getPosition().toString();
367 					}
368 					assertTrue("not equal: " + pb, res);
369 
370 					assertNotSame(element, other);
371 				}
372 			}
373 		}
374 		final ActualCounterScanner actual = new ActualCounterScanner();
375 		actual.biScan(_rootPackage, _rootPackage.clone());
376 
377 		// contract: scan and biscan are executed the same number of times
378 		assertEquals(counterInclNull.scan, counterBiScan.scan);
379 
380 		// for pure beauty: parallel visit of the same tree!
381 		Counter counterBiScan2 = new Counter();
382 		new CtBiScannerDefault() {
383 			@Override
384 			public void biScan(CtElement element, CtElement other) {
385 				counterBiScan2.scan++;
386 				// we have the exact same element
387 				assertSame(element, other);
388 				super.biScan(element, other);
389 			}
390 		}.biScan(_rootPackage, _rootPackage);
391 		// contract: scan and biscan are executed the same number of times
392 		assertEquals(counterInclNull.scan, counterBiScan2.scan);
393 	}
394 
395 	/** checks that all assignments are aither a CtFieldWrite, a CtVariableWrite or a CtArrayWrite */
396 	public void checkAssignmentContracts() {
397 		for (CtAssignment assign : _rootPackage.getElements(new TypeFilter<>(CtAssignment.class))) {
398 			CtExpression assigned = assign.getAssigned();
399 			if (!(assigned instanceof CtFieldWrite
400 					|| assigned instanceof CtVariableWrite || assigned instanceof CtArrayWrite)) {
401 				throw new AssertionError("AssignmentContract error:" + assign.getPosition() + "\n" + assign.toString() + "\nAssigned is " + assigned.getClass());
402 			}
403 		}
404 	}
405 
406 	/** checks that the scanner behavior and the parents correspond */
407 	public void checkParentConsistency() {
408 		checkParentConsistency(_rootPackage);
409 	}
410 
411 	/** public modifier for testing purpose only, not in the public API */
412 	@Internal
413 	public void checkParentConsistency(CtElement element) {
414 		final Set<CtElement> inconsistentParents = new HashSet<>();
415 		new CtScanner() {
416 			private Deque<CtElement> previous = new ArrayDeque();
417 
418 			@Override
419 			protected void enter(CtElement e) {
420 				if (e != null) {
421 					if (!previous.isEmpty()) {
422 						try {
423 							if (e.getParent() != previous.getLast()) {
424 								inconsistentParents.add(e);
425 							}
426 						} catch (ParentNotInitializedException ignore) {
427 							inconsistentParents.add(e);
428 						}
429 					}
430 					previous.add(e);
431 				}
432 				super.enter(e);
433 			}
434 
435 			@Override
436 			protected void exit(CtElement e) {
437 				if (e == null) {
438 					return;
439 				}
440 				if (e.equals(previous.getLast())) {
441 					previous.removeLast();
442 				} else {
443 					throw new RuntimeException("Inconsistent stack");
444 				}
445 				super.exit(e);
446 			}
447 		}.scan(element);
448 		assertEquals("All parents have to be consistent", 0, inconsistentParents.size());
449 	}
450 
451 	/**
452 	 * contract: each element is used only once in the model
453 	 */
454 	public void checkModelIsTree() {
455 		Exception dummyException = new Exception("STACK");
456 		PrinterHelper problems = new PrinterHelper(_rootPackage.getFactory().getEnvironment());
457 		Map<CtElement, Exception> allElements = new IdentityHashMap<>();
458 		_rootPackage.filterChildren(null).forEach((CtElement ele) -> {
459 			//uncomment this line to get stacktrace of real problem. The dummyException is used to avoid OutOfMemoryException
460 //			Exception secondStack = new Exception("STACK");
461 			Exception secondStack = dummyException;
462 			Exception firstStack = allElements.put(ele, secondStack);
463 			if (firstStack != null) {
464 				if (firstStack == dummyException) {
465 					fail("The Spoon model is not a tree. The " + ele.getClass().getSimpleName() + ":" + ele.toString() + " is shared");
466 				}
467 				//the element ele was already visited. It means it used on more places
468 				//report the stacktrace of first and second usage, so that place can be found easily
469 				problems.write("The element " + ele.getClass().getSimpleName()).writeln()
470 						.incTab()
471 						.write(ele.toString()).writeln()
472 						.write("Is linked by these stacktraces").writeln()
473 						.write("1) " + getStackTrace(firstStack)).writeln()
474 						.write("2) " + getStackTrace(secondStack)).writeln()
475 						.decTab();
476 			}
477 		});
478 
479 		String report = problems.toString();
480 		if (!report.isEmpty()) {
481 			fail(report);
482 		}
483 	}
484 
485 	private String getStackTrace(Exception e) {
486 		StringWriter sw = new StringWriter();
487 		e.printStackTrace(new PrintWriter(sw));
488 		return sw.toString();
489 	}
490 
491 	public void checkRoleInParent() {
492 		_rootPackage.accept(new CtScanner() {
493 			@Override
494 			public void scan(CtRole role, CtElement element) {
495 				if (element != null) {
496 					//contract: getMyRoleInParent returns the expected parent
497 					assertSame(role, element.getRoleInParent());
498 				}
499 				super.scan(role, element);
500 			}
501 		});
502 	}
503 
504 	/**
505 	 * Asserts that all siblings and children of sp are well ordered
506 	 * @param sourceFragment
507 	 * @param minOffset TODO
508 	 * @param maxOffset TODO
509 	 * @return number of checked {@link SourcePosition} nodes
510 	 */
511 	private int assertSourcePositionTreeIsCorrectlyOrder(ElementSourceFragment sourceFragment, int minOffset, int maxOffset) {
512 		int nr = 0;
513 		int pos = minOffset;
514 		while (sourceFragment != null) {
515 			nr++;
516 			assertTrue("min(" + pos + ") <= fragment.start(" + sourceFragment.getStart() + ")", pos <= sourceFragment.getStart());
517 			assertTrue("fragment.start(" + sourceFragment.getStart() + ") <= fragment.end(" + sourceFragment.getEnd() + ")", sourceFragment.getStart() <= sourceFragment.getEnd());
518 			pos = sourceFragment.getEnd();
519 			nr += assertSourcePositionTreeIsCorrectlyOrder(sourceFragment.getFirstChild(), sourceFragment.getStart(), sourceFragment.getEnd());
520 			sourceFragment = sourceFragment.getNextSibling();
521 		}
522 		assertTrue("lastFragment.end(" + pos + ") <= max(" + maxOffset + ")", pos <= maxOffset);
523 		return nr;
524 	}
525 
526 	/** checks that for all elements, the path can be obtained, parsed, and give the same element when evaluated */
527 	public void checkElementToPathToElementEquivalence() {
528 		_rootPackage.getPackage("spoon").getElements(e -> true).parallelStream().forEach(element -> {
529 			CtPath path = element.getPath();
530 			String pathStr = path.toString();
531 			try {
532 				CtPath pathRead = new CtPathStringBuilder().fromString(pathStr);
533 				assertEquals(pathStr, pathRead.toString());
534 				Collection<CtElement> returnedElements = pathRead.evaluateOn(_rootPackage);
535 				//contract: CtUniqueRolePathElement.evaluateOn() returns a unique elements if provided only a list of one inputs
536 				assertEquals(1, returnedElements.size());
537 				CtElement actualElement = (CtElement) returnedElements.toArray()[0];
538 				//contract: Element -> Path -> String -> Path -> Element leads to the original element
539 				assertSame(element, actualElement);
540 			} catch (CtPathException e) {
541 				throw new AssertionError("Path " + pathStr + " is either incorrectly generated or incorrectly read", e);
542 			} catch (AssertionError e) {
543 				throw new AssertionError("Path " + pathStr + " detection failed on " + element.getClass().getSimpleName() + ": " + element.toString(), e);
544 			}
545 		});
546 	}
547 
548 	/** contract: element is contained in attribute of element's parent */
549 	public void checkElementIsContainedInAttributeOfItsParent() {
550 		_rootPackage.accept(new CtScanner() {
551 			@Override
552 			public void scan(CtRole role, CtElement element) {
553 				if (element != null) {
554 					//contract: element is contained in attribute of element's parent
555 					CtElement parent = element.getParent();
556 					Object attributeOfParent = parent.getValueByRole(role);
557 					if (attributeOfParent instanceof CtElement) {
558 						assertSame("Element of type " + element.getClass().getName()
559 								+ " is not the value of attribute of role " + role.name()
560 								+ " of parent type " + parent.getClass().getName(), element, attributeOfParent);
561 					} else if (attributeOfParent instanceof Collection) {
562 						assertTrue("Element of type " + element.getClass().getName()
563 										+ " not found in Collection value of attribute of role " + role.name()
564 										+ " of parent type " + parent.getClass().getName(),
565 								((Collection<CtElement>) attributeOfParent).stream().anyMatch(e -> e == element));
566 					} else if (attributeOfParent instanceof Map) {
567 						assertTrue("Element of type " + element.getClass().getName()
568 										+ " not found in Map#values of attribute of role " + role.name()
569 										+ " of parent type " + parent.getClass().getName(),
570 								((Map<String, ?>) attributeOfParent).values().stream().anyMatch(e -> e == element));
571 					} else {
572 						fail("Attribute of Role " + role + " not checked");
573 					}
574 				}
575 				super.scan(role, element);
576 			}
577 		});
578 	}
579 
580 	public void checkGenericContracts() {
581 		checkParentContract();
582 
583 		// assignments
584 		checkAssignmentContracts();
585 
586 		// scanners
587 		checkContractCtScanner();
588 
589 		// type parameter reference.
590 		checkBoundAndUnboundTypeReference();
591 	}
592 
593 	/** checks that the identifiers are valid */
594 	public void checkJavaIdentifiers() {
595 		// checking method JavaIdentifiers.isLegalJavaPackageIdentifier
596 		_rootPackage.getElements(new TypeFilter<>(CtPackage.class)).parallelStream().forEach(element -> {
597 			// the default package is excluded (called "unnamed package")
598 			if (element instanceof CtModelImpl.CtRootPackage) {
599 				return;
600 			}
601 
602 
603 			assertTrue("isLegalJavaPackageIdentifier is broken for " + element.getSimpleName() + " " + element.getPosition(), JavaIdentifiers.isLegalJavaPackageIdentifier(element.getSimpleName()));
604 		});
605 		// checking method JavaIdentifiers.isLegalJavaExecutableIdentifier
606 		_rootPackage.getElements(new TypeFilter<>(CtExecutable.class)).parallelStream().forEach(element -> {
607 
608 			// static methods have an empty string as identifier
609 			if (element instanceof CtAnonymousExecutable) {
610 				return;
611 			}
612 
613 			assertTrue("isLegalJavaExecutableIdentifier is broken " + element.getSimpleName() + " " + element.getPosition(), JavaIdentifiers.isLegalJavaExecutableIdentifier(element.getSimpleName()));
614 		});
615 
616 	}
617 }