Skip to content

Commit 4e5dd6d

Browse files
authored
Reject private declarative fallback methods (helidon-io#11602)
1 parent 574fd8c commit 4e5dd6d

2 files changed

Lines changed: 127 additions & 0 deletions

File tree

declarative/codegen/src/main/java/io/helidon/declarative/codegen/faulttolerance/FallbackHandler.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ private void fallbackMethod(ClassModel.Builder classModel,
114114
boolean expectsThrowable = false;
115115
boolean found = false;
116116
TypedElementInfo fallbackMethod = null;
117+
TypedElementInfo privateFallbackMethod = null;
117118
List<BadCandidate> badCandidates = new ArrayList<>();
118119

119120
Predicate<TypedElementInfo> withThrowable = ElementInfoPredicates.hasParams(argsWithThrowable);
@@ -122,12 +123,24 @@ private void fallbackMethod(ClassModel.Builder classModel,
122123
String reason;
123124
if (elementInfo.typeName().resolvedName().equals(returnType.resolvedName())) {
124125
if (withThrowable.test(elementInfo)) {
126+
if (elementInfo.accessModifier() == AccessModifier.PRIVATE) {
127+
privateFallbackMethod = elementInfo;
128+
reason = "Method is private and cannot be called from generated interceptors";
129+
badCandidates.add(new BadCandidate(elementInfo, reason));
130+
continue;
131+
}
125132
expectsThrowable = true;
126133
found = true;
127134
fallbackMethod = elementInfo;
128135
break;
129136
}
130137
if (noThrowable.test(elementInfo)) {
138+
if (elementInfo.accessModifier() == AccessModifier.PRIVATE) {
139+
privateFallbackMethod = elementInfo;
140+
reason = "Method is private and cannot be called from generated interceptors";
141+
badCandidates.add(new BadCandidate(elementInfo, reason));
142+
continue;
143+
}
131144
found = true;
132145
fallbackMethod = elementInfo;
133146
break;
@@ -140,6 +153,12 @@ private void fallbackMethod(ClassModel.Builder classModel,
140153
badCandidates.add(new BadCandidate(elementInfo, reason));
141154
}
142155
if (!found) {
156+
if (privateFallbackMethod != null) {
157+
throw new CodegenException("Fallback method " + privateFallbackMethod.signature().text()
158+
+ " on " + typeInfo.typeName().fqName()
159+
+ " must not be private, generated interceptors cannot call private methods.",
160+
privateFallbackMethod.originatingElementValue());
161+
}
143162
throw new CodegenException("Could not find matching fallback method for "
144163
+ returnType.className() + " "
145164
+ fallbackMethodName
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright (c) 2026 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.declarative.codegen.faulttolerance;
18+
19+
import java.io.IOException;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.util.List;
23+
24+
import io.helidon.builder.api.Prototype;
25+
import io.helidon.codegen.apt.AptProcessor;
26+
import io.helidon.codegen.testing.TestCompiler;
27+
import io.helidon.common.Generated;
28+
import io.helidon.common.types.Annotation;
29+
import io.helidon.faulttolerance.ErrorChecker;
30+
import io.helidon.faulttolerance.Ft;
31+
import io.helidon.faulttolerance.FtSupport;
32+
import io.helidon.service.registry.Dependency;
33+
import io.helidon.service.registry.Service;
34+
import io.helidon.service.registry.ServiceDescriptor;
35+
36+
import org.junit.jupiter.api.Test;
37+
38+
import static org.hamcrest.CoreMatchers.containsString;
39+
import static org.hamcrest.CoreMatchers.is;
40+
import static org.hamcrest.CoreMatchers.not;
41+
import static org.hamcrest.MatcherAssert.assertThat;
42+
43+
class FallbackPrivateMethodCodegenTest {
44+
private static final List<Class<?>> CLASSPATH = List.of(
45+
Annotation.class,
46+
Dependency.class,
47+
ErrorChecker.class,
48+
Ft.class,
49+
FtSupport.class,
50+
Generated.class,
51+
Prototype.class,
52+
Service.class,
53+
ServiceDescriptor.class
54+
);
55+
56+
@Test
57+
void privateFallbackMethodIsRejectedBeforeGeneratedCompileFails() throws IOException {
58+
Path workDirRoot = Path.of("target/test-compiler");
59+
Files.createDirectories(workDirRoot);
60+
Path workDir = Files.createTempDirectory(workDirRoot, "faulttolerance-private-fallback-");
61+
62+
var result = TestCompiler.builder()
63+
.currentRelease()
64+
.addClasspath(CLASSPATH)
65+
.addProcessor(AptProcessor::new)
66+
.workDir(workDir)
67+
.addSource("FallbackService.java", """
68+
package com.example;
69+
70+
import io.helidon.faulttolerance.Ft;
71+
import io.helidon.service.registry.Service;
72+
73+
@Service.Singleton
74+
class FallbackService {
75+
@Ft.Fallback("privateFallback")
76+
String hello(String name) {
77+
throw new IllegalStateException("boom");
78+
}
79+
80+
private String privateFallback(String name) {
81+
return "fallback " + name;
82+
}
83+
}
84+
""")
85+
.addSource("Main.java", """
86+
package com.example;
87+
88+
import io.helidon.service.registry.Service;
89+
90+
@Service.GenerateBinding
91+
class Main {
92+
}
93+
""")
94+
.build()
95+
.compile();
96+
97+
String diagnostics = String.join("\n", result.diagnostics());
98+
assertThat(diagnostics, result.success(), is(false));
99+
assertThat(diagnostics,
100+
containsString("Fallback method privateFallback(java.lang.String) on com.example.FallbackService"
101+
+ " must not be private"));
102+
assertThat(diagnostics, containsString("generated interceptors cannot call private methods"));
103+
assertThat(diagnostics, not(containsString("has private access")));
104+
105+
var generatedFallback = result.sourceOutput().resolve("com/example/FallbackService_hello__Fallback.java");
106+
assertThat(Files.exists(generatedFallback), is(false));
107+
}
108+
}

0 commit comments

Comments
 (0)