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

pmd / pmd / 4351

20 Jan 2025 11:27AM UTC coverage: 77.675% (-0.001%) from 77.676%
4351

push

github

web-flow
Bump liquid from 5.6.0 to 5.7.0 in the all-gems group across 1 directory (#5475)

17307 of 23228 branches covered (74.51%)

Branch coverage included in aggregate %.

38089 of 48090 relevant lines covered (79.2%)

0.8 hits per line

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

82.51
/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<>();
1✔
62
    private List<JClassSymbol> memberClasses = new ArrayList<>();
1✔
63
    private List<JMethodSymbol> methods = new ArrayList<>();
1✔
64
    private List<JConstructorSymbol> ctors = new ArrayList<>();
1✔
65
    private List<JRecordComponentSymbol> recordComponents = null;
1✔
66
    private List<JFieldSymbol> enumConstants = null;
1✔
67
    private List<JClassSymbol> permittedSubclasses = null;
1✔
68

69
    private PSet<SymAnnot> annotations = HashTreePSet.empty();
1✔
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("[;<>\\[.]");
1✔
77

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

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

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

88
        this.parseLock = new ParseLock("ClassStub:" + internalName) {
1✔
89
            @Override
90
            protected boolean doParse() throws IOException {
91
                try (InputStream instream = loader.getInputStream()) {
1✔
92
                    if (instream != null) {
1✔
93
                        ClassReader classReader = new ClassReader(instream);
1✔
94
                        ClassStubBuilder builder = new ClassStubBuilder(ClassStub.this, resolver);
1✔
95
                        classReader.accept(builder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
1✔
96
                        return true;
1✔
97
                    } else {
98
                        return false;
1✔
99
                    }
100
                } catch (IOException e) {
1!
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) {
1✔
109
                    // this may be normal
110
                    enclosingInfo = EnclosingInfo.NO_ENCLOSING;
1✔
111
                }
112
                if (signature == null) {
1✔
113
                    assert failed : "No signature, but the parse hasn't failed? investigate";
1!
114
                    signature = LazyClassSignature.defaultWhenUnresolved(ClassStub.this, observedArity);
1✔
115
                }
116
                methods = Collections.unmodifiableList(methods);
1✔
117
                ctors = Collections.unmodifiableList(ctors);
1✔
118
                fields = Collections.unmodifiableList(fields);
1✔
119
                memberClasses = Collections.unmodifiableList(memberClasses);
1✔
120
                enumConstants = CollectionUtil.makeUnmodifiableAndNonNull(enumConstants);
1✔
121
                recordComponents = CollectionUtil.makeUnmodifiableAndNonNull(recordComponents);
1✔
122
                if (isEnum()) {
1✔
123
                    permittedSubclasses = Collections.emptyList();
1✔
124
                }
125
                permittedSubclasses = CollectionUtil.makeUnmodifiableAndNonNull(permittedSubclasses);
1✔
126

127
                if (EnclosingInfo.NO_ENCLOSING.equals(enclosingInfo)) {
1✔
128
                    if (names.canonicalName == null || names.simpleName == null) {
1!
129
                        // This happens if the simple name contains dollars,
130
                        // in which case we might have an enclosing class, and
131
                        // we can only tell now (no enclosingInfo) that that's
132
                        // not the case.
133
                        names.finishOuterClass();
1✔
134
                    }
135
                }
136
                annotAttributes = (accessFlags & Opcodes.ACC_ANNOTATION) != 0
1✔
137
                                  ? getDeclaredMethods().stream().filter(JMethodSymbol::isAnnotationAttribute)
1✔
138
                                                        .map(JElementSymbol::getSimpleName)
1✔
139
                                                        .collect(CollectionUtil.toPersistentSet())
1✔
140
                                  : HashTreePSet.empty();
1✔
141
            }
1✔
142

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

152
            @Override
153
            protected boolean postCondition() {
154
                return signature != null && enclosingInfo != null;
1!
155
            }
156
        };
157
    }
1✔
158

159
    @Override
160
    public AsmSymbolResolver getResolver() {
161
        return resolver;
1✔
162
    }
163

164
    // <editor-fold  defaultstate="collapsed" desc="Setters used during loading">
165

166
    void setHeader(@Nullable String signature,
167
                   @Nullable String superName,
168
                   String[] interfaces) {
169
        this.signature = new LazyClassSignature(this, signature, superName, interfaces);
1✔
170
    }
1✔
171

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

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

188
            Here is the diff (+ lines (resp. - lines) are only available
189
            in InnerClasses (resp. ClassInfo), the rest are available in both)
190

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

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

209
            Also ACC_SUPER conflicts with ACC_SYNCHRONIZED, which
210
            Modifier.toString would reflect.
211

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

230
        if ((accessFlags & Opcodes.ACC_ENUM) != 0) {
1✔
231
            this.enumConstants = new ArrayList<>();
1✔
232
        }
233
        if ((accessFlags & Opcodes.ACC_RECORD) != 0) {
1✔
234
            this.recordComponents = new ArrayList<>();
1✔
235
        }
236
    }
1✔
237

238
    void setOuterClass(ClassStub outer, @Nullable String methodName, @Nullable String methodDescriptor) {
239
        if (enclosingInfo == null) {
1✔
240
            if (outer == null) {
1!
241
                assert methodName == null && methodDescriptor == null
×
242
                    : "Enclosing method requires enclosing class";
243
                this.enclosingInfo = EnclosingInfo.NO_ENCLOSING;
×
244
            } else {
245
                this.enclosingInfo = new EnclosingInfo(outer, methodName, methodDescriptor);
1✔
246
            }
247
        }
248
    }
1✔
249

250
    void addField(FieldStub fieldStub) {
251
        fields.add(fieldStub);
1✔
252

253
        if (fieldStub.isEnumConstant() && enumConstants != null) {
1!
254
            enumConstants.add(fieldStub);
1✔
255
        }
256
    }
1✔
257

258
    void addMemberClass(ClassStub classStub) {
259
        classStub.setOuterClass(this, null, null);
1✔
260
        memberClasses.add(classStub);
1✔
261
    }
1✔
262

263
    void addMethod(MethodStub methodStub) {
264
        methods.add(methodStub);
1✔
265
    }
1✔
266

267
    void addCtor(CtorStub methodStub) {
268
        ctors.add(methodStub);
1✔
269
    }
1✔
270

271
    void addRecordComponent(RecordComponentStub recordComponentStub) {
272
        if (recordComponents == null) {
1!
273
            recordComponents = new ArrayList<>();
×
274
        }
275
        recordComponents.add(recordComponentStub);
1✔
276
    }
1✔
277

278
    void addPermittedSubclass(ClassStub permittedSubclass) {
279
        if (this.permittedSubclasses == null) {
1✔
280
            this.permittedSubclasses = new ArrayList<>(2);
1✔
281
        }
282
        this.permittedSubclasses.add(permittedSubclass);
1✔
283
    }
1✔
284

285
    @Override
286
    public void addAnnotation(SymAnnot annot) {
287
        annotations = annotations.plus(annot);
1✔
288
    }
1✔
289

290

291
    // </editor-fold>
292

293

294
    @Override
295
    public @Nullable JClassSymbol getSuperclass() {
296
        parseLock.ensureParsed();
1✔
297
        return signature.getRawSuper();
1✔
298
    }
299

300
    @Override
301
    public List<JClassSymbol> getSuperInterfaces() {
302
        parseLock.ensureParsed();
1✔
303
        return signature.getRawItfs();
1✔
304
    }
305

306
    @Override
307
    public @Nullable JClassType getSuperclassType(Substitution substitution) {
308
        parseLock.ensureParsed();
1✔
309
        return signature.getSuperType(substitution);
1✔
310
    }
311

312
    @Override
313
    public List<JClassType> getSuperInterfaceTypes(Substitution substitution) {
314
        parseLock.ensureParsed();
1✔
315
        return signature.getSuperItfs(substitution);
1✔
316
    }
317

318
    @Override
319
    public List<JTypeVar> getTypeParameters() {
320
        parseLock.ensureParsed();
1✔
321
        return signature.getTypeParams();
1✔
322
    }
323

324
    @Override
325
    public int getTypeParameterCount() {
326
        parseLock.ensureParsed();
1✔
327
        return signature.getTypeParameterCount();
1✔
328
    }
329

330
    @Override
331
    public LexicalScope getLexicalScope() {
332
        if (scope == null) {
1✔
333
            scope = JClassSymbol.super.getLexicalScope();
1✔
334
        }
335
        return scope;
1✔
336
    }
337

338
    @Override
339
    public List<JFieldSymbol> getDeclaredFields() {
340
        parseLock.ensureParsed();
1✔
341
        return fields;
1✔
342
    }
343

344
    @Override
345
    public List<JMethodSymbol> getDeclaredMethods() {
346
        parseLock.ensureParsed();
1✔
347
        return methods;
1✔
348
    }
349

350
    @Override
351
    public List<JConstructorSymbol> getConstructors() {
352
        parseLock.ensureParsed();
1✔
353
        return ctors;
1✔
354
    }
355

356
    @Override
357
    public List<JClassSymbol> getDeclaredClasses() {
358
        parseLock.ensureParsed();
1✔
359
        return memberClasses;
1✔
360
    }
361

362
    @Override
363
    public PSet<SymAnnot> getDeclaredAnnotations() {
364
        parseLock.ensureParsed();
1✔
365
        return annotations;
1✔
366
    }
367

368
    @Override
369
    public PSet<String> getAnnotationAttributeNames() {
370
        parseLock.ensureParsed();
1✔
371
        return annotAttributes;
1✔
372
    }
373

374
    @Override
375
    public @Nullable SymbolicValue getDefaultAnnotationAttributeValue(String attrName) {
376
        parseLock.ensureParsed();
1✔
377
        if (!annotAttributes.contains(attrName)) {
1✔
378
            // this is a shortcut, because the default impl checks each method
379
            return null;
1✔
380
        }
381
        return JClassSymbol.super.getDefaultAnnotationAttributeValue(attrName);
1✔
382
    }
383

384
    @Override
385
    public @Nullable JClassSymbol getEnclosingClass() {
386
        parseLock.ensureParsed();
1✔
387
        return enclosingInfo.getEnclosingClass();
1✔
388
    }
389

390
    @Override
391
    public @Nullable JExecutableSymbol getEnclosingMethod() {
392
        parseLock.ensureParsed();
1✔
393
        return enclosingInfo.getEnclosingMethod();
1✔
394
    }
395

396
    @Override
397
    public @NonNull List<JFieldSymbol> getEnumConstants() {
398
        parseLock.ensureParsed();
1✔
399
        return enumConstants;
1✔
400
    }
401

402

403
    @Override
404
    public @NonNull List<JRecordComponentSymbol> getRecordComponents() {
405
        parseLock.ensureParsed();
1✔
406
        return recordComponents;
1✔
407
    }
408

409

410
    @Override
411
    public List<JClassSymbol> getPermittedSubtypes() {
412
        parseLock.ensureParsed();
1✔
413
        return permittedSubclasses;
1✔
414
    }
415

416
    @Override
417
    public JTypeParameterOwnerSymbol getEnclosingTypeParameterOwner() {
418
        parseLock.ensureParsed();
1✔
419
        return enclosingInfo.getEnclosing();
1✔
420
    }
421

422
    @Override
423
    public String toString() {
424
        // do not use SymbolToString as it triggers the class parsing,
425
        // making tests undebuggable
426
        return getInternalName();
1✔
427
    }
428

429
    @Override
430
    public int hashCode() {
431
        return SymbolEquality.CLASS.hash(this);
1✔
432
    }
433

434
    @Override
435
    public boolean equals(Object obj) {
436
        return SymbolEquality.CLASS.equals(this, obj);
1✔
437
    }
438

439
    // <editor-fold  defaultstate="collapsed" desc="Names">
440

441
    public String getInternalName() {
442
        return getNames().internalName;
1✔
443
    }
444

445
    private Names getNames() {
446
        return names;
1✔
447
    }
448

449
    @Override
450
    public @NonNull String getBinaryName() {
451
        return getNames().binaryName;
1✔
452
    }
453

454
    /**
455
     * Simpler check than computing the canonical name.
456
     */
457
    boolean hasCanonicalName() {
458
        if (names.canonicalName != null) {
1✔
459
            return true;
1✔
460
        }
461
        parseLock.ensureParsed();
1✔
462
        if (isAnonymousClass() || isLocalClass()) {
1!
463
            return false;
×
464
        }
465
        JClassSymbol enclosing = getEnclosingClass();
1✔
466
        return enclosing == null // top-level class
1!
467
            || enclosing instanceof ClassStub
468
            && ((ClassStub) enclosing).hasCanonicalName();
1!
469
    }
470

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

481
    private @Nullable String computeCanonicalName() {
482
        parseLock.ensureParsed();
1✔
483
        if (names.canonicalName != null) {
1!
484
            return names.canonicalName;
×
485
        }
486
        JClassSymbol enclosing = getEnclosingClass();
1✔
487
        if (enclosing == null) {
1!
488
            return names.packageName + '.' + getSimpleName();
×
489
        }
490
        String outerName = enclosing.getCanonicalName();
1✔
491
        if (outerName == null) {
1!
492
            return null;
×
493
        }
494
        return outerName + '.' + getSimpleName();
1✔
495
    }
496

497
    @Override
498
    public @NonNull String getPackageName() {
499
        return getNames().packageName;
1✔
500
    }
501

502
    @Override
503
    public @NonNull String getSimpleName() {
504
        String mySimpleName = names.simpleName;
1✔
505
        if (mySimpleName == null) {
1!
506
            parseLock.ensureParsed();
×
507
            return Objects.requireNonNull(names.simpleName, "Null simple name after parsing");
×
508
        }
509
        return mySimpleName;
1✔
510
    }
511

512
    @Override
513
    public TypeSystem getTypeSystem() {
514
        return getResolver().getTypeSystem();
1✔
515
    }
516

517
    // </editor-fold>
518

519
    // <editor-fold  defaultstate="collapsed" desc="Modifier info">
520

521

522
    @Override
523
    public boolean isUnresolved() {
524
        return parseLock.isFailed();
1✔
525
    }
526

527
    @Override
528
    public boolean isArray() {
529
        return false;
1✔
530
    }
531

532
    @Override
533
    public boolean isPrimitive() {
534
        return false;
×
535
    }
536

537
    @Override
538
    public @Nullable JTypeDeclSymbol getArrayComponent() {
539
        return null;
×
540
    }
541

542
    @Override
543
    public int getModifiers() {
544
        parseLock.ensureParsed();
1✔
545
        return accessFlags;
1✔
546
    }
547

548
    @Override
549
    public boolean isAbstract() {
550
        return (getModifiers() & Opcodes.ACC_ABSTRACT) != 0;
1✔
551
    }
552

553
    @Override
554
    public boolean isEnum() {
555
        return (getModifiers() & Opcodes.ACC_ENUM) != 0;
1✔
556
    }
557

558
    @Override
559
    public boolean isAnnotation() {
560
        return (getModifiers() & Opcodes.ACC_ANNOTATION) != 0;
1✔
561
    }
562

563
    @Override
564
    public boolean isInterface() {
565
        return (getModifiers() & Opcodes.ACC_INTERFACE) != 0;
1✔
566
    }
567

568
    @Override
569
    public boolean isClass() {
570
        return (getModifiers() & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ANNOTATION)) == 0;
1✔
571
    }
572

573
    @Override
574
    public boolean isRecord() {
575
        JClassSymbol sup = getSuperclass();
1✔
576
        return sup != null && "java.lang.Record".equals(sup.getBinaryName());
1!
577
    }
578

579
    @Override
580
    public boolean isLocalClass() {
581
        return enclosingInfo.isLocal();
1✔
582
    }
583

584
    @Override
585
    public boolean isAnonymousClass() {
586
        return getSimpleName().isEmpty();
1✔
587
    }
588

589
    boolean isFailed() {
590
        return this.parseLock.isFailed();
1✔
591
    }
592

593
    boolean isNotParsed() {
594
        return this.parseLock.isNotParsed();
1✔
595
    }
596

597

598
    // </editor-fold>
599

600

601
    static class Names {
1✔
602

603
        final String binaryName;
604
        final String internalName;
605
        final String packageName;
606
        /** If null, the class requires parsing to find out the actual canonical name. */
607
        @Nullable String canonicalName;
608
        /** If null, the class requires parsing to find out the actual simple name. */
609
        @Nullable String simpleName;
610

611
        Names(String internalName) {
1✔
612
            assert isValidInternalName(internalName) : internalName;
1!
613
            int packageEnd = internalName.lastIndexOf('/');
1✔
614

615
            this.internalName = internalName;
1✔
616
            this.binaryName = internalName.replace('/', '.');
1✔
617
            if (packageEnd == -1) {
1✔
618
                this.packageName = "";
1✔
619
            } else {
620
                this.packageName = binaryName.substring(0, packageEnd);
1✔
621
            }
622

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

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

641
        public void finishOuterClass() {
642
            int packageEnd = internalName.lastIndexOf('/');
1✔
643
            this.simpleName = binaryName.substring(packageEnd + 1); // if -1, start from 0
1✔
644
            this.canonicalName = binaryName;
1✔
645
        }
1✔
646
    }
647

648
    static class EnclosingInfo {
649

650
        static final EnclosingInfo NO_ENCLOSING = new EnclosingInfo(null, null, null);
1✔
651

652
        private final @Nullable JClassSymbol stub;
653
        private final @Nullable String methodName;
654
        private final @Nullable String methodDescriptor;
655

656
        EnclosingInfo(@Nullable JClassSymbol stub, @Nullable String methodName, @Nullable String methodDescriptor) {
1✔
657
            this.stub = stub;
1✔
658
            this.methodName = methodName;
1✔
659
            this.methodDescriptor = methodDescriptor;
1✔
660
        }
1✔
661

662
        boolean isLocal() {
663
            return methodName != null || methodDescriptor != null;
1!
664
        }
665

666
        public @Nullable JClassSymbol getEnclosingClass() {
667
            return stub;
1✔
668
        }
669

670
        public @Nullable MethodStub getEnclosingMethod() {
671
            if (stub instanceof ClassStub && methodName != null) {
1!
672
                ClassStub stub1 = (ClassStub) stub;
×
673
                stub1.parseLock.ensureParsed();
×
674
                for (JMethodSymbol m : stub1.methods) {
×
675
                    MethodStub ms = (MethodStub) m;
×
676
                    if (ms.matches(methodName, methodDescriptor)) {
×
677
                        return ms;
×
678
                    }
679
                }
×
680
            }
681
            return null;
1✔
682
        }
683

684

685
        JTypeParameterOwnerSymbol getEnclosing() {
686
            if (methodName != null) {
1!
687
                return getEnclosingMethod();
×
688
            } else {
689
                return getEnclosingClass();
1✔
690
            }
691
        }
692

693
        @Override
694
        public boolean equals(Object o) {
695
            if (this == o) {
1✔
696
                return true;
1✔
697
            }
698
            if (o == null || getClass() != o.getClass()) {
1!
699
                return false;
×
700
            }
701
            EnclosingInfo that = (EnclosingInfo) o;
1✔
702
            return Objects.equals(stub, that.stub)
1!
703
                && Objects.equals(methodName, that.methodName)
×
704
                && Objects.equals(methodDescriptor, that.methodDescriptor);
1!
705
        }
706

707
        @Override
708
        public int hashCode() {
709
            return Objects.hash(stub, methodName, methodDescriptor);
×
710
        }
711
    }
712
}
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