Skip to content

Commit a770f94

Browse files
authored
Fix smithyFormat classpath serialization error (#161)
When smithyFormat fails on invalid syntax, the ModelSyntaxException thrown inside the isolated URLClassLoader cannot be serialized by Gradle's daemon (ShapeId is not visible to the daemon classloader). Strip the cause chain at the worker boundary and rethrow as a plain GradleException with only the root cause message.
1 parent acecfef commit a770f94

6 files changed

Lines changed: 91 additions & 1 deletion

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// This example fails because smithyFormat is run on a model with a syntax error.
2+
// format is intentionally left enabled (the default) to exercise the smithyFormat task path.
3+
// the build must fail with the real syntax error message, not a secondary NoClassDefFoundError
4+
// caused by smithy-model types escaping the isolated classloader.
5+
6+
plugins {
7+
id("java-library")
8+
id("software.amazon.smithy.gradle.smithy-base").version("1.3.0")
9+
}
10+
11+
repositories {
12+
mavenLocal()
13+
mavenCentral()
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace smithy.example
2+
3+
// Intentional syntax error: bare keyword with no identifier following it.
4+
// The smithyFormat task must fail with a clear "Cannot format invalid models" message,
5+
// not a secondary NoClassDefFoundError (regression for SMITHY-3541).
6+
structure
7+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
rootProject.name = "syntax-error-format"
2+
3+
pluginManagement {
4+
repositories {
5+
mavenLocal()
6+
mavenCentral()
7+
}
8+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"version": "1.0"
3+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.gradle;
7+
8+
import org.gradle.testkit.runner.BuildResult;
9+
import org.gradle.testkit.runner.GradleRunner;
10+
import org.gradle.testkit.runner.TaskOutcome;
11+
import org.junit.jupiter.api.Assertions;
12+
import org.junit.jupiter.api.Test;
13+
14+
/**
15+
* Regression test for case when smithyFormat runs on a model with a syntax error,
16+
* the build must fail with the real "Cannot format invalid models" message rather than a
17+
* secondary NoClassDefFoundError caused by smithy-model types (e.g. ShapeId) escaping the
18+
* isolated URLClassLoader and failing Gradle's daemon exception serializer.
19+
*/
20+
public class SyntaxErrorFormatTest {
21+
@Test
22+
public void formatTaskFailsWithReadableMessageOnSyntaxError() {
23+
Utils.withCopy("base-plugin/failure-cases/syntax-error-format", buildDir -> {
24+
BuildResult result = GradleRunner.create()
25+
.forwardOutput()
26+
.withProjectDir(buildDir)
27+
.withArguments("smithyFormat", "--stacktrace")
28+
.buildAndFail();
29+
30+
// The real error from the Smithy formatter must be present.
31+
Assertions.assertTrue(
32+
result.getOutput().contains("Cannot format invalid models"),
33+
"Expected 'Cannot format invalid models' in :smithyFormat output but got:\n" + result.getOutput());
34+
35+
// A NoClassDefFoundError for ShapeId would indicate the classloader isolation bug has regressed.
36+
Assertions.assertFalse(
37+
result.getOutput().contains("NoClassDefFoundError"),
38+
"Unexpected NoClassDefFoundError in output -- classloader isolation regression:\n"
39+
+ result.getOutput());
40+
41+
// The smithyFormat task itself must be the one that failed, not some other task.
42+
Assertions.assertEquals(TaskOutcome.FAILED, result.task(":smithyFormat").getOutcome());
43+
});
44+
}
45+
}

smithy-base/src/main/java/software/amazon/smithy/gradle/SmithyUtils.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,20 @@ public void execute() {
164164
smithyCliClass.getDeclaredMethod("run", List.class)
165165
.invoke(cli, getParameters().getArguments().get());
166166
} catch (ReflectiveOperationException e) {
167-
throw new RuntimeException(e);
167+
// Unwrap to the root cause message. We intentionally do NOT reuse
168+
// unwrapException() here and do NOT attach the cause to GradleException:
169+
// smithy-model types (e.g. ModelSyntaxException, which references ShapeId)
170+
// are loaded only in the isolated URLClassLoader, so Gradle's daemon
171+
// serializer cannot see them. Attaching the cause chain would produce a
172+
// secondary NoClassDefFoundError that obscures the real error.
173+
Throwable cause = e;
174+
while (cause.getCause() != null) {
175+
cause = cause.getCause();
176+
}
177+
String message = cause.getMessage() != null
178+
? cause.getMessage()
179+
: cause.getClass().getName();
180+
throw new GradleException(message);
168181
}
169182
});
170183
});

0 commit comments

Comments
 (0)