ADFA-3365: add ability to re-write class name references in desugar plugin#1097
ADFA-3365: add ability to re-write class name references in desugar plugin#1097itsaky-adfa wants to merge 2 commits intostagefrom
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
✅ Files skipped from review due to trivial changes (4)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughRelease Notes: Class Reference Rewriting in Desugar PluginFeatures
Risks & Considerations
WalkthroughAdds class-level bytecode reference replacement: new Changes
Sequence DiagramsequenceDiagram
participant Client
participant DesugarClassVisitor
participant ClassRefReplacingMethodVisitor
participant DesugarMethodVisitor
participant MethodBody
Client->>DesugarClassVisitor: visitMethod(name, descriptor, signature)
activate DesugarClassVisitor
DesugarClassVisitor->>DesugarClassVisitor: rewrite method descriptor & signature
DesugarClassVisitor->>ClassRefReplacingMethodVisitor: create with classReplacements
activate ClassRefReplacingMethodVisitor
DesugarClassVisitor->>DesugarMethodVisitor: create wrapping ClassRefReplacingMethodVisitor
activate DesugarMethodVisitor
MethodBody->>ClassRefReplacingMethodVisitor: emit instructions (NEW, CHECKCAST, LDC, field/method insns, local vars, try/catch)
ClassRefReplacingMethodVisitor->>DesugarMethodVisitor: emit rewritten instructions
DesugarMethodVisitor->>Client: return transformed method visitor
deactivate DesugarMethodVisitor
deactivate ClassRefReplacingMethodVisitor
deactivate DesugarClassVisitor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/ClassRefReplacingMethodVisitor.kt (1)
24-105: Consider overridingvisitInvokeDynamicInsnfor complete coverage.The visitor covers many bytecode sites but
invokedynamicinstructions (used by lambdas and method references) contain bootstrap method handles and descriptors that may also reference the replaced classes. Without handling this, lambda captures or method references involving the replaced class may not be rewritten.Suggested addition
override fun visitInvokeDynamicInsn( name: String, descriptor: String, bootstrapMethodHandle: Handle, vararg bootstrapMethodArguments: Any?, ) { val newHandle = Handle( bootstrapMethodHandle.tag, replace(bootstrapMethodHandle.owner), bootstrapMethodHandle.name, replaceInDescriptor(bootstrapMethodHandle.desc), bootstrapMethodHandle.isInterface, ) val newArgs = bootstrapMethodArguments.map { arg -> when (arg) { is Type -> when (arg.sort) { Type.OBJECT -> Type.getObjectType(replace(arg.internalName)) Type.METHOD -> Type.getMethodType(replaceInDescriptor(arg.descriptor)) else -> arg } is Handle -> Handle( arg.tag, replace(arg.owner), arg.name, replaceInDescriptor(arg.desc), arg.isInterface, ) else -> arg } }.toTypedArray() super.visitInvokeDynamicInsn(name, replaceInDescriptor(descriptor), newHandle, *newArgs) }Note: Requires importing
org.objectweb.asm.Handle.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/ClassRefReplacingMethodVisitor.kt` around lines 24 - 105, Add an override of visitInvokeDynamicInsn in ClassRefReplacingMethodVisitor to rewrite bootstrap method handles and bootstrap arguments that reference replaced classes: create a new Handle from the incoming bootstrapMethodHandle with owner/name/desc rewritten via replace(...) and replaceInDescriptor(...), map bootstrapMethodArguments to rewrite Type (OBJECT and METHOD kinds) and nested Handle entries similarly, and call super.visitInvokeDynamicInsn with the rewritten descriptor (replaceInDescriptor), new Handle, and rewritten args; also import org.objectweb.asm.Handle. Ensure you use the existing replace(...) and replaceInDescriptor(...) helpers and mirror how other visit* methods perform replacements so invokedynamic lambdas and method refs are updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/DesugarClassVisitor.kt`:
- Around line 44-89: The current visitor rewrites descriptors but leaves raw
internal-name slots unmapped — add remapping for superName and interfaces by
overriding visit(classVersion, access, name, signature, superName, interfaces)
and remap the superName and each interfaces entry (e.g., via a new helper
replaceInternalName(name) or by delegating to an ASM Remapper), and also remap
the exceptions array inside visitMethod by mapping each exceptions[i] before
passing to super.visitMethod; keep existing
replaceInDescriptor/replaceInSignature, reuse the same helper across visit,
visitField, and visitMethod, and ensure
ClassRefReplacingMethodVisitor/DesugarMethodVisitor still wrap the returned
MethodVisitor.
- Around line 37-41: The current dot-to-slash conversion in
slashClassReplacements assumes input strings are already binary names and fails
for canonical names with nested classes (e.g., java.util.Map.Entry); update the
mapping code (where params.classReplacements.get().entries are processed into
slashClassReplacements) to first normalize canonical nested-class separators to
binary '$' separators and then convert dots to slashes, or alternatively reject
non-binary input; for example, before doing from.replace('.', '/') and
to.replace('.', '/'), run a small normalization that replaces occurrences of
".<Identifier>" that denote nested types with "$<Identifier>" (apply iteratively
until no change) so nested classes become binary names, and keep or document the
safer overload replaceClass(Class<*>, Class<*>) as preferred.
In
`@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/DesugarReplacementsContainer.kt`:
- Around line 105-118: KDoc for DesugarReplacementsContainer.replaceClass
currently states it throws UnsupportedOperationException but the implementation
uses require(...) which throws IllegalArgumentException; either update the KDoc
to document IllegalArgumentException (or remove the `@throws`) or change the
checks in replaceClass(fromClass: Class<*>, toClass: Class<*>) to explicitly
throw UnsupportedOperationException with the same error message (matching the
ReplaceMethodInsn.Builder.fromClass/toClass pattern) so the documented exception
and actual behavior are consistent.
In
`@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/ReplaceMethodInsnKey.kt`:
- Around line 32-33: Move the instance-level declaration of serialVersionUID
into the class's companion object so it becomes a static field; in
ReplaceMethodInsnKey (the class containing the current `@JvmField` val
serialVersionUID = 1L), remove or relocate that line and add it inside the
companion object (matching the pattern used in ReplaceClassRef) as `@JvmField` val
serialVersionUID = 1L to ensure proper Java serialization behavior.
---
Nitpick comments:
In
`@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/ClassRefReplacingMethodVisitor.kt`:
- Around line 24-105: Add an override of visitInvokeDynamicInsn in
ClassRefReplacingMethodVisitor to rewrite bootstrap method handles and bootstrap
arguments that reference replaced classes: create a new Handle from the incoming
bootstrapMethodHandle with owner/name/desc rewritten via replace(...) and
replaceInDescriptor(...), map bootstrapMethodArguments to rewrite Type (OBJECT
and METHOD kinds) and nested Handle entries similarly, and call
super.visitInvokeDynamicInsn with the rewritten descriptor
(replaceInDescriptor), new Handle, and rewritten args; also import
org.objectweb.asm.Handle. Ensure you use the existing replace(...) and
replaceInDescriptor(...) helpers and mirror how other visit* methods perform
replacements so invokedynamic lambdas and method refs are updated.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 038a1430-53af-4e78-afe6-f3741b178f1b
📒 Files selected for processing (9)
composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/ClassRefReplacingMethodVisitor.ktcomposite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/DesugarClassVisitor.ktcomposite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/DesugarClassVisitorFactory.ktcomposite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/DesugarParams.ktcomposite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/DesugarReplacementsContainer.ktcomposite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/MethodOpcode.ktcomposite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/ReplaceClassRef.ktcomposite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/ReplaceMethodInsn.ktcomposite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/ReplaceMethodInsnKey.kt
| private val slashClassReplacements: Map<String, String> by lazy { | ||
| params.classReplacements.get() | ||
| .entries.associate { (from, to) -> | ||
| from.replace('.', '/') to to.replace('.', '/') | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd 'ReplaceClassRef\.kt|DesugarReplacementsContainer\.kt|DesugarParams\.kt' composite-builds/build-logic/desugaring/src/main/java -t f \
-x sh -c 'printf "\n=== %s ===\n" "$1"; sed -n "1,220p" "$1"' sh {}
rg -n --type=kt -C2 'qualifiedName|canonicalName|replaceClass\(' composite-builds/build-logic/desugaring/src/main/javaRepository: appdevforall/CodeOnTheGo
Length of output: 7696
Use replaceClass(Class<*>, Class<*>) or validate that replaceClass(String, String) receives binary names, not canonical names.
Line 37–41 performs a simple dot-to-slash conversion that assumes from and to already use binary notation with $ for nested classes. If callers pass canonical names like java.util.Map.Entry, the conversion produces java/util/Map/Entry instead of the correct internal name java/util/Map$Entry, causing the class replacement to never match.
The replaceClass(Class<*>, Class<*>) overload is safe because it uses Class.name (binary names), but the replaceClass(String, String) overload has no validation. Either add validation to reject or normalize canonical names, or update the documentation to explicitly require binary notation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/DesugarClassVisitor.kt`
around lines 37 - 41, The current dot-to-slash conversion in
slashClassReplacements assumes input strings are already binary names and fails
for canonical names with nested classes (e.g., java.util.Map.Entry); update the
mapping code (where params.classReplacements.get().entries are processed into
slashClassReplacements) to first normalize canonical nested-class separators to
binary '$' separators and then convert dots to slashes, or alternatively reject
non-binary input; for example, before doing from.replace('.', '/') and
to.replace('.', '/'), run a small normalization that replaces occurrences of
".<Identifier>" that denote nested types with "$<Identifier>" (apply iteratively
until no change) so nested classes become binary names, and keep or document the
safer overload replaceClass(Class<*>, Class<*>) as preferred.
| // ------------------------------------------------------------------------- | ||
| // Class-structure level: rewrite descriptors in field / method declarations | ||
| // ------------------------------------------------------------------------- | ||
|
|
||
| override fun visitField( | ||
| access: Int, | ||
| name: String, | ||
| descriptor: String, | ||
| signature: String?, | ||
| value: Any?, | ||
| ): FieldVisitor? = super.visitField( | ||
| access, | ||
| name, | ||
| replaceInDescriptor(descriptor), | ||
| replaceInSignature(signature), | ||
| value, | ||
| ) | ||
|
|
||
| override fun visitMethod( | ||
| access: Int, | ||
| name: String?, | ||
| descriptor: String?, | ||
| signature: String?, | ||
| exceptions: Array<out String>?, | ||
| ): MethodVisitor { | ||
| // Rewrite the method's own descriptor/signature at the class-structure level. | ||
| val base = super.visitMethod( | ||
| access, | ||
| name, | ||
| descriptor?.let { replaceInDescriptor(it) }, | ||
| replaceInSignature(signature), | ||
| exceptions, | ||
| ) | ||
|
|
||
| // Layer 1 — class-reference replacement inside the method body. | ||
| // Skip instantiation entirely when there are no class replacements. | ||
| val withClassRefs: MethodVisitor = when { | ||
| slashClassReplacements.isNotEmpty() -> | ||
| ClassRefReplacingMethodVisitor(api, base, slashClassReplacements) | ||
| else -> base | ||
| } | ||
|
|
||
| // Layer 2 — fine-grained method-call replacement. | ||
| // Runs first; any instruction it emits flows through withClassRefs. | ||
| return DesugarMethodVisitor(params, classContext, api, withClassRefs) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In ASM's ClassVisitor, which callbacks expose type references as raw internal names rather than descriptors? Specifically, are superName/interfacessupplied tovisit(...), and are declared checked exceptions supplied as a separate array in visitMethod(...)?
💡 Result:
Yes, in ASM's ClassVisitor: - The superName and interfaces parameters in visit(int version, int access, String name, String signature, String superName, String[] interfaces) are supplied as raw internal names (e.g., "java/lang/Object"), explicitly documented as such via Type.getInternalName. - Declared checked exceptions are supplied as a separate String[] exceptions array in visitMethod(int access, String name, String descriptor, String signature, String[] exceptions), where each element is the raw internal name of an exception class. These are type references provided as internal names rather than full type descriptors (which would include primitives like "I" or object references like "Ljava/lang/Object;"). Internal names are specifically for class/interface types (fully qualified name with '/' separators), distinguishing them from general descriptors used for fields (descriptor) and method parameters/returns (descriptor).
Citations:
- 1: https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html
- 2: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/asm/ClassVisitor.html
- 3: https://code.yawk.at/java/10/java.base/jdk/internal/org/objectweb/asm/ClassVisitor.java
- 4: https://javadoc.io/static/org.ow2.asm/asm/7.0/org/objectweb/asm/ClassVisitor.html
- 5: https://stackoverflow.com/questions/35317692/when-a-classvisitors-visitmethod-will-be-called-in-asm
🏁 Script executed:
fd -t f "DesugarClassVisitor.kt" --type fRepository: appdevforall/CodeOnTheGo
Length of output: 177
🏁 Script executed:
rg -A 200 "class DesugarClassVisitor" --type kotlinRepository: appdevforall/CodeOnTheGo
Length of output: 18205
🏁 Script executed:
cat -n composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/DesugarClassVisitor.kt | sed -n '96,120p'Repository: appdevforall/CodeOnTheGo
Length of output: 456
🏁 Script executed:
rg "fun replaceInDescriptor|fun replaceInSignature" composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/DesugarClassVisitor.kt -A 10Repository: appdevforall/CodeOnTheGo
Length of output: 442
Remap superName, interfaces, and exceptions — all internal-name slots.
replaceInDescriptor(...) only covers descriptor format (L...;). The superName and interfaces parameters in a missing visit(...) override, plus the exceptions array in visitMethod(...), are raw internal names and remain unmapped. A class extending, implementing, or declaring a replaced type will carry the old reference after this pass. Extract a shared internal-name remapping helper or use ASM's Remapper to handle both formats uniformly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/DesugarClassVisitor.kt`
around lines 44 - 89, The current visitor rewrites descriptors but leaves raw
internal-name slots unmapped — add remapping for superName and interfaces by
overriding visit(classVersion, access, name, signature, superName, interfaces)
and remap the superName and each interfaces entry (e.g., via a new helper
replaceInternalName(name) or by delegating to an ASM Remapper), and also remap
the exceptions array inside visitMethod by mapping each exceptions[i] before
passing to super.visitMethod; keep existing
replaceInDescriptor/replaceInSignature, reuse the same helper across visit,
visitField, and visitMethod, and ensure
ClassRefReplacingMethodVisitor/DesugarMethodVisitor still wrap the returned
MethodVisitor.
| /** | ||
| * Replaces every bytecode reference to [fromClass] with [toClass]. | ||
| * | ||
| * @throws UnsupportedOperationException for array or primitive types. | ||
| */ | ||
| fun replaceClass(fromClass: Class<*>, toClass: Class<*>) { | ||
| require(!fromClass.isArray && !fromClass.isPrimitive) { | ||
| "Array and primitive types are not supported for class replacement." | ||
| } | ||
| require(!toClass.isArray && !toClass.isPrimitive) { | ||
| "Array and primitive types are not supported for class replacement." | ||
| } | ||
| replaceClass(fromClass.name, toClass.name) | ||
| } |
There was a problem hiding this comment.
KDoc @throws clause doesn't match implementation.
The KDoc states @throws UnsupportedOperationException but require throws IllegalArgumentException. Either update the KDoc or use throw UnsupportedOperationException(...) if that's the intended exception type.
Option 1: Fix KDoc
/**
* Replaces every bytecode reference to [fromClass] with [toClass].
*
- * `@throws` UnsupportedOperationException for array or primitive types.
+ * `@throws` IllegalArgumentException for array or primitive types.
*/Option 2: Match existing ReplaceMethodInsn pattern
If UnsupportedOperationException is preferred (consistent with ReplaceMethodInsn.Builder.fromClass/toClass), change to explicit throws:
fun replaceClass(fromClass: Class<*>, toClass: Class<*>) {
- require(!fromClass.isArray && !fromClass.isPrimitive) {
- "Array and primitive types are not supported for class replacement."
- }
- require(!toClass.isArray && !toClass.isPrimitive) {
- "Array and primitive types are not supported for class replacement."
- }
+ if (fromClass.isArray || fromClass.isPrimitive) {
+ throw UnsupportedOperationException(
+ "Array and primitive types are not supported for class replacement."
+ )
+ }
+ if (toClass.isArray || toClass.isPrimitive) {
+ throw UnsupportedOperationException(
+ "Array and primitive types are not supported for class replacement."
+ )
+ }
replaceClass(fromClass.name, toClass.name)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * Replaces every bytecode reference to [fromClass] with [toClass]. | |
| * | |
| * @throws UnsupportedOperationException for array or primitive types. | |
| */ | |
| fun replaceClass(fromClass: Class<*>, toClass: Class<*>) { | |
| require(!fromClass.isArray && !fromClass.isPrimitive) { | |
| "Array and primitive types are not supported for class replacement." | |
| } | |
| require(!toClass.isArray && !toClass.isPrimitive) { | |
| "Array and primitive types are not supported for class replacement." | |
| } | |
| replaceClass(fromClass.name, toClass.name) | |
| } | |
| /** | |
| * Replaces every bytecode reference to [fromClass] with [toClass]. | |
| * | |
| * `@throws` IllegalArgumentException for array or primitive types. | |
| */ | |
| fun replaceClass(fromClass: Class<*>, toClass: Class<*>) { | |
| require(!fromClass.isArray && !fromClass.isPrimitive) { | |
| "Array and primitive types are not supported for class replacement." | |
| } | |
| require(!toClass.isArray && !toClass.isPrimitive) { | |
| "Array and primitive types are not supported for class replacement." | |
| } | |
| replaceClass(fromClass.name, toClass.name) | |
| } |
| /** | |
| * Replaces every bytecode reference to [fromClass] with [toClass]. | |
| * | |
| * @throws UnsupportedOperationException for array or primitive types. | |
| */ | |
| fun replaceClass(fromClass: Class<*>, toClass: Class<*>) { | |
| require(!fromClass.isArray && !fromClass.isPrimitive) { | |
| "Array and primitive types are not supported for class replacement." | |
| } | |
| require(!toClass.isArray && !toClass.isPrimitive) { | |
| "Array and primitive types are not supported for class replacement." | |
| } | |
| replaceClass(fromClass.name, toClass.name) | |
| } | |
| /** | |
| * Replaces every bytecode reference to [fromClass] with [toClass]. | |
| * | |
| * `@throws` UnsupportedOperationException for array or primitive types. | |
| */ | |
| fun replaceClass(fromClass: Class<*>, toClass: Class<*>) { | |
| if (fromClass.isArray || fromClass.isPrimitive) { | |
| throw UnsupportedOperationException( | |
| "Array and primitive types are not supported for class replacement." | |
| ) | |
| } | |
| if (toClass.isArray || toClass.isPrimitive) { | |
| throw UnsupportedOperationException( | |
| "Array and primitive types are not supported for class replacement." | |
| ) | |
| } | |
| replaceClass(fromClass.name, toClass.name) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/DesugarReplacementsContainer.kt`
around lines 105 - 118, KDoc for DesugarReplacementsContainer.replaceClass
currently states it throws UnsupportedOperationException but the implementation
uses require(...) which throws IllegalArgumentException; either update the KDoc
to document IllegalArgumentException (or remove the `@throws`) or change the
checks in replaceClass(fromClass: Class<*>, toClass: Class<*>) to explicitly
throw UnsupportedOperationException with the same error message (matching the
ReplaceMethodInsn.Builder.fromClass/toClass pattern) so the documented exception
and actual behavior are consistent.
| @JvmField | ||
| val serialVersionUID = 1L |
There was a problem hiding this comment.
serialVersionUID should be in a companion object.
The serialVersionUID is declared as an instance field, but Java serialization requires it to be a static field to be recognized. Compare with ReplaceClassRef.kt which correctly places it in a companion object.
Proposed fix
) : Serializable {
- `@JvmField`
- val serialVersionUID = 1L
+ companion object {
+ private const val serialVersionUID = 1L
+ }
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@composite-builds/build-logic/desugaring/src/main/java/com/itsaky/androidide/desugaring/dsl/ReplaceMethodInsnKey.kt`
around lines 32 - 33, Move the instance-level declaration of serialVersionUID
into the class's companion object so it becomes a static field; in
ReplaceMethodInsnKey (the class containing the current `@JvmField` val
serialVersionUID = 1L), remove or relocate that line and add it inside the
companion object (matching the pattern used in ReplaceClassRef) as `@JvmField` val
serialVersionUID = 1L to ensure proper Java serialization behavior.
1b069cb to
6a5b20a
Compare
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
6a5b20a to
4af1c30
Compare
| @@ -0,0 +1,31 @@ | |||
| package com.itsaky.androidide.desugaring.dsl | |||
There was a problem hiding this comment.
Isn't this file dead code?
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
See ADFA-3365 for more details.