1
2
3
4
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
64
65
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
77 public ContractVerifier() {
78 }
79
80
81
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
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
156 public void checkParentContract() {
157 _rootPackage.filterChildren(null).forEach((CtElement elem) -> {
158
159 assertTrue("no parent for " + elem.getClass() + "-" + elem.getPosition(), elem.isParentInitialized());
160 });
161
162
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
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
240 for (int i = 0; i < reference.getParameters().size(); i++) {
241
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
250 if (reference.getActualTypeArguments().isEmpty()
251 && executableDeclaration instanceof CtMethod
252 && !((CtMethod) executableDeclaration).getFormalCtTypeParameters().isEmpty()
253 ) {
254 assertEquals(reference.getSignature(), executableDeclaration.getSignature());
255 }
256
257
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
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
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
378 assertEquals(counterInclNull.scan, counterBiScan.scan);
379
380
381 Counter counterBiScan2 = new Counter();
382 new CtBiScannerDefault() {
383 @Override
384 public void biScan(CtElement element, CtElement other) {
385 counterBiScan2.scan++;
386
387 assertSame(element, other);
388 super.biScan(element, other);
389 }
390 }.biScan(_rootPackage, _rootPackage);
391
392 assertEquals(counterInclNull.scan, counterBiScan2.scan);
393 }
394
395
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
407 public void checkParentConsistency() {
408 checkParentConsistency(_rootPackage);
409 }
410
411
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
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
460
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
468
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
497 assertSame(role, element.getRoleInParent());
498 }
499 super.scan(role, element);
500 }
501 });
502 }
503
504
505
506
507
508
509
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
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
536 assertEquals(1, returnedElements.size());
537 CtElement actualElement = (CtElement) returnedElements.toArray()[0];
538
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
549 public void checkElementIsContainedInAttributeOfItsParent() {
550 _rootPackage.accept(new CtScanner() {
551 @Override
552 public void scan(CtRole role, CtElement element) {
553 if (element != null) {
554
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
584 checkAssignmentContracts();
585
586
587 checkContractCtScanner();
588
589
590 checkBoundAndUnboundTypeReference();
591 }
592
593
594 public void checkJavaIdentifiers() {
595
596 _rootPackage.getElements(new TypeFilter<>(CtPackage.class)).parallelStream().forEach(element -> {
597
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
606 _rootPackage.getElements(new TypeFilter<>(CtExecutable.class)).parallelStream().forEach(element -> {
607
608
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 }