aws / aws/aws-sdk-java-v2

TableSchema.fromBean stack overflow if class hierarchy is transitively recursive

Open
#6,110 1 comment 0 reactions 0 assignees View on GitHub
bug dynamodb-enhanced p2
Dominant language
Java
Stars
2.6k
Forks
1k
Avg merge
2d 9h
Merged PRs (30d)
51

Description

### Describe the bug

`TableSchema.fromBean(Class beanClass)` fails with infinite recursion if beanClass has transitive recursion. It works fine if beanClass has self-recursion.

I found a past issue https://github.com/aws/aws-sdk-java-v2/pull/2020 that was about fixing recursion, but it didn't add a test case for transitive recursion. By self-recursion, I mean that `RecursiveRecordBean` references its own type, not another type that references `RecursiveRecordBean` again.

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Expected Behavior

`TableSchema.fromBean(Parent.class);` call to return with a successfully created `TableSchema` instance in the example code in the Reproduction Steps section.

### Current Behavior

`TableSchema.fromBean(Parent.class);` fails with infinite recursion in the example code in the Reproduction Steps section:

```
Exception in thread "main" java.lang.IllegalArgumentException: Failed to generate method handle.
at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.LambdaToMethodBridgeBuilder.build(LambdaToMethodBridgeBuilder.java:99)
at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ObjectConstructor.create(ObjectConstructor.java:41)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.newObjectSupplierForClass(BeanTableSchema.java:448)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$createConverterProvidersFromAnnotation$10(BeanTableSchema.java:309)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$15$1.accept(ReferencePipeline.java:541)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createConverterProvidersFromAnnotation(BeanTableSchema.java:310)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:230)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:177)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$create$0(BeanTableSchema.java:166)
at java.base/java.util.Map.computeIfAbsent(Map.java:1054)
at java.base/java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2760)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:165)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.recursiveCreate(BeanTableSchema.java:201)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.convertTypeToEnhancedType(BeanTableSchema.java:379)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.staticAttributeBuilder(BeanTableSchema.java:320)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$createStaticTableSchema$3(BeanTableSchema.java:248)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:236)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:177)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$create$0(BeanTableSchema.java:166)
at java.base/java.util.Map.computeIfAbsent(Map.java:1054)
at java.base/java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2760)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:165)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.recursiveCreate(BeanTableSchema.java:201)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.convertTypeToEnhancedType(BeanTableSchema.java:379)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.staticAttributeBuilder(BeanTableSchema.java:320)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$createStaticTableSchema$3(BeanTableSchema.java:248)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:236)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:177)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$create$0(BeanTableSchema.java:166)
at java.base/java.util.Map.computeIfAbsent(Map.java:1054)
at java.base/java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2760)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:165)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.recursiveCreate(BeanTableSchema.java:201)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.convertTypeToEnhancedType(BeanTableSchema.java:379)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.staticAttributeBuilder(BeanTableSchema.java:320)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$createStaticTableSchema$3(BeanTableSchema.java:248)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:236)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:177)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$create$0(BeanTableSchema.java:166)
at java.base/java.util.Map.computeIfAbsent(Map.java:1054)
at java.base/java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2760)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:165)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.recursiveCreate(BeanTableSchema.java:201)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.convertTypeToEnhancedType(BeanTableSchema.java:379)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.staticAttributeBuilder(BeanTableSchema.java:320)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$createStaticTableSchema$3(BeanTableSchema.java:248)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:236)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:177)
...
...
...
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$create$0(BeanTableSchema.java:166)
at java.base/java.util.Map.computeIfAbsent(Map.java:1054)
at java.base/java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2760)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:165)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.recursiveCreate(BeanTableSchema.java:201)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.convertTypeToEnhancedType(BeanTableSchema.java:379)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.staticAttributeBuilder(BeanTableSchema.java:320)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$createStaticTableSchema$3(BeanTableSchema.java:248)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:236)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:177)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.lambda$create$0(BeanTableSchema.java:166)
at java.base/java.util.Map.computeIfAbsent(Map.java:1054)
at java.base/java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2760)
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:165)
Disconnected from the target VM, address: '127.0.0.1:60510', transport: 'socket'

Process finished with exit code 1

```

### Reproduction Steps

```
package com.example;

import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;

public class RecursiveBeanTest {

@DynamoDbBean
public static class Parent {

private Child child;

public Child getChild() {
return child;
}

public void setChild(Child child) {
this.child = child;
}
}

@DynamoDbBean
public static class Child {

private Parent parent;

public Parent getParent() {
return parent;
}

public void setParent(Parent parent) {
this.parent = parent;
}
}

@DynamoDbBean
public static class SelfRecursive {

private SelfRecursive selfRecursive;

public SelfRecursive getSelfRecursive() {
return selfRecursive;
}

public void setSelfRecursive(SelfRecursive selfRecursive) {
this.selfRecursive = selfRecursive;
}
}

public static void main(String[] args) {
// this works
TableSchema.fromBean(SelfRecursive.class);
// this throws
TableSchema.fromBean(Parent.class);
}
}

```

### Possible Solution

Could it be that the fix would be as easy as propagating the `metaTableSchema` local variable as a second parameter to the `create` call in `BeanTableSchema.recursiveCreate` instead of letting it create a new one?

https://github.com/aws/aws-sdk-java-v2/blob/7dfea1636ab3a6edc7b3ae77d9e0441ba36af518/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java#L201

### Additional Information/Context

_No response_

### AWS Java SDK version used

2.31.41

### JDK version used

17

### Operating System and version

macOS Sequoia 15.4.1

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.