1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.analysis;
33
34import com.google.common.base.Predicates;
35import com.google.common.base.Supplier;
36import com.google.common.base.Suppliers;
37import com.google.common.collect.Iterables;
38import com.google.common.collect.Lists;
39import com.google.common.collect.Maps;
40import org.jf.dexlib2.AccessFlags;
41import org.jf.dexlib2.analysis.util.TypeProtoUtils;
42import org.jf.dexlib2.iface.ClassDef;
43import org.jf.dexlib2.iface.Field;
44import org.jf.dexlib2.iface.Method;
45import org.jf.dexlib2.iface.reference.FieldReference;
46import org.jf.dexlib2.iface.reference.MethodReference;
47import org.jf.dexlib2.immutable.ImmutableMethod;
48import org.jf.util.ExceptionWithContext;
49import org.jf.util.SparseArray;
50
51import javax.annotation.Nonnull;
52import javax.annotation.Nullable;
53import java.util.ArrayList;
54import java.util.Collections;
55import java.util.LinkedHashMap;
56import java.util.List;
57
58/**
59 * A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields
60 * and their offsets.
61 */
62public class ClassProto implements TypeProto {
63    @Nonnull protected final ClassPath classPath;
64    @Nonnull protected final String type;
65
66    protected boolean vtableFullyResolved = true;
67    protected boolean interfacesFullyResolved = true;
68
69    public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) {
70        if (type.charAt(0) != 'L') {
71            throw new ExceptionWithContext("Cannot construct ClassProto for non reference type: %s", type);
72        }
73        this.classPath = classPath;
74        this.type = type;
75    }
76
77    @Override public String toString() { return type; }
78    @Nonnull @Override public ClassPath getClassPath() { return classPath; }
79    @Nonnull @Override public String getType() { return type; }
80
81    @Nonnull
82    public ClassDef getClassDef() {
83        return classDefSupplier.get();
84    }
85
86
87    @Nonnull private final Supplier<ClassDef> classDefSupplier = Suppliers.memoize(new Supplier<ClassDef>() {
88        @Override public ClassDef get() {
89            return classPath.getClassDef(type);
90        }
91    });
92
93    /**
94     * Returns true if this class is an interface.
95     *
96     * If this class is not defined, then this will throw an UnresolvedClassException
97     *
98     * @return True if this class is an interface
99     */
100    public boolean isInterface() {
101        ClassDef classDef = getClassDef();
102        return (classDef.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
103    }
104
105    /**
106     * Returns the set of interfaces that this class implements as a Map<String, ClassDef>.
107     *
108     * The ClassDef value will be present only for the interfaces that this class directly implements (including any
109     * interfaces transitively implemented), but not for any interfaces that are only implemented by a superclass of
110     * this class
111     *
112     * For any interfaces that are only implemented by a superclass (or the class itself, if the class is an interface),
113     * the value will be null.
114     *
115     * If any interface couldn't be resolved, then the interfacesFullyResolved field will be set to false upon return.
116     *
117     * @return the set of interfaces that this class implements as a Map<String, ClassDef>.
118     */
119    @Nonnull
120    protected LinkedHashMap<String, ClassDef> getInterfaces() {
121        return interfacesSupplier.get();
122    }
123
124    @Nonnull
125    private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier =
126            Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
127                @Override public LinkedHashMap<String, ClassDef> get() {
128                    LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
129
130                    try {
131                        for (String interfaceType: getClassDef().getInterfaces()) {
132                            if (!interfaces.containsKey(interfaceType)) {
133                                ClassDef interfaceDef;
134                                try {
135                                    interfaceDef = classPath.getClassDef(interfaceType);
136                                    interfaces.put(interfaceType, interfaceDef);
137                                } catch (UnresolvedClassException ex) {
138                                    interfaces.put(interfaceType, null);
139                                    interfacesFullyResolved = false;
140                                }
141
142                                ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
143                                for (String superInterface: interfaceProto.getInterfaces().keySet()) {
144                                    if (!interfaces.containsKey(superInterface)) {
145                                        interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
146                                    }
147                                }
148                                if (!interfaceProto.interfacesFullyResolved) {
149                                    interfacesFullyResolved = false;
150                                }
151                            }
152                        }
153                    } catch (UnresolvedClassException ex) {
154                        interfacesFullyResolved = false;
155                    }
156
157                    // now add self and super class interfaces, required for common super class lookup
158                    // we don't really need ClassDef's for that, so let's just use null
159
160                    if (isInterface() && !interfaces.containsKey(getType())) {
161                        interfaces.put(getType(), null);
162                    }
163
164                    try {
165                        String superclass = getSuperclass();
166                        if (superclass != null) {
167                            ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
168                            for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
169                                if (!interfaces.containsKey(superclassInterface)) {
170                                    interfaces.put(superclassInterface, null);
171                                }
172                            }
173                            if (!superclassProto.interfacesFullyResolved) {
174                                interfacesFullyResolved = false;
175                            }
176                        }
177                    } catch (UnresolvedClassException ex) {
178                        interfacesFullyResolved = false;
179                    }
180
181                    return interfaces;
182                }
183            });
184
185    /**
186     * Gets the interfaces directly implemented by this class, or the interfaces they transitively implement.
187     *
188     * This does not include any interfaces that are only implemented by a superclass
189     *
190     * @return An iterables of ClassDefs representing the directly or transitively implemented interfaces
191     * @throws UnresolvedClassException if interfaces could not be fully resolved
192     */
193    @Nonnull
194    protected Iterable<ClassDef> getDirectInterfaces() {
195        Iterable<ClassDef> directInterfaces =
196                Iterables.filter(getInterfaces().values(), Predicates.notNull());
197
198        if (!interfacesFullyResolved) {
199            throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
200        }
201
202        return directInterfaces;
203    }
204
205    /**
206     * Checks if this class implements the given interface.
207     *
208     * If the interfaces of this class cannot be fully resolved then this
209     * method will either return true or throw an UnresolvedClassException
210     *
211     * @param iface The interface to check for
212     * @return true if this class implements the given interface, otherwise false
213     * @throws UnresolvedClassException if the interfaces for this class could not be fully resolved, and the interface
214     * is not one of the interfaces that were successfully resolved
215     */
216    @Override
217    public boolean implementsInterface(@Nonnull String iface) {
218        if (getInterfaces().containsKey(iface)) {
219            return true;
220        }
221        if (!interfacesFullyResolved) {
222            throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
223        }
224        return false;
225    }
226
227    @Nullable @Override
228    public String getSuperclass() {
229        return getClassDef().getSuperclass();
230    }
231
232    /**
233     * This is a helper method for getCommonSuperclass
234     *
235     * It checks if this class is an interface, and if so, if other implements it.
236     *
237     * If this class is undefined, we go ahead and check if it is listed in other's interfaces. If not, we throw an
238     * UndefinedClassException
239     *
240     * If the interfaces of other cannot be fully resolved, we check the interfaces that can be resolved. If not found,
241     * we throw an UndefinedClassException
242     *
243     * @param other The class to check the interfaces of
244     * @return true if this class is an interface (or is undefined) other implements this class
245     *
246     */
247    private boolean checkInterface(@Nonnull ClassProto other) {
248        boolean isResolved = true;
249        boolean isInterface = true;
250        try {
251            isInterface = isInterface();
252        } catch (UnresolvedClassException ex) {
253            isResolved = false;
254            // if we don't know if this class is an interface or not,
255            // we can still try to call other.implementsInterface(this)
256        }
257        if (isInterface) {
258            try {
259                if (other.implementsInterface(getType())) {
260                    return true;
261                }
262            } catch (UnresolvedClassException ex) {
263                // There are 2 possibilities here, depending on whether we were able to resolve this class.
264                // 1. If this class is resolved, then we know it is an interface class. The other class either
265                //    isn't defined, or its interfaces couldn't be fully resolved.
266                //    In this case, we throw an UnresolvedClassException
267                // 2. If this class is not resolved, we had tried to call implementsInterface anyway. We don't
268                //    know for sure if this class is an interface or not. We return false, and let processing
269                //    continue in getCommonSuperclass
270                if (isResolved) {
271                    throw ex;
272                }
273            }
274        }
275        return false;
276    }
277
278    @Override @Nonnull
279    public TypeProto getCommonSuperclass(@Nonnull TypeProto other) {
280        // use the other type's more specific implementation
281        if (!(other instanceof ClassProto)) {
282            return other.getCommonSuperclass(this);
283        }
284
285        if (this == other || getType().equals(other.getType())) {
286            return this;
287        }
288
289        if (this.getType().equals("Ljava/lang/Object;")) {
290            return this;
291        }
292
293        if (other.getType().equals("Ljava/lang/Object;")) {
294            return other;
295        }
296
297        boolean gotException = false;
298        try {
299            if (checkInterface((ClassProto)other)) {
300                return this;
301            }
302        } catch (UnresolvedClassException ex) {
303            gotException = true;
304        }
305
306        try {
307            if (((ClassProto)other).checkInterface(this)) {
308                return other;
309            }
310        } catch (UnresolvedClassException ex) {
311            gotException = true;
312        }
313        if (gotException) {
314            return classPath.getUnknownClass();
315        }
316
317        List<TypeProto> thisChain = Lists.<TypeProto>newArrayList(this);
318        Iterables.addAll(thisChain, TypeProtoUtils.getSuperclassChain(this));
319
320        List<TypeProto> otherChain = Lists.newArrayList(other);
321        Iterables.addAll(otherChain, TypeProtoUtils.getSuperclassChain(other));
322
323        // reverse them, so that the first entry is either Ljava/lang/Object; or Ujava/lang/Object;
324        thisChain = Lists.reverse(thisChain);
325        otherChain = Lists.reverse(otherChain);
326
327        for (int i=Math.min(thisChain.size(), otherChain.size())-1; i>=0; i--) {
328            TypeProto typeProto = thisChain.get(i);
329            if (typeProto.getType().equals(otherChain.get(i).getType())) {
330                return typeProto;
331            }
332        }
333
334        return classPath.getUnknownClass();
335    }
336
337    @Override
338    @Nullable
339    public FieldReference getFieldByOffset(int fieldOffset) {
340        if (getInstanceFields().size() == 0) {
341            return null;
342        }
343        return getInstanceFields().get(fieldOffset);
344    }
345
346    @Override
347    @Nullable
348    public MethodReference getMethodByVtableIndex(int vtableIndex) {
349        List<Method> vtable = getVtable();
350        if (vtableIndex < 0 || vtableIndex >= vtable.size()) {
351            return null;
352        }
353
354        return vtable.get(vtableIndex);
355    }
356
357    @Nonnull SparseArray<FieldReference> getInstanceFields() {
358        return instanceFieldsSupplier.get();
359    }
360
361    @Nonnull private final Supplier<SparseArray<FieldReference>> instanceFieldsSupplier =
362            Suppliers.memoize(new Supplier<SparseArray<FieldReference>>() {
363                @Override public SparseArray<FieldReference> get() {
364                    //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
365                    //arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
366                    //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
367
368                    final byte REFERENCE = 0;
369                    final byte WIDE = 1;
370                    final byte OTHER = 2;
371
372                    ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
373                    final int fieldCount = fields.size();
374                    //the "type" for each field in fields. 0=reference,1=wide,2=other
375                    byte[] fieldTypes = new byte[fields.size()];
376                    for (int i=0; i<fieldCount; i++) {
377                        fieldTypes[i] = getFieldType(fields.get(i));
378                    }
379
380                    //The first operation is to move all of the reference fields to the front. To do this, find the first
381                    //non-reference field, then find the last reference field, swap them and repeat
382                    int back = fields.size() - 1;
383                    int front;
384                    for (front = 0; front<fieldCount; front++) {
385                        if (fieldTypes[front] != REFERENCE) {
386                            while (back > front) {
387                                if (fieldTypes[back] == REFERENCE) {
388                                    swap(fieldTypes, fields, front, back--);
389                                    break;
390                                }
391                                back--;
392                            }
393                        }
394
395                        if (fieldTypes[front] != REFERENCE) {
396                            break;
397                        }
398                    }
399
400                    int startFieldOffset = 8;
401                    String superclassType = getSuperclass();
402                    ClassProto superclass = null;
403                    if (superclassType != null) {
404                        superclass = (ClassProto) classPath.getClass(superclassType);
405                        if (superclass != null) {
406                            startFieldOffset = superclass.getNextFieldOffset();
407                        }
408                    }
409
410                    int fieldIndexMod;
411                    if ((startFieldOffset % 8) == 0) {
412                        fieldIndexMod = 0;
413                    } else {
414                        fieldIndexMod = 1;
415                    }
416
417                    //next, we need to group all the wide fields after the reference fields. But the wide fields have to be
418                    //8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
419                    //is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
420                    //If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
421                    if (front < fieldCount && (front % 2) != fieldIndexMod) {
422                        if (fieldTypes[front] == WIDE) {
423                            //we need to swap in a 32-bit field, so the wide fields will be correctly aligned
424                            back = fieldCount - 1;
425                            while (back > front) {
426                                if (fieldTypes[back] == OTHER) {
427                                    swap(fieldTypes, fields, front++, back);
428                                    break;
429                                }
430                                back--;
431                            }
432                        } else {
433                            //there's already a 32-bit field here that we can use
434                            front++;
435                        }
436                    }
437
438                    //do the swap thing for wide fields
439                    back = fieldCount - 1;
440                    for (; front<fieldCount; front++) {
441                        if (fieldTypes[front] != WIDE) {
442                            while (back > front) {
443                                if (fieldTypes[back] == WIDE) {
444                                    swap(fieldTypes, fields, front, back--);
445                                    break;
446                                }
447                                back--;
448                            }
449                        }
450
451                        if (fieldTypes[front] != WIDE) {
452                            break;
453                        }
454                    }
455
456                    SparseArray<FieldReference> superFields;
457                    if (superclass != null) {
458                        superFields = superclass.getInstanceFields();
459                    } else {
460                        superFields = new SparseArray<FieldReference>();
461                    }
462                    int superFieldCount = superFields.size();
463
464                    //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
465                    int totalFieldCount = superFieldCount + fieldCount;
466                    SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
467
468                    int fieldOffset;
469
470                    if (superclass != null && superFieldCount > 0) {
471                        for (int i=0; i<superFieldCount; i++) {
472                            instanceFields.append(superFields.keyAt(i), superFields.valueAt(i));
473                        }
474
475                        fieldOffset = instanceFields.keyAt(superFieldCount-1);
476
477                        FieldReference lastSuperField = superFields.valueAt(superFieldCount-1);
478                        char fieldType = lastSuperField.getType().charAt(0);
479                        if (fieldType == 'J' || fieldType == 'D') {
480                            fieldOffset += 8;
481                        } else {
482                            fieldOffset += 4;
483                        }
484                    } else {
485                        //the field values start at 8 bytes into the DataObject dalvik structure
486                        fieldOffset = 8;
487                    }
488
489                    boolean gotDouble = false;
490                    for (int i=0; i<fieldCount; i++) {
491                        FieldReference field = fields.get(i);
492
493                        //add padding to align the wide fields, if needed
494                        if (fieldTypes[i] == WIDE && !gotDouble) {
495                            if (!gotDouble) {
496                                if (fieldOffset % 8 != 0) {
497                                    assert fieldOffset % 8 == 4;
498                                    fieldOffset += 4;
499                                }
500                                gotDouble = true;
501                            }
502                        }
503
504                        instanceFields.append(fieldOffset, field);
505                        if (fieldTypes[i] == WIDE) {
506                            fieldOffset += 8;
507                        } else {
508                            fieldOffset += 4;
509                        }
510                    }
511
512                    return instanceFields;
513                }
514
515                @Nonnull
516                private ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
517                    ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
518                    Collections.sort(fields);
519                    return fields;
520                }
521
522                private byte getFieldType(@Nonnull FieldReference field) {
523                    switch (field.getType().charAt(0)) {
524                        case '[':
525                        case 'L':
526                            return 0; //REFERENCE
527                        case 'J':
528                        case 'D':
529                            return 1; //WIDE
530                        default:
531                            return 2; //OTHER
532                    }
533                }
534
535                private void swap(byte[] fieldTypes, List<Field> fields, int position1, int position2) {
536                    byte tempType = fieldTypes[position1];
537                    fieldTypes[position1] = fieldTypes[position2];
538                    fieldTypes[position2] = tempType;
539
540                    Field tempField = fields.set(position1, fields.get(position2));
541                    fields.set(position2, tempField);
542                }
543            });
544
545    private int getNextFieldOffset() {
546        SparseArray<FieldReference> instanceFields = getInstanceFields();
547        if (instanceFields.size() == 0) {
548            return 8;
549        }
550
551        int lastItemIndex = instanceFields.size()-1;
552        int fieldOffset = instanceFields.keyAt(lastItemIndex);
553        FieldReference lastField = instanceFields.valueAt(lastItemIndex);
554
555        switch (lastField.getType().charAt(0)) {
556            case 'J':
557            case 'D':
558                return fieldOffset + 8;
559            default:
560                return fieldOffset + 4;
561        }
562    }
563
564    @Nonnull List<Method> getVtable() {
565        return vtableSupplier.get();
566    }
567
568    //TODO: check the case when we have a package private method that overrides an interface method
569    @Nonnull private final Supplier<List<Method>> vtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
570        @Override public List<Method> get() {
571            List<Method> vtable = Lists.newArrayList();
572
573            //copy the virtual methods from the superclass
574            String superclassType;
575            try {
576                superclassType = getSuperclass();
577            } catch (UnresolvedClassException ex) {
578                vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
579                vtableFullyResolved = false;
580                return vtable;
581            }
582
583            if (superclassType != null) {
584                ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
585                vtable.addAll(superclass.getVtable());
586
587                // if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this
588                // class should start, so we just propagate what we can from the parent and hope for the best.
589                if (!superclass.vtableFullyResolved) {
590                    vtableFullyResolved = false;
591                    return vtable;
592                }
593            }
594
595            //iterate over the virtual methods in the current class, and only add them when we don't already have the
596            //method (i.e. if it was implemented by the superclass)
597            if (!isInterface()) {
598                addToVtable(getClassDef().getVirtualMethods(), vtable, true);
599
600                // assume that interface method is implemented in the current class, when adding it to vtable
601                // otherwise it looks like that method is invoked on an interface, which fails Dalvik's optimization checks
602                for (ClassDef interfaceDef: getDirectInterfaces()) {
603                    List<Method> interfaceMethods = Lists.newArrayList();
604                    for (Method interfaceMethod: interfaceDef.getVirtualMethods()) {
605                        ImmutableMethod method = new ImmutableMethod(
606                                type,
607                                interfaceMethod.getName(),
608                                interfaceMethod.getParameters(),
609                                interfaceMethod.getReturnType(),
610                                interfaceMethod.getAccessFlags(),
611                                interfaceMethod.getAnnotations(),
612                                interfaceMethod.getImplementation());
613                        interfaceMethods.add(method);
614                    }
615                    addToVtable(interfaceMethods, vtable, false);
616                }
617            }
618            return vtable;
619        }
620
621        private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
622                                 @Nonnull List<Method> vtable, boolean replaceExisting) {
623            List<? extends Method> methods = Lists.newArrayList(localMethods);
624            Collections.sort(methods);
625
626            outer: for (Method virtualMethod: methods) {
627                for (int i=0; i<vtable.size(); i++) {
628                    Method superMethod = vtable.get(i);
629                    if (methodSignaturesMatch(superMethod, virtualMethod)) {
630                        if (!classPath.shouldCheckPackagePrivateAccess() || canAccess(superMethod)) {
631                            if (replaceExisting) {
632                                vtable.set(i, virtualMethod);
633                            }
634                            continue outer;
635                        }
636                    }
637                }
638                // we didn't find an equivalent method, so add it as a new entry
639                vtable.add(virtualMethod);
640            }
641        }
642
643        private boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) {
644            return (a.getName().equals(b.getName()) &&
645                    a.getReturnType().equals(b.getReturnType()) &&
646                    a.getParameters().equals(b.getParameters()));
647        }
648
649        private boolean canAccess(@Nonnull Method virtualMethod) {
650            if (!methodIsPackagePrivate(virtualMethod.getAccessFlags())) {
651                return true;
652            }
653
654            String otherPackage = getPackage(virtualMethod.getDefiningClass());
655            String ourPackage = getPackage(getClassDef().getType());
656            return otherPackage.equals(ourPackage);
657        }
658
659        @Nonnull
660        private String getPackage(@Nonnull String classType) {
661            int lastSlash = classType.lastIndexOf('/');
662            if (lastSlash < 0) {
663                return "";
664            }
665            return classType.substring(1, lastSlash);
666        }
667
668        private boolean methodIsPackagePrivate(int accessFlags) {
669            return (accessFlags & (AccessFlags.PRIVATE.getValue() |
670                    AccessFlags.PROTECTED.getValue() |
671                    AccessFlags.PUBLIC.getValue())) == 0;
672        }
673    });
674}
675