2020import jadx .core .dex .attributes .AType ;
2121import jadx .core .dex .instructions .ConstStringNode ;
2222import jadx .core .dex .instructions .FilledNewArrayNode ;
23+ import jadx .core .dex .instructions .InvokeNode ;
2324import jadx .core .dex .instructions .args .InsnArg ;
2425import jadx .core .dex .instructions .args .RegisterArg ;
2526import jadx .core .dex .instructions .args .SSAVar ;
@@ -135,7 +136,13 @@ public void visit(MethodNode mth) {
135136 if (fieldConstants .contains (str )) {
136137 continue ;
137138 }
138- csn .addAttr (AType .CODE_COMMENTS , new CodeComment (decoded .commentText (), CommentStyle .LINE ));
139+ // Attach to the top-level statement rather than the ConstStringNode so that
140+ // comments from multiple decoded strings in one expression all survive.
141+ // inheritMetadata uses the untyped addAttr (map.put = replace), so comments
142+ // riding up through inlining chains overwrite each other; attaching directly
143+ // to the statement uses the typed addAttr (list.add = merge) and bypasses that.
144+ InsnNode stmtInsn = findStatementInsn (csn );
145+ stmtInsn .addAttr (AType .CODE_COMMENTS , new CodeComment (decoded .commentText (), CommentStyle .LINE ));
139146 }
140147 }
141148
@@ -149,6 +156,86 @@ public void visit(MethodNode mth) {
149156 }
150157 }
151158
159+ /**
160+ * Walks the SSA def-use chain from {@code csn} upward to find the top-level statement
161+ * instruction (the one that is not itself consumed by another instruction).
162+ * Attaching CODE_COMMENTS there — using the typed, list-merging addAttr — avoids the
163+ * replace-on-copy problem in inheritMetadata when multiple decoded strings live in the
164+ * same chained expression.
165+ */
166+ private static InsnNode findStatementInsn (ConstStringNode csn ) {
167+ RegisterArg result = csn .getResult ();
168+ if (result == null ) {
169+ return csn ;
170+ }
171+ SSAVar var = result .getSVar ();
172+ if (var == null ) {
173+ return csn ;
174+ }
175+ InsnNode current = csn ;
176+ for (int depth = 0 ; depth < 16 ; depth ++) {
177+ List <RegisterArg > uses = var .getUseList ();
178+ if (uses .size () != 1 ) {
179+ break ;
180+ }
181+ InsnNode useInsn = uses .get (0 ).getParentInsn ();
182+ if (useInsn == null ) {
183+ break ;
184+ }
185+ RegisterArg useResult = useInsn .getResult ();
186+ if (useResult == null ) {
187+ // Constructor call (invoke-direct on <init>): the constructed object flows through
188+ // arg[0] rather than a result register — pivot to its SSAVar and continue tracing.
189+ if (useInsn instanceof InvokeNode && ((InvokeNode ) useInsn ).getCallMth ().isConstructor ()) {
190+ InsnArg arg0 = useInsn .getArg (0 );
191+ if (arg0 instanceof RegisterArg ) {
192+ SSAVar ctorVar = ((RegisterArg ) arg0 ).getSVar ();
193+ if (ctorVar != null ) {
194+ InsnNode postCtorUser = singlePostCtorUser (ctorVar , useInsn );
195+ if (postCtorUser != null ) {
196+ RegisterArg postResult = postCtorUser .getResult ();
197+ if (postResult == null ) {
198+ return postCtorUser ;
199+ }
200+ SSAVar postVar = postResult .getSVar ();
201+ if (postVar == null || postVar .getUseList ().isEmpty ()) {
202+ return postCtorUser ;
203+ }
204+ current = postCtorUser ;
205+ var = postVar ;
206+ continue ;
207+ }
208+ }
209+ }
210+ }
211+ return useInsn ;
212+ }
213+ SSAVar useVar = useResult .getSVar ();
214+ if (useVar == null || useVar .getUseList ().isEmpty ()) {
215+ return useInsn ;
216+ }
217+ current = useInsn ;
218+ var = useVar ;
219+ }
220+ return current ;
221+ }
222+
223+ /** Returns the single use of {@code ctorVar} that is not the constructor call itself, or null. */
224+ private static InsnNode singlePostCtorUser (SSAVar ctorVar , InsnNode ctorInsn ) {
225+ InsnNode found = null ;
226+ for (RegisterArg use : ctorVar .getUseList ()) {
227+ InsnNode parent = use .getParentInsn ();
228+ if (parent == null || parent == ctorInsn ) {
229+ continue ;
230+ }
231+ if (found != null ) {
232+ return null ;
233+ }
234+ found = parent ;
235+ }
236+ return found ;
237+ }
238+
152239 /** Returns the FilledNewArrayNode that directly uses the result of {@code csn}, or null. */
153240 private static FilledNewArrayNode findFilledNewArrayParent (ConstStringNode csn ) {
154241 RegisterArg result = csn .getResult ();
0 commit comments