Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle exceptions thrown on reflection based calls #28361

Merged
merged 4 commits into from Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .teamcity/subprojects.json
Expand Up @@ -199,7 +199,7 @@
"name": "declarative-dsl-core",
"path": "platforms/core-configuration/declarative-dsl-core",
"unitTests": true,
"functionalTests": false,
"functionalTests": true,
"crossVersionTests": false
},
{
Expand Down
Expand Up @@ -28,4 +28,6 @@ dependencies {

testImplementation(libs.futureKotlin("test-junit5"))
testImplementation("org.jetbrains:annotations:24.0.1")

integTestDistributionRuntimeOnly(project(":distributions-full"))
}
@@ -0,0 +1,111 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.gradle.internal.declarativedsl

import org.gradle.integtests.fixtures.AbstractIntegrationSpec

class ErrorHandlingOnReflectiveCallsSpec extends AbstractIntegrationSpec {

def 'when reflective invocation fails the cause is identified correctly'() {
given:
file("buildSrc/build.gradle") << """
plugins {
id('java-gradle-plugin')
}
gradlePlugin {
plugins {
create("restrictedPlugin") {
id = "com.example.restricted"
implementationClass = "com.example.restricted.RestrictedPlugin"
}
}
}
"""

file("buildSrc/src/main/java/com/example/restricted/Extension.java") << """
package com.example.restricted;
import org.gradle.declarative.dsl.model.annotations.Configuring;
import org.gradle.declarative.dsl.model.annotations.Restricted;
import org.gradle.api.Action;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import javax.inject.Inject;
@Restricted
public abstract class Extension {
private final Access access;
private final ObjectFactory objects;
public Access getAccess() {
return access;
}
@Inject
public Extension(ObjectFactory objects) {
this.objects = objects;
this.access = objects.newInstance(Access.class);
}
@Configuring
public void access(Action<? super Access> configure) {
throw new RuntimeException("Boom");
}
public abstract static class Access {
@Restricted
public abstract Property<String> getName();
}
}
"""

file("buildSrc/src/main/java/com/example/restricted/RestrictedPlugin.java") << """
package com.example.restricted;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class RestrictedPlugin implements Plugin<Project> {
@Override
public void apply(Project target) {
target.getExtensions().create("restricted", Extension.class);
}
}
"""

file("build.gradle.something") << """
plugins {
id("com.example.restricted")
}
restricted {
access {
name = "something"
}
}
"""

when:
fails(":help")

then:
failureCauseContains("Boom")
}

}
Expand Up @@ -117,13 +117,13 @@ class DeclarativeReflectionToObjectConverter(
fun invokeFunctionAndGetResult(
receiverInstance: Any,
origin: ObjectOrigin.FunctionInvocationOrigin
): RestrictedRuntimeFunction.InvocationResult {
): DeclarativeRuntimeFunction.InvocationResult {
val dataFun = origin.function
val receiverKClass = receiverInstance::class
return when (val runtimeFunction = functionResolver.resolve(receiverKClass, dataFun.simpleName, origin.parameterBindings)) {
is RuntimeFunctionResolver.Resolution.Resolved -> {
val bindingWithValues = origin.parameterBindings.bindingMap.mapValues { getObjectByResolvedOrigin(it.value) }
runtimeFunction.function.callBy(receiverInstance, bindingWithValues, origin.parameterBindings.providesConfigureBlock)
runtimeFunction.function.callByWithErrorHandling(receiverInstance, bindingWithValues, origin.parameterBindings.providesConfigureBlock)
}

RuntimeFunctionResolver.Resolution.Unresolved -> error("could not resolve a member function $dataFun call in the owner class $receiverKClass")
Expand All @@ -150,7 +150,7 @@ class DeclarativeReflectionToObjectConverter(

when (val runtimeFunction = functionResolver.resolve(receiverKClass, function.simpleName, parameterBinding)) {
is RuntimeFunctionResolver.Resolution.Resolved ->
runtimeFunction.function.callBy(receiverInstance, parameterBinding.bindingMap.mapValues { getObjectByResolvedOrigin(it.value) }, parameterBinding.providesConfigureBlock).result
runtimeFunction.function.callByWithErrorHandling(receiverInstance, parameterBinding.bindingMap.mapValues { getObjectByResolvedOrigin(it.value) }, parameterBinding.providesConfigureBlock).result
RuntimeFunctionResolver.Resolution.Unresolved -> error("could not resolve a member function $function call in the owner class $receiverKClass")
}
}
Expand Down
Expand Up @@ -18,22 +18,31 @@ package org.gradle.internal.declarativedsl.mappingToJvm

import org.gradle.internal.declarativedsl.analysis.DataParameter
import org.gradle.internal.declarativedsl.schemaBuilder.ConfigureLambdaHandler
import java.lang.reflect.InvocationTargetException
import kotlin.reflect.KFunction


interface RestrictedRuntimeFunction {
interface DeclarativeRuntimeFunction {
jbartok marked this conversation as resolved.
Show resolved Hide resolved
fun callBy(receiver: Any, binding: Map<DataParameter, Any?>, hasLambda: Boolean): InvocationResult

fun callByWithErrorHandling(receiver: Any, binding: Map<DataParameter, Any?>, hasLambda: Boolean): InvocationResult {
try {
return callBy(receiver, binding, hasLambda)
} catch (ite: InvocationTargetException) {
throw ite.cause ?: ite
}
}

data class InvocationResult(val result: Any?, val capturedValue: Any?)
}


internal
class ReflectionFunction(private val kFunction: KFunction<*>, private val configureLambdaHandler: ConfigureLambdaHandler) : RestrictedRuntimeFunction {
override fun callBy(receiver: Any, binding: Map<DataParameter, Any?>, hasLambda: Boolean): RestrictedRuntimeFunction.InvocationResult {
class ReflectionFunction(private val kFunction: KFunction<*>, private val configureLambdaHandler: ConfigureLambdaHandler) : DeclarativeRuntimeFunction {
override fun callBy(receiver: Any, binding: Map<DataParameter, Any?>, hasLambda: Boolean): DeclarativeRuntimeFunction.InvocationResult {
val params = FunctionBinding.convertBinding(kFunction, receiver, binding, hasLambda, configureLambdaHandler)
?: error("signature of $kFunction does not match the arguments: $binding")
val captor = params.valueCaptor
return RestrictedRuntimeFunction.InvocationResult(kFunction.callBy(params.map), captor?.value)
return DeclarativeRuntimeFunction.InvocationResult(kFunction.callBy(params.map), captor?.value)
}
}
Expand Up @@ -17,7 +17,7 @@
package org.gradle.internal.declarativedsl.mappingToJvm


interface RestrictedRuntimeProperty {
interface DeclarativeRuntimeProperty {
fun getValue(receiver: Any): Any?
fun setValue(receiver: Any, value: Any?)
}
Expand Up @@ -26,7 +26,7 @@ interface RuntimeFunctionResolver {
fun resolve(receiverClass: KClass<*>, name: String, parameterValueBinding: ParameterValueBinding): Resolution

sealed interface Resolution {
data class Resolved(val function: RestrictedRuntimeFunction) : Resolution
data class Resolved(val function: DeclarativeRuntimeFunction) : Resolution
data object Unresolved : Resolution
}
}
Expand Down
Expand Up @@ -30,7 +30,7 @@ interface RuntimePropertyResolver {
fun resolvePropertyWrite(receiverClass: KClass<*>, name: String): Resolution

sealed interface Resolution {
data class Resolved(val property: RestrictedRuntimeProperty) : Resolution
data class Resolved(val property: DeclarativeRuntimeProperty) : Resolution
data object Unresolved : Resolution
}
}
Expand All @@ -43,7 +43,7 @@ object ReflectionRuntimePropertyResolver : RuntimePropertyResolver {

return when (callable) {
null -> Unresolved
else -> Resolved(object : RestrictedRuntimeProperty {
else -> Resolved(object : DeclarativeRuntimeProperty {
override fun getValue(receiver: Any): Any? = callable.call(receiver)
override fun setValue(receiver: Any, value: Any?) = throw UnsupportedOperationException()
})
Expand All @@ -56,7 +56,7 @@ object ReflectionRuntimePropertyResolver : RuntimePropertyResolver {

return when (setter) {
null -> Unresolved
else -> Resolved(object : RestrictedRuntimeProperty {
else -> Resolved(object : DeclarativeRuntimeProperty {
override fun getValue(receiver: Any): Any = throw UnsupportedOperationException()
override fun setValue(receiver: Any, value: Any?) {
setter.call(receiver, value)
Expand Down
Expand Up @@ -22,7 +22,7 @@ import org.gradle.internal.declarativedsl.analysis.DataProperty
import org.gradle.internal.declarativedsl.analysis.DataTypeRef
import org.gradle.internal.declarativedsl.analysis.FqName
import org.gradle.internal.declarativedsl.evaluationSchema.EvaluationSchemaComponent
import org.gradle.internal.declarativedsl.mappingToJvm.RestrictedRuntimeProperty
import org.gradle.internal.declarativedsl.mappingToJvm.DeclarativeRuntimeProperty
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver
import org.gradle.internal.declarativedsl.schemaBuilder.CollectedPropertyInformation
import org.gradle.internal.declarativedsl.schemaBuilder.DefaultPropertyExtractor
Expand Down Expand Up @@ -89,7 +89,7 @@ private
class ProjectPropertyAccessorRuntimeResolver : RuntimePropertyResolver {
override fun resolvePropertyRead(receiverClass: KClass<*>, name: String): RuntimePropertyResolver.Resolution {
if (receiverClass.isSubclassOf(Project::class) && name == "projects") {
return RuntimePropertyResolver.Resolution.Resolved(object : RestrictedRuntimeProperty {
return RuntimePropertyResolver.Resolution.Resolved(object : DeclarativeRuntimeProperty {
override fun getValue(receiver: Any) = (receiver as Project).extensions.getByName("projects")
override fun setValue(receiver: Any, value: Any?): Unit = throw UnsupportedOperationException()
})
Expand Down
Expand Up @@ -24,7 +24,7 @@ import org.gradle.internal.declarativedsl.analysis.FunctionSemantics
import org.gradle.internal.declarativedsl.analysis.ParameterSemantics
import org.gradle.internal.declarativedsl.analysis.ParameterValueBinding
import org.gradle.internal.declarativedsl.analysis.SchemaMemberFunction
import org.gradle.internal.declarativedsl.mappingToJvm.RestrictedRuntimeFunction
import org.gradle.internal.declarativedsl.mappingToJvm.DeclarativeRuntimeFunction
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimeFunctionResolver
import org.gradle.internal.declarativedsl.schemaBuilder.DataSchemaBuilder
import org.gradle.internal.declarativedsl.schemaBuilder.FunctionExtractor
Expand Down Expand Up @@ -126,10 +126,10 @@ class RuntimeDependencyFunctionResolver(configurations: DependencyConfigurations

override fun resolve(receiverClass: KClass<*>, name: String, parameterValueBinding: ParameterValueBinding): RuntimeFunctionResolver.Resolution {
if (receiverClass.isSubclassOf(DependencyHandler::class) && name in nameSet && parameterValueBinding.bindingMap.size == 1) {
return RuntimeFunctionResolver.Resolution.Resolved(object : RestrictedRuntimeFunction {
override fun callBy(receiver: Any, binding: Map<DataParameter, Any?>, hasLambda: Boolean): RestrictedRuntimeFunction.InvocationResult {
return RuntimeFunctionResolver.Resolution.Resolved(object : DeclarativeRuntimeFunction {
override fun callBy(receiver: Any, binding: Map<DataParameter, Any?>, hasLambda: Boolean): DeclarativeRuntimeFunction.InvocationResult {
(receiver as DependencyHandler).add(name, binding.values.single() ?: error("null value in dependency DSL"))
return RestrictedRuntimeFunction.InvocationResult(Unit, null)
return DeclarativeRuntimeFunction.InvocationResult(Unit, null)
}
})
}
Expand All @@ -148,12 +148,12 @@ class ImplicitDependencyCollectorFunctionResolver(configurations: DependencyConf
if (name in configurationNames) {
val getterFunction = getDependencyCollectorGetter(receiverClass, name)
if (getterFunction != null) {
return RuntimeFunctionResolver.Resolution.Resolved(object : RestrictedRuntimeFunction {
override fun callBy(receiver: Any, binding: Map<DataParameter, Any?>, hasLambda: Boolean): RestrictedRuntimeFunction.InvocationResult {
return RuntimeFunctionResolver.Resolution.Resolved(object : DeclarativeRuntimeFunction {
override fun callBy(receiver: Any, binding: Map<DataParameter, Any?>, hasLambda: Boolean): DeclarativeRuntimeFunction.InvocationResult {
val dependencyNotation = binding.values.single().toString()
val collector: DependencyCollector = getterFunction.call(receiver) as DependencyCollector
collector.add(dependencyNotation)
return RestrictedRuntimeFunction.InvocationResult(Unit, null)
return DeclarativeRuntimeFunction.InvocationResult(Unit, null)
}
})
}
Expand Down