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

Cannot invoke "org.jboss.jandex.MethodInfo.flags()" because "noArgsConstructor" is null #45462

Open
flynndi opened this issue Jan 9, 2025 · 13 comments
Labels
area/arc Issue related to ARC (dependency injection) kind/bug Something isn't working

Comments

@flynndi
Copy link

flynndi commented Jan 9, 2025

Describe the bug

I used ASM to generate Java bytecode and automatically create CRUD methods for some interfaces. The generated class works perfectly in non-test environments.

However, when I attempted to use @QuarkusTest to test the generated class, I encountered the following error:


[INFO] Results:
[INFO] 
[ERROR] Errors: 
[ERROR]   JSqlClientTestCase.testScalarProvider » Runtime java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.arc.deployment.ArcProcessor#generateResources threw an exception: java.lang.reflect.UndeclaredThrowableException
	at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:862)
	at io.quarkus.builder.BuildContext.run(BuildContext.java:256)
	at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
	at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2516)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2495)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1521)
	at java.base/java.lang.Thread.run(Thread.java:1583)
	at org.jboss.threads.JBossThread.run(JBossThread.java:483)
Caused by: java.util.concurrent.ExecutionException: java.lang.NullPointerException: Cannot invoke "org.jboss.jandex.MethodInfo.flags()" because "noArgsConstructor" is null
	at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
	at io.quarkus.arc.processor.BeanProcessor.generateResources(BeanProcessor.java:429)
	at io.quarkus.arc.deployment.ArcProcessor.generateResources(ArcProcessor.java:537)
	at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:733)
	at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:856)
	... 7 more
Caused by: java.lang.NullPointerException: Cannot invoke "org.jboss.jandex.MethodInfo.flags()" because "noArgsConstructor" is null
	at io.quarkus.arc.processor.BeanGenerator.newInstanceHandle(BeanGenerator.java:1357)
	at io.quarkus.arc.processor.BeanGenerator.implementCreateForClassBean(BeanGenerator.java:1759)
	at io.quarkus.arc.processor.BeanGenerator.implementCreate(BeanGenerator.java:1045)
	at io.quarkus.arc.processor.BeanGenerator.generateClassBean(BeanGenerator.java:392)
	at io.quarkus.arc.processor.BeanGenerator.generate(BeanGenerator.java:136)
	at io.quarkus.arc.processor.BeanProcessor$4.call(BeanProcessor.java:317)
	at io.quarkus.arc.processor.BeanProcessor$4.call(BeanProcessor.java:313)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
	... 6 more

Here is the class I generated. JRepositoryImpl serves as a template and requires a constructor with a JSqlClient parameter. BookRepository is the user’s class.

@Singleton
@Unremovable
public class BookRepositoryAsmImpl extends JRepositoryImpl implements BookRepository {
    public BookRepositoryAsmImpl(JSqlClient var1) {
        super(var1, Book.class);
    }
}

This is a portion of the JRepositoryImpl code:


public class JRepositoryImpl<E, ID> implements JRepository<E, ID> {

    protected final JSqlClientImplementor sqlClient;

    protected final Class<E> entityType;

    protected final ImmutableType immutableType;

    protected JRepositoryImpl(JSqlClient sqlClient) {
        this(sqlClient, null);
    }

    public JRepositoryImpl(JSqlClient sqlClient, Class<E> entityType) {
        this.sqlClient = Utils.validateSqlClient(sqlClient);
        this.entityType = entityType;
        this.immutableType = ImmutableType.get(entityType);
        if (!immutableType.isEntity()) {
            throw new IllegalArgumentException(
                    "\"" +
                            entityType +
                            "\" is not entity type decorated by @" +
                            Entity.class.getName());
        }
    }
        // crud methods
}

This is my test code:

@QuarkusTest
@TestProfile(IntegrationTestsProfile.class)
public class TestResourceTestCase {

    @Inject
    BookRepository bookRepository;

    @Test
    void testRepository() {
        BookRepository bookRepository = Arc.container().instance(BookRepository.class).get();
        Assertions.assertNotNull(bookRepository);
        Assertions.assertEquals(bookRepository, this.bookRepository);
    }
}

When I start Quarkus normally, everything works as expected. The framework automatically recognizes the generated code and discovers it as a bean.

Under normal startup and in the test environment, the code generated by ASM should automatically be recognized as a bean. Why is the framework attempting to call a no-argument constructor on the generated class?

Expected behavior

Under normal startup and in the test environment, the code generated by ASM should automatically be recognized as a bean.

Actual behavior

The test environment was not correctly identified as a bean

How to Reproduce?

branch: report-bug
https://github.com/flynndi/quarkus-jimmer-extension/tree/report-bug

To view the generated bytecode, use the following command:
mvn clean compile quarkus:dev -Dquarkus.debug.generated-classes-dir=xxxxx

It will reappear when you mvn install it
there are no problems when starting the application normally.

Output of uname -a or ver

macbook pro m1

Output of java -version

17

Quarkus version or git rev

3.15

Build tool (ie. output of mvnw --version or gradlew --version)

Apache Maven 3.8.7

Additional information

Here are some related issues I encountered while developing a Quarkus extension. If possible, please help me with these as well.

  1. When using ASM to generate a class, adding the @ApplicationScoped annotation results in an error due to the absence of a default no-argument constructor. To resolve this, I replaced @ApplicationScoped with @singleton. Is this the correct approach?
  2. Currently, I use GeneratedBeanBuildItem to output my bytecode so that it can be correctly recognized by the framework. Without using GeneratedBeanBuildItem, the framework cannot recognize the generated class. How can I add my generated class to Jandex?
@flynndi flynndi added the kind/bug Something isn't working label Jan 9, 2025
@quarkus-bot quarkus-bot bot added the env/m1 Impacts Apple M1 machines label Jan 9, 2025
Copy link

quarkus-bot bot commented Jan 9, 2025

/cc @gastaldi (m1)

@gastaldi gastaldi added area/arc Issue related to ARC (dependency injection) and removed env/m1 Impacts Apple M1 machines labels Jan 9, 2025
@gastaldi
Copy link
Contributor

gastaldi commented Jan 9, 2025

/cc @mkouba @Ladicek

@mkouba
Copy link
Contributor

mkouba commented Jan 9, 2025

  • When using ASM to generate a class, adding the @ApplicationScoped annotation results in an error due to the absence of a default no-argument constructor. To resolve this, I replaced @ApplicationScoped with @singleton. Is this the correct approach?

You can use the @ApplicationScoped but in that case you would have to add a no-args constructor that calls super(null). The no-args constructor is needed for any CDI normal scope so that client proxies can be used.

  • Currently, I use GeneratedBeanBuildItem to output my bytecode so that it can be correctly recognized by the framework. Without using GeneratedBeanBuildItem, the framework cannot recognize the generated class. How can I add my generated class to Jandex?

Use the io.quarkus.arc.deployment.GeneratedBeanBuildItem instead. Ah, sorry, I did not read your question carefully ;-). So what's the problem with GeneratedBeanBuildItem? It's designed exactly for your use case.

@mkouba
Copy link
Contributor

mkouba commented Jan 9, 2025

The test environment was not correctly identified as a bean

Hm, so if I understand it correctly the bean is not registered in the test mode, but works just fine in the prod mode? This is weird. I will take a look at your reproducer.

@flynndi
Copy link
Author

flynndi commented Jan 9, 2025

The test environment was not correctly identified as a bean

Hm, so if I understand it correctly the bean is not registered in the test mode, but works just fine in the prod mode? This is weird. I will take a look at your reproducer.

In my test cases, I use the @QuarkusTest annotation to write the test cases, not in the test environment.

@QuarkusTest
@TestProfile(IntegrationTestsProfile.class)
public class TestResourceTestCase {

    @Inject
    BookRepository bookRepository;

    @Test
    void testRepository() {
        BookRepository bookRepository = Arc.container().instance(BookRepository.class).get();
        Assertions.assertNotNull(bookRepository);
        Assertions.assertEquals(bookRepository, this.bookRepository);
    }
}

When I run this test case, it throws a “noArgsConstructor is null” error.
However, when I start Quarkus normally and inject and use the BookRepository, no error occurs.The framework correctly scans the bytecode I generated.

@flynndi
Copy link
Author

flynndi commented Jan 9, 2025

  • When using ASM to generate a class, adding the @ApplicationScoped annotation results in an error due to the absence of a default no-argument constructor. To resolve this, I replaced @ApplicationScoped with @singleton. Is this the correct approach?

You can use the @ApplicationScoped but in that case you would have to add a no-args constructor that calls super(null). The no-args constructor is needed for any CDI normal scope so that client proxies can be used.

I can’t add a no-args constructor because I need to inject a bean of type sqlClient.
Using the @ApplicationScoped annotation requires a no-args constructor, which contradicts my intention.
So I used @singleton, which doesn’t require a no-args constructor, but now I’m facing the issue I mentioned.

@mkouba
Copy link
Contributor

mkouba commented Jan 9, 2025

  • When using ASM to generate a class, adding the @ApplicationScoped annotation results in an error due to the absence of a default no-argument constructor. To resolve this, I replaced @ApplicationScoped with @singleton. Is this the correct approach?

You can use the @ApplicationScoped but in that case you would have to add a no-args constructor that calls super(null). The no-args constructor is needed for any CDI normal scope so that client proxies can be used.

I can’t add a no-args constructor because I need to inject a bean of type sqlClient. Using the @ApplicationScoped annotation requires a no-args constructor, which contradicts my intention. So I used @singleton, which doesn’t require a no-args constructor, but now I’m facing the issue I mentioned.

You can, but you'll need to add the @Inject to your constructor so that ArC can identify the bean constructor correctly.

@mkouba
Copy link
Contributor

mkouba commented Jan 9, 2025

BTW is there a reason you're using ASM directly? I.e. is there something that gizmo cannot handle?

@flynndi
Copy link
Author

flynndi commented Jan 9, 2025

BTW is there a reason you're using ASM directly? I.e. is there something that gizmo cannot handle?

As you can see, this extension takes care of Kotlin users. I’ve used Gizmo on the master branch, and it isn’t very friendly with Kotlin.

@mkouba
Copy link
Contributor

mkouba commented Jan 9, 2025

BTW is there a reason you're using ASM directly? I.e. is there something that gizmo cannot handle?

As you can see, this extension takes care of Kotlin users. I’ve used Gizmo on the master branch, and it isn’t very friendly with Kotlin.

If you encounter a problem with Kotlin classes feel free to report an issue in https://github.com/quarkusio/gizmo.

@flynndi
Copy link
Author

flynndi commented Jan 9, 2025

  • When using ASM to generate a class, adding the @ApplicationScoped annotation results in an error due to the absence of a default no-argument constructor. To resolve this, I replaced @ApplicationScoped with @singleton. Is this the correct approach?

You can use the @ApplicationScoped but in that case you would have to add a no-args constructor that calls super(null). The no-args constructor is needed for any CDI normal scope so that client proxies can be used.

I can’t add a no-args constructor because I need to inject a bean of type sqlClient. Using the @ApplicationScoped annotation requires a no-args constructor, which contradicts my intention. So I used @singleton, which doesn’t require a no-args constructor, but now I’m facing the issue I mentioned.

You can, but you'll need to add the @Inject to your constructor so that ArC can identify the bean constructor correctly.

@ApplicationScoped
@Unremovable
public class BookRepositoryAsmImpl extends JRepositoryImpl<Book, Long> implements BookRepository {

    @Inject
    JSqlClient sqlClient;

    protected BookRepositoryAsmImpl() {
        super(sqlClient, Book.class);
    }
}

Do you mean generating code like this? But this will lead to another issue.

"Cannot reference 'BookRepositoryAsmImpl. sqlClient' before superclass constructor is called"

@mkouba
Copy link
Contributor

mkouba commented Jan 9, 2025

  • When using ASM to generate a class, adding the @ApplicationScoped annotation results in an error due to the absence of a default no-argument constructor. To resolve this, I replaced @ApplicationScoped with @singleton. Is this the correct approach?

You can use the @ApplicationScoped but in that case you would have to add a no-args constructor that calls super(null). The no-args constructor is needed for any CDI normal scope so that client proxies can be used.

I can’t add a no-args constructor because I need to inject a bean of type sqlClient. Using the @ApplicationScoped annotation requires a no-args constructor, which contradicts my intention. So I used @singleton, which doesn’t require a no-args constructor, but now I’m facing the issue I mentioned.

You can, but you'll need to add the @Inject to your constructor so that ArC can identify the bean constructor correctly.

@ApplicationScoped
@Unremovable
public class BookRepositoryAsmImpl extends JRepositoryImpl<Book, Long> implements BookRepository {

    @Inject
    JSqlClient sqlClient;

    protected BookRepositoryAsmImpl() {
        super(sqlClient, Book.class);
    }
}

Do you mean generating code like this? But this will lead to another issue.

"Cannot reference 'BookRepositoryAsmImpl. sqlClient' before superclass constructor is called"

Nope, I mean something like:

@ApplicationScoped
@Unremovable
public class BookRepositoryAsmImpl extends JRepositoryImpl<Book, Long> implements BookRepository {
 
     // just for the client proxy
     public BookRepositoryAsmImpl() {
        super(null);
     }
    
     @Inject // this is a bean constructor
     public BookRepositoryAsmImpl(JSqlClient sqlClient) {
         super(sqlClient, Book.class);
     }
}

@flynndi
Copy link
Author

flynndi commented Jan 9, 2025

  • When using ASM to generate a class, adding the @ApplicationScoped annotation results in an error due to the absence of a default no-argument constructor. To resolve this, I replaced @ApplicationScoped with @singleton. Is this the correct approach?

You can use the @ApplicationScoped but in that case you would have to add a no-args constructor that calls super(null). The no-args constructor is needed for any CDI normal scope so that client proxies can be used.

I can’t add a no-args constructor because I need to inject a bean of type sqlClient. Using the @ApplicationScoped annotation requires a no-args constructor, which contradicts my intention. So I used @singleton, which doesn’t require a no-args constructor, but now I’m facing the issue I mentioned.

You can, but you'll need to add the @Inject to your constructor so that ArC can identify the bean constructor correctly.

@ApplicationScoped
@Unremovable
public class BookRepositoryAsmImpl extends JRepositoryImpl<Book, Long> implements BookRepository {

    @Inject
    JSqlClient sqlClient;

    protected BookRepositoryAsmImpl() {
        super(sqlClient, Book.class);
    }
}

Do you mean generating code like this? But this will lead to another issue.
"Cannot reference 'BookRepositoryAsmImpl. sqlClient' before superclass constructor is called"

Nope, I mean something like:

@ApplicationScoped
@unremovable
public class BookRepositoryAsmImpl extends JRepositoryImpl<Book, Long> implements BookRepository {

 // just for the client proxy
 public BookRepositoryAsmImpl() {
    super(null);
 }

 @Inject // this is a bean constructor
 public BookRepositoryAsmImpl(JSqlClient sqlClient) {
     super(sqlClient, Book.class);
 }

}

It still can’t inject sqlClient. I’ve updated the code in my report-bug branch; you can take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/arc Issue related to ARC (dependency injection) kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants