Repository navigation
Description
Summary
ViewDispatches gates a Spring entrypoint's String-returning method into the view-name tier whenever the owning type (or method) is flagged with the spring entrypoint framework. That flag doesn't distinguish @RestController from @Controller, and doesn't check @ResponseBody on @Controller methods either — so a REST endpoint's return value (an HTTP response body) gets analyzed as if it names a view template.
Found while fixing the crash in #271 (a non-identifier target in this same code path threw an NPE); this is the "related semantic issue" called out but intentionally not fixed in that PR, since it changes detection scope rather than just the crash.
Where
SpringEntrypointFinder.isEntrypointClass matches on substring, so both annotations set the same spring framework tag:
if (annotation.getNameAsString().contains("RestController")
|| annotation.getNameAsString().contains("Controller")
|| ...(src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java)
ViewDispatches.collectTypes then uses that same undifferentiated tag to gate the return site:
boolean springType = type.getEntrypointFrameworks().contains("spring");
...
boolean viewNames = springType || callable.getEntrypointFrameworks().contains("spring");(src/main/java/com/ibm/cldk/artifacts/ViewDispatches.java)
Reproducer
Robot Shop's shipping service is a concrete real-world example (Controller.java):
@RestController
public class Controller {
@GetMapping("/health")
public String health() {
return "OK";
}
@GetMapping("/count")
public String count() {
long count = cityrepo.count();
return String.valueOf(count);
}
}Both health() and count() are REST response bodies, not view names, but both currently create a view-name dispatch site:
health()'s"OK"closes as a literal and (if any artifact happens to match) would produce a confident-wrongJ_DISPATCHES_TOedge.count()'sString.valueOf(count)isn't a literal or bare identifier, so it (correctly, after fix(artifacts): a non-identifier view-dispatch target is not a dataflow variable, not a NullPointerException #271) ends upunresolvedwith reasonnon-literal— but it shouldn't have been considered a candidate site at all.
The same gap applies to @Controller methods annotated @ResponseBody:
@Controller
class Controller {
@ResponseBody
@GetMapping("/count")
public String count() {
return String.valueOf(10);
}
}Expected
ViewDispatches should not create a view-name site for:
- any method on a type annotated
@RestController, and - any method (or its declaring type) annotated
@ResponseBody.
Ordinary @Controller view-returning methods (return "home";, dataflow-traced locals, etc.) should keep working exactly as they do today.
Suggested regression tests
@RestController+ literal return (return "OK";) → noview-namesite at all.@RestController+ non-identifier return (return String.valueOf(n);) → noview-namesite (currently ends up correctly unresolved post-fix(artifacts): a non-identifier view-dispatch target is not a dataflow variable, not a NullPointerException #271, but ideally isn't a site in the first place).@Controller+@ResponseBody+ literal return → noview-namesite.@Controller+ literal return, no@ResponseBody→ existing view-name behavior unchanged.@Controller+ dataflow-traced local return (String page = "home"; return page;) → existing dataflow-based view resolution unchanged.
Activity