[RESOLVED] SOAR M23 Clinit cycle detected - singleton classes

Hello,

I’m getting the following SOAR error when trying to implement a singleton using the static class holder pattern to get the thread safe and lazy loading.

The singleton use a logger which is also a singleton.

[M23] - Clinit cycle detected: Config$Holder->MyLogger->Config$Holder.

public final class Config {
       
      private final Properties props = new Properties();

	private Config() {
            try{
                // load configuration into props from a file
            } catch ( Exception e) {
               MyLoggerProvider.instance.error("error", e);
           }
        }

	private static class Holder {
		public static final Config instance = new Config();
	}

	public static Config getInstance() {  return Holder.instance;	}

	public String getLoggerLevel() {
		return this.props.getProperty("logger.level", "INFO");
	}
}

public final class MyLoggerProvider{
	public static final Logger instance = new SimpleLogger();
	private MyLoggerProvider() {}
}

public final class SimpleLogger implements Logger{
         SimpleLogger(){
             String slevel = Config.getInstance().getLoggerLevel();
             //...
         }
}

The execution order of static initializers (also called “clinit”) is computed by SOAR from the application entry points, as described at SOAR — MicroEJ Documentation.

From you code snippet, I don’t see how MyLogger touches Config$Holder (likely you simplified the code)

To go further, I suggest to have a look to the .clinitmap generated in the soar folder

image

Please share the reason why Config$Holder requires MyLogger and why MyLogger requires Config$Holder.

Thanks.

I have updated the code snippet to show the relation between the two classes.

Actually there is an init cycle between the 2 classes:

  • Config is using LoggerProvider in constructor to log errors if any
  • LoggerProvider instantiate SimpleLogger which uses Config in constructor to get the config level

This behavior is OK on SIM, and only throws an error when the simulator option Use target characteristics is selected.
The issue is always reproductible on device.

Thanks for the .clinitmap file, it helps finding the cycle:

Config$Holder->MyLoggerProvider: static access: instance
	Config.Config()void
	Config.Config(Config)void
	Config$Holder.<clinit>()void
Config$Holder->java.lang.Float: static method call: isInfinite

MyLoggerProvider->Config$Holder: static access: config
	Config.getInstance()Config
	MyLoggerProvider.<clinit>()void
MyLoggerProvider->java.lang.Character: static method call: toLowerCase
	java.lang.String.toLowerCase()java.lang.String
	SimpleLogger.SimpleLogger()void
	MyLoggerProvider.<clinit>()void

On Simulator, static initializers are executed on demand by default, whereas the SOAR static analysis is applied when the Use target characteristics is enabled.

However, the cycle may not be detected on Simulator if MyLoggerProvider.instance is only accessed in the catch block but not executed.

To go further:

  • what is the effect on Simulator if you access MyLoggerProvider.instance at the beginning of the constructor ?
    private Config() {
        MyLoggerProvider.instance.info("test"); // test in normal path
        try{
            // load configuration into props from a file
        } catch ( Exception e) {
            MyLoggerProvider.instance.error("error", e);
        }
    }
    
  • please also share the initialization code of MyLoggerProvider (especially the initialization of static field named config)

Thank you for the details.

  • When moving the MyLoggerProvider.instance.info("test") outside of the catch without selecting the option Use target characteristics I get.
Exception in thread "main" java.lang.ExceptionInInitializerError: java.lang.NullPointerException
	at java.lang.Throwable.fillInStackTrace(Throwable.java:82)
	at java.lang.Throwable.<init>(Throwable.java:32)
	at java.lang.Exception.<init>(Exception.java:14)
	at java.lang.RuntimeException.<init>(RuntimeException.java:14)
	at java.lang.NullPointerException.<init>(NullPointerException.java:6)
  • The class MyLoggerProvider doesn’t have a constructor, it simply create an instance of SimpleLogger inline with the field declaration

The .clinitmap file was very useful, I was able to identify and fix the issue.
Thanks