• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 4502

13 Mar 2025 12:14PM UTC coverage: 83.089% (+3.4%) from 79.659%
4502

push

github

adangel
[java] Fix crash when parsing class for anonymous class (#5588)

Merge pull request #5588 from oowekyala:fix-anon-class-loading

1912 of 2411 branches covered (79.3%)

Branch coverage included in aggregate %.

29 of 33 new or added lines in 2 files covered. (87.88%)

30 existing lines in 6 files now uncovered.

4559 of 5377 relevant lines covered (84.79%)

14.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.67
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/ClassStub.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
6

7
import java.io.IOException;
8
import java.io.InputStream;
9
import java.util.ArrayList;
10
import java.util.Collections;
11
import java.util.List;
12
import java.util.Objects;
13
import java.util.regex.Pattern;
14

15
import org.checkerframework.checker.nullness.qual.NonNull;
16
import org.checkerframework.checker.nullness.qual.Nullable;
17
import org.objectweb.asm.ClassReader;
18
import org.objectweb.asm.Opcodes;
19
import org.pcollections.HashTreePSet;
20
import org.pcollections.PSet;
21

22
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
23
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
24
import net.sourceforge.pmd.lang.java.symbols.JElementSymbol;
25
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
26
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
27
import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
28
import net.sourceforge.pmd.lang.java.symbols.JRecordComponentSymbol;
29
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
30
import net.sourceforge.pmd.lang.java.symbols.JTypeParameterOwnerSymbol;
31
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue;
32
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
33
import net.sourceforge.pmd.lang.java.symbols.internal.SymbolEquality;
34
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ExecutableStub.CtorStub;
35
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ExecutableStub.MethodStub;
36
import net.sourceforge.pmd.lang.java.symbols.internal.asm.GenericSigBase.LazyClassSignature;
37
import net.sourceforge.pmd.lang.java.types.JClassType;
38
import net.sourceforge.pmd.lang.java.types.JTypeVar;
39
import net.sourceforge.pmd.lang.java.types.LexicalScope;
40
import net.sourceforge.pmd.lang.java.types.Substitution;
41
import net.sourceforge.pmd.lang.java.types.TypeSystem;
42
import net.sourceforge.pmd.util.CollectionUtil;
43

44

45
final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
46

47
    static final int UNKNOWN_ARITY = 0;
48

49
    private final AsmSymbolResolver resolver;
50

51
    private final Names names;
52

53
    // all the following are lazy and depend on the parse lock
54

55
    private int accessFlags;
56

57
    private EnclosingInfo enclosingInfo;
58
    private LazyClassSignature signature;
59
    private LexicalScope scope;
60

61
    private List<JFieldSymbol> fields = new ArrayList<>();
2✔
62
    private List<JClassSymbol> memberClasses = new ArrayList<>();
2✔
63
    private List<JMethodSymbol> methods = new ArrayList<>();
2✔
64
    private List<JConstructorSymbol> ctors = new ArrayList<>();
2✔
65
    private List<JRecordComponentSymbol> recordComponents = null;
2✔
66
    private List<JFieldSymbol> enumConstants = null;
2✔
67
    private List<JClassSymbol> permittedSubclasses = null;
2✔
68

69
    private PSet<SymAnnot> annotations = HashTreePSet.empty();
2✔
70

71
    private PSet<String> annotAttributes;
72

73
    private final ParseLock parseLock;
74

75
    /** Note that '.' is forbidden because in internal names they're replaced by slashes '/'. */
76
    private static final Pattern INTERNAL_NAME_FORBIDDEN_CHARS = Pattern.compile("[;<>\\[.]");
2✔
77

78
    private static boolean isValidInternalName(String internalName) {
79
        return !internalName.isEmpty() && !INTERNAL_NAME_FORBIDDEN_CHARS.matcher(internalName).find();
2!
80
    }
81

82
    ClassStub(AsmSymbolResolver resolver, String internalName, @NonNull Loader loader, int observedArity) {
2✔
83
        assert isValidInternalName(internalName) : internalName;
2!
84

85
        this.resolver = resolver;
2✔
86
        this.names = new Names(internalName);
2✔
87

88
        this.parseLock = new ParseLock("ClassStub:" + internalName) {
2✔
89
            @Override
90
            protected boolean doParse() throws IOException {
91
                try (InputStream instream = loader.getInputStream()) {
2✔
92
                    if (instream != null) {
2✔
93
                        ClassReader classReader = new ClassReader(instream);
2✔
94
                        ClassStubBuilder builder = new ClassStubBuilder(ClassStub.this, resolver);
2✔
95
                        classReader.accept(builder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
2✔
96
                        return true;
2✔
97
                    } else {
98
                        return false;
2✔
99
                    }
100
                } catch (IOException e) {
2!
101
                    // add a bit more info to the exception
102
                    throw new IOException("While loading class from " + loader, e);
×
103
                }
104
            }
105

106
            @Override
107
            protected void finishParse(boolean failed) {
108
                if (enclosingInfo == null) {
2✔
109
                    // this may be normal
110
                    enclosingInfo = EnclosingInfo.NO_ENCLOSING;
2✔
111
                }
112
                if (signature == null) {
2✔
113
                    assert failed : "No signature, but the parse hasn't failed? investigate";
2!
114
                    signature = LazyClassSignature.defaultWhenUnresolved(ClassStub.this, observedArity);
2✔
115
                }
116
                methods = Collections.unmodifiableList(methods);
2✔
117
                ctors = Collections.unmodifiableList(ctors);
2✔
118
                fields = Collections.unmodifiableList(fields);
2✔
119
                memberClasses = Collections.unmodifiableList(memberClasses);
2✔
120
                enumConstants = CollectionUtil.makeUnmodifiableAndNonNull(enumConstants);
2✔
121
                recordComponents = CollectionUtil.makeUnmodifiableAndNonNull(recordComponents);
2✔
122
                if (isEnum()) {
2✔
123
                    permittedSubclasses = Collections.emptyList();
2✔
124
                }
125
                permittedSubclasses = CollectionUtil.makeUnmodifiableAndNonNull(permittedSubclasses);
2✔
126

127
                if (enclosingInfo.getEnclosingClass() == null && names.simpleName == null) {
2✔
128
                    // Top-level classes don't get their simple-name populated during parsing.
129
                    // If the class simple name contains dollars, we can only know after parsing
130
                    // whether they are top-level or not.
131
                    names.finishOuterClass();
2✔
132
                }
133
                assert names.simpleName != null : "At this point the simple name should be known";
2!
134
                if (!enclosingInfo.isLocalOrAnon && names.canonicalName == null) {
2✔
135
                    // note that this may recursively parse the parent classes. This is
136
                    // only necessary in specific cases where class simple names contain dollar signs.
137
                    names.canonicalName = computeCanonicalName();
2✔
138
                }
139

140
                annotAttributes = (accessFlags & Opcodes.ACC_ANNOTATION) != 0
2✔
141
                                  ? getDeclaredMethods().stream().filter(JMethodSymbol::isAnnotationAttribute)
2✔
142
                                                        .map(JElementSymbol::getSimpleName)
2✔
143
                                                        .collect(CollectionUtil.toPersistentSet())
2✔
144
                                  : HashTreePSet.empty();
2✔
145
            }
2✔
146

147
            @Override
148
            protected boolean canReenter() {
149
                // We might call the parsing logic again in the same thread,
150
                // e.g. in order to determine "annotAttributes", getDeclaredMethods() is called, which
151
                // calls ensureParsed().
152
                // Note: Other threads can't reenter, since our thread own the ParseLock monitor.
153
                return true;
2✔
154
            }
155

156
            @Override
157
            protected boolean postCondition() {
158
                return signature != null && enclosingInfo != null;
2!
159
            }
160
        };
161
    }
2✔
162

163
    @Override
164
    public AsmSymbolResolver getResolver() {
165
        return resolver;
2✔
166
    }
167

168
    // <editor-fold  defaultstate="collapsed" desc="Setters used during loading">
169

170
    void setHeader(@Nullable String signature,
171
                   @Nullable String superName,
172
                   String[] interfaces) {
173
        this.signature = new LazyClassSignature(this, signature, superName, interfaces);
2✔
174
    }
2✔
175

176
    /**
177
     * Called if this is an inner class (their simple name cannot be
178
     * derived from splitting the internal/binary name on dollars, as
179
     * the simple name may itself contain dollars).
180
     */
181
    void setSimpleName(String simpleName) {
182
        this.names.simpleName = simpleName;
2✔
183
    }
2✔
184

185
    void setModifiers(int accessFlags, boolean fromClassInfo) {
186
        /*
187
            A different set of modifiers is contained in the ClassInfo
188
            structure and the InnerClasses structure. See
189
            https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html#jvms-4.1-200-E.1
190
            https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html#jvms-4.7.6-300-D.1-D.1
191

192
            Here is the diff (+ lines (resp. - lines) are only available
193
            in InnerClasses (resp. ClassInfo), the rest are available in both)
194

195
            ACC_PUBLIC      0x0001  Declared public; may be accessed from outside its package.
196
         +  ACC_PRIVATE     0x0002  Marked private in source.
197
         +  ACC_PROTECTED   0x0004  Marked protected in source.
198
         +  ACC_STATIC      0x0008  Marked or implicitly static in source.
199
            ACC_FINAL       0x0010  Declared final; no subclasses allowed.
200
         -  ACC_SUPER       0x0020  Treat superclass methods specially when invoked by the invokespecial instruction.
201
            ACC_INTERFACE   0x0200  Is an interface, not a class.
202
            ACC_ABSTRACT    0x0400  Declared abstract; must not be instantiated.
203
            ACC_SYNTHETIC   0x1000  Declared synthetic; not present in the source code.
204
            ACC_ANNOTATION  0x2000  Declared as an annotation type.
205
            ACC_ENUM        0x4000  Declared as an enum type.
206
         -  ACC_MODULE      0x8000  Is a module, not a class or interface.
207

208
            If this stub is a nested class, then we don't have all its
209
            modifiers just with the ClassInfo, the actual source-declared
210
            visibility (if not public) is only in the InnerClasses, as
211
            well as its ACC_STATIC.
212

213
            Also ACC_SUPER conflicts with ACC_SYNCHRONIZED, which
214
            Modifier.toString would reflect.
215

216
            Since the differences are disjoint we can just OR the two
217
            sets of flags.
218
         */
219
        final int visibilityMask = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE;
2✔
220
        int myAccess = this.accessFlags;
2✔
221
        if (fromClassInfo) {
2✔
222
            // we don't care about ACC_SUPER and it conflicts
223
            // with ACC_SYNCHRONIZED
224
            accessFlags = accessFlags & ~Opcodes.ACC_SUPER;
2✔
225
        } else if ((myAccess & Opcodes.ACC_PUBLIC) != 0
2✔
226
            && (accessFlags & visibilityMask) != Opcodes.ACC_PUBLIC) {
227
            // ClassInfo mentions ACC_PUBLIC even if the real
228
            // visibility is protected or private
229
            // We remove the public to avoid a "public protected" or "public private" combination
230
            myAccess = myAccess & ~Opcodes.ACC_PUBLIC;
2✔
231
        }
232
        this.accessFlags = myAccess | accessFlags;
2✔
233

234
        // setModifiers is called multiple times: once from ClassFile structure (fromClassInfo==true)
235
        // and additionally from InnerClasses attribute (fromClassInfo==false)
236
        // The enum constants and record components should only be initialized once
237
        // to avoid losing the constants.
238
        if (fromClassInfo) {
2✔
239
            if ((accessFlags & Opcodes.ACC_ENUM) != 0) {
2✔
240
                this.enumConstants = new ArrayList<>();
2✔
241
            }
242
            if ((accessFlags & Opcodes.ACC_RECORD) != 0) {
2✔
243
                this.recordComponents = new ArrayList<>();
2✔
244
            }
245
        }
246
    }
2✔
247

248
    void setEnclosingInfo(ClassStub outer, boolean localOrAnon, @Nullable String methodName, @Nullable String methodDescriptor) {
249
        if (enclosingInfo == null) {
2✔
250
            if (outer == null) {
2!
251
                assert methodName == null && methodDescriptor == null
×
252
                    : "Enclosing method requires enclosing class";
253
                this.enclosingInfo = EnclosingInfo.NO_ENCLOSING;
×
254
            } else {
255
                this.enclosingInfo = new EnclosingInfo(outer, localOrAnon, methodName, methodDescriptor);
2✔
256
            }
257
        }
258
    }
2✔
259

260
    void addField(FieldStub fieldStub) {
261
        fields.add(fieldStub);
2✔
262

263
        if (fieldStub.isEnumConstant() && enumConstants != null) {
2!
264
            enumConstants.add(fieldStub);
2✔
265
        }
266
    }
2✔
267

268
    void addMemberClass(ClassStub classStub) {
269
        classStub.setEnclosingInfo(this, false, null, null);
2✔
270
        memberClasses.add(classStub);
2✔
271
    }
2✔
272

273
    void addMethod(MethodStub methodStub) {
274
        methods.add(methodStub);
2✔
275
    }
2✔
276

277
    void addCtor(CtorStub methodStub) {
278
        ctors.add(methodStub);
2✔
279
    }
2✔
280

281
    void addRecordComponent(RecordComponentStub recordComponentStub) {
282
        if (recordComponents == null) {
2!
283
            recordComponents = new ArrayList<>();
×
284
        }
285
        recordComponents.add(recordComponentStub);
2✔
286
    }
2✔
287

288
    void addPermittedSubclass(ClassStub permittedSubclass) {
289
        if (this.permittedSubclasses == null) {
2✔
290
            this.permittedSubclasses = new ArrayList<>(2);
2✔
291
        }
292
        this.permittedSubclasses.add(permittedSubclass);
2✔
293
    }
2✔
294

295
    @Override
296
    public void addAnnotation(SymAnnot annot) {
297
        annotations = annotations.plus(annot);
2✔
298
    }
2✔
299

300

301
    // </editor-fold>
302

303

304
    @Override
305
    public @Nullable JClassSymbol getSuperclass() {
306
        parseLock.ensureParsed();
2✔
307
        return signature.getRawSuper();
2✔
308
    }
309

310
    @Override
311
    public List<JClassSymbol> getSuperInterfaces() {
312
        parseLock.ensureParsed();
2✔
313
        return signature.getRawItfs();
2✔
314
    }
315

316
    @Override
317
    public @Nullable JClassType getSuperclassType(Substitution substitution) {
318
        parseLock.ensureParsed();
2✔
319
        return signature.getSuperType(substitution);
2✔
320
    }
321

322
    @Override
323
    public List<JClassType> getSuperInterfaceTypes(Substitution substitution) {
324
        parseLock.ensureParsed();
2✔
325
        return signature.getSuperItfs(substitution);
2✔
326
    }
327

328
    @Override
329
    public List<JTypeVar> getTypeParameters() {
330
        parseLock.ensureParsed();
2✔
331
        return signature.getTypeParams();
2✔
332
    }
333

334
    @Override
335
    public int getTypeParameterCount() {
336
        parseLock.ensureParsed();
2✔
337
        return signature.getTypeParameterCount();
2✔
338
    }
339

340
    @Override
341
    public LexicalScope getLexicalScope() {
342
        if (scope == null) {
2✔
343
            scope = JClassSymbol.super.getLexicalScope();
2✔
344
        }
345
        return scope;
2✔
346
    }
347

348
    @Override
349
    public List<JFieldSymbol> getDeclaredFields() {
350
        parseLock.ensureParsed();
2✔
351
        return fields;
2✔
352
    }
353

354
    @Override
355
    public List<JMethodSymbol> getDeclaredMethods() {
356
        parseLock.ensureParsed();
2✔
357
        return methods;
2✔
358
    }
359

360
    @Override
361
    public List<JConstructorSymbol> getConstructors() {
362
        parseLock.ensureParsed();
2✔
363
        return ctors;
2✔
364
    }
365

366
    @Override
367
    public List<JClassSymbol> getDeclaredClasses() {
368
        parseLock.ensureParsed();
2✔
369
        return memberClasses;
2✔
370
    }
371

372
    @Override
373
    public PSet<SymAnnot> getDeclaredAnnotations() {
374
        parseLock.ensureParsed();
2✔
375
        return annotations;
2✔
376
    }
377

378
    @Override
379
    public PSet<String> getAnnotationAttributeNames() {
380
        parseLock.ensureParsed();
2✔
381
        return annotAttributes;
2✔
382
    }
383

384
    @Override
385
    public @Nullable SymbolicValue getDefaultAnnotationAttributeValue(String attrName) {
386
        parseLock.ensureParsed();
2✔
387
        if (!annotAttributes.contains(attrName)) {
2✔
388
            // this is a shortcut, because the default impl checks each method
389
            return null;
2✔
390
        }
391
        return JClassSymbol.super.getDefaultAnnotationAttributeValue(attrName);
2✔
392
    }
393

394
    @Override
395
    public @Nullable JClassSymbol getEnclosingClass() {
396
        parseLock.ensureParsed();
2✔
397
        return enclosingInfo.getEnclosingClass();
2✔
398
    }
399

400
    @Override
401
    public @Nullable JExecutableSymbol getEnclosingMethod() {
402
        parseLock.ensureParsed();
2✔
403
        return enclosingInfo.getEnclosingMethod();
2✔
404
    }
405

406
    @Override
407
    public @NonNull List<JFieldSymbol> getEnumConstants() {
408
        parseLock.ensureParsed();
2✔
409
        return enumConstants;
2✔
410
    }
411

412

413
    @Override
414
    public @NonNull List<JRecordComponentSymbol> getRecordComponents() {
415
        parseLock.ensureParsed();
2✔
416
        return recordComponents;
2✔
417
    }
418

419

420
    @Override
421
    public List<JClassSymbol> getPermittedSubtypes() {
422
        parseLock.ensureParsed();
2✔
423
        return permittedSubclasses;
2✔
424
    }
425

426
    @Override
427
    public JTypeParameterOwnerSymbol getEnclosingTypeParameterOwner() {
428
        parseLock.ensureParsed();
2✔
429
        return enclosingInfo.getEnclosing();
2✔
430
    }
431

432
    @Override
433
    public String toString() {
434
        // do not use SymbolToString as it triggers the class parsing,
435
        // making tests undebuggable
436
        return getInternalName();
2✔
437
    }
438

439
    @Override
440
    public int hashCode() {
441
        return SymbolEquality.CLASS.hash(this);
2✔
442
    }
443

444
    @Override
445
    public boolean equals(Object obj) {
446
        return SymbolEquality.CLASS.equals(this, obj);
2✔
447
    }
448

449
    // <editor-fold  defaultstate="collapsed" desc="Names">
450

451
    public String getInternalName() {
452
        return getNames().internalName;
2✔
453
    }
454

455
    private Names getNames() {
456
        return names;
2✔
457
    }
458

459
    @Override
460
    public @NonNull String getBinaryName() {
461
        return getNames().binaryName;
2✔
462
    }
463

464
    /**
465
     * Simpler check than computing the canonical name.
466
     */
467
    boolean hasCanonicalName() {
468
        return getCanonicalName() != null;
2!
469
    }
470

471
    @Override
472
    public @Nullable String getCanonicalName() {
473
        @Nullable String canoName = names.canonicalName;
2✔
474
        if (canoName == null) {
2✔
475
            parseLock.ensureParsed();
2✔
476
            canoName = names.canonicalName;
2✔
477
        }
478
        return canoName;
2✔
479
    }
480

481
    private @Nullable String computeCanonicalName() {
482
        parseLock.ensureParsed();
2✔
483
        if (names.canonicalName != null) {
2!
484
            return names.canonicalName;
×
485
        } else if (enclosingInfo.isLocalOrAnon()) {
2!
NEW
486
            return null;
×
487
        }
488
        assert names.simpleName != null && !names.simpleName.isEmpty() : "Anon class should not take this branch";
2!
489

490
        JClassSymbol enclosing = enclosingInfo.getEnclosingClass();
2✔
491
        if (enclosing == null) {
2!
NEW
492
            return names.binaryName;
×
493
        }
494
        String outerName = enclosing.getCanonicalName();
2✔
495
        if (outerName == null) {
2!
496
            return null;
×
497
        }
498
        return outerName + '.' + names.simpleName;
2✔
499
    }
500

501
    @Override
502
    public @NonNull String getPackageName() {
503
        return getNames().packageName;
2✔
504
    }
505

506
    @Override
507
    public @NonNull String getSimpleName() {
508
        String mySimpleName = names.simpleName;
2✔
509
        if (mySimpleName == null) {
2!
510
            parseLock.ensureParsed();
×
NEW
511
            return Objects.requireNonNull(names.simpleName, "Null simple name after parsing " + getInternalName());
×
512
        }
513
        return mySimpleName;
2✔
514
    }
515

516
    @Override
517
    public TypeSystem getTypeSystem() {
518
        return getResolver().getTypeSystem();
2✔
519
    }
520

521
    // </editor-fold>
522

523
    // <editor-fold  defaultstate="collapsed" desc="Modifier info">
524

525

526
    @Override
527
    public boolean isUnresolved() {
528
        return parseLock.isFailed();
2✔
529
    }
530

531
    @Override
532
    public boolean isArray() {
533
        return false;
2✔
534
    }
535

536
    @Override
537
    public boolean isPrimitive() {
538
        return false;
×
539
    }
540

541
    @Override
542
    public @Nullable JTypeDeclSymbol getArrayComponent() {
543
        return null;
×
544
    }
545

546
    @Override
547
    public int getModifiers() {
548
        parseLock.ensureParsed();
2✔
549
        return accessFlags;
2✔
550
    }
551

552
    @Override
553
    public boolean isAbstract() {
554
        return (getModifiers() & Opcodes.ACC_ABSTRACT) != 0;
2✔
555
    }
556

557
    @Override
558
    public boolean isEnum() {
559
        return (getModifiers() & Opcodes.ACC_ENUM) != 0;
2✔
560
    }
561

562
    @Override
563
    public boolean isAnnotation() {
564
        return (getModifiers() & Opcodes.ACC_ANNOTATION) != 0;
2✔
565
    }
566

567
    @Override
568
    public boolean isInterface() {
569
        return (getModifiers() & Opcodes.ACC_INTERFACE) != 0;
2✔
570
    }
571

572
    @Override
573
    public boolean isClass() {
574
        return (getModifiers() & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ANNOTATION)) == 0;
2✔
575
    }
576

577
    @Override
578
    public boolean isRecord() {
579
        JClassSymbol sup = getSuperclass();
2✔
580
        return sup != null && "java.lang.Record".equals(sup.getBinaryName());
2!
581
    }
582

583
    @Override
584
    public boolean isLocalClass() {
585
        parseLock.ensureParsed();
2✔
586
        return enclosingInfo.isLocalOrAnon() && !isAnonymousClass();
2!
587
    }
588

589
    @Override
590
    public boolean isAnonymousClass() {
591
        return getSimpleName().isEmpty();
2✔
592
    }
593

594
    boolean isFailed() {
595
        return this.parseLock.isFailed();
2✔
596
    }
597

598
    boolean isNotParsed() {
599
        return this.parseLock.isNotParsed();
2✔
600
    }
601

602

603
    // </editor-fold>
604

605

606
    static class Names {
2✔
607

608
        final String binaryName;
609
        final String internalName;
610
        final String packageName;
611
        /** If null, the class requires parsing to find out the actual canonical name. */
612
        @Nullable String canonicalName;
613
        /** If null, the class requires parsing to find out the actual simple name. */
614
        @Nullable String simpleName;
615

616
        Names(String internalName) {
2✔
617
            assert isValidInternalName(internalName) : internalName;
2!
618
            int packageEnd = internalName.lastIndexOf('/');
2✔
619

620
            this.internalName = internalName;
2✔
621
            this.binaryName = internalName.replace('/', '.');
2✔
622
            if (packageEnd == -1) {
2✔
623
                this.packageName = "";
2✔
624
            } else {
625
                this.packageName = binaryName.substring(0, packageEnd);
2✔
626
            }
627

628
            if (binaryName.indexOf('$', packageEnd + 1) >= 0) {
2✔
629
                // Contains a dollar in class name (after package)
630
                // Requires parsing to find out the actual simple name,
631
                // this might be an inner class, or simply a class with
632
                // a dollar in its name.
633

634
                // ASSUMPTION: all JVM languages use the $ convention
635
                // to separate inner classes. Java compilers do so but
636
                // not necessarily true of all compilers/languages.
637
                this.canonicalName = null;
2✔
638
                this.simpleName = null;
2✔
639
            } else {
640
                // fast path
641
                this.canonicalName = binaryName;
2✔
642
                this.simpleName = binaryName.substring(packageEnd + 1);
2✔
643
            }
644
        }
2✔
645

646
        public void finishOuterClass() {
647
            int packageEnd = internalName.lastIndexOf('/');
2✔
648
            this.simpleName = binaryName.substring(packageEnd + 1); // if -1, start from 0
2✔
649
            this.canonicalName = binaryName;
2✔
650
        }
2✔
651
    }
652

653
    static class EnclosingInfo {
654

655
        static final EnclosingInfo NO_ENCLOSING = new EnclosingInfo(null, false, null, null);
2✔
656

657
        private final @Nullable JClassSymbol stub;
658
        private final @Nullable String methodName;
659
        private final @Nullable String methodDescriptor;
660
        private final boolean isLocalOrAnon;
661

662
        EnclosingInfo(@Nullable JClassSymbol stub, boolean isLocalOrAnon, @Nullable String methodName, @Nullable String methodDescriptor) {
2✔
663
            this.stub = stub;
2✔
664
            this.isLocalOrAnon = isLocalOrAnon;
2✔
665
            this.methodName = methodName;
2✔
666
            this.methodDescriptor = methodDescriptor;
2✔
667
        }
2✔
668

669
        boolean isLocalOrAnon() {
670
            return isLocalOrAnon;
2✔
671
        }
672

673
        public @Nullable JClassSymbol getEnclosingClass() {
674
            return stub;
2✔
675
        }
676

677
        public @Nullable MethodStub getEnclosingMethod() {
678
            if (stub instanceof ClassStub && methodName != null) {
2✔
679
                ClassStub stub1 = (ClassStub) stub;
2✔
680
                stub1.parseLock.ensureParsed();
2✔
681
                for (JMethodSymbol m : stub1.methods) {
2!
682
                    MethodStub ms = (MethodStub) m;
2✔
683
                    if (ms.matches(methodName, methodDescriptor)) {
2!
684
                        return ms;
2✔
685
                    }
686
                }
×
687
            }
688
            return null;
2✔
689
        }
690

691

692
        JTypeParameterOwnerSymbol getEnclosing() {
693
            if (methodName != null) {
2!
694
                return getEnclosingMethod();
×
695
            } else {
696
                return getEnclosingClass();
2✔
697
            }
698
        }
699

700
        @Override
701
        public boolean equals(Object o) {
UNCOV
702
            if (this == o) {
×
UNCOV
703
                return true;
×
704
            }
UNCOV
705
            if (o == null || getClass() != o.getClass()) {
×
706
                return false;
×
707
            }
UNCOV
708
            EnclosingInfo that = (EnclosingInfo) o;
×
UNCOV
709
            return Objects.equals(stub, that.stub)
×
710
                && isLocalOrAnon == that.isLocalOrAnon
UNCOV
711
                && Objects.equals(methodName, that.methodName)
×
UNCOV
712
                && Objects.equals(methodDescriptor, that.methodDescriptor);
×
713
        }
714

715
        @Override
716
        public int hashCode() {
NEW
717
            return Objects.hash(stub, isLocalOrAnon, methodName, methodDescriptor);
×
718
        }
719
    }
720
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc