All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.umd.cs.findbugs.detect.SerializableIdiom Maven / Gradle / Ivy

The newest version!
/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003,2004 University of Maryland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.detect;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import edu.umd.cs.findbugs.util.ClassName;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.FieldOrMethod;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.Synthetic;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.ClassAnnotation;
import edu.umd.cs.findbugs.DeepSubtypeAnalysis;
import edu.umd.cs.findbugs.OpcodeStack.Item;
import edu.umd.cs.findbugs.Priorities;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.FieldSummary;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.ba.type.TypeFrameModelingVisitor;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.Global;

public class SerializableIdiom extends OpcodeStackDetector {

    private static final boolean DEBUG = SystemProperties.getBoolean("se.debug");

    final static boolean reportTransientFieldOfNonSerializableClass = SystemProperties
            .getBoolean("reportTransientFieldOfNonSerializableClass");

    boolean sawSerialVersionUID;

    boolean isSerializable, implementsSerializableDirectly;

    boolean isExternalizable;

    boolean isGUIClass;

    boolean isEjbImplClass;

    boolean isJSPClass;

    boolean isRecord;

    boolean foundSynthetic;

    boolean seenTransientField;

    boolean foundSynchronizedMethods;

    boolean writeObjectIsSynchronized;

    private final BugReporter bugReporter;

    boolean isAbstract;

    private final List fieldWarningList = new LinkedList<>();

    private final HashMap fieldsThatMightBeAProblem = new HashMap<>();

    private final HashMap transientFieldsUpdates = new HashMap<>();

    private final HashSet transientFieldsSetInConstructor = new HashSet<>();

    private final HashSet transientFieldsSetToDefaultValueInConstructor = new HashSet<>();

    private final Map optionalBugsInReadExternal = new HashMap();

    private Set initializedCheckerVariables = new HashSet<>();

    private int initializeCheckerBranchTarget;

    private boolean sawReadExternalBranchExit;

    private boolean sawReadExternalExit;

    private boolean sawReadExternal;

    private boolean sawWriteExternal;

    private boolean sawReadObject;

    private boolean sawReadResolve;

    private boolean sawWriteObject;

    private boolean superClassImplementsSerializable;

    private boolean superClassHasReadObject;

    private boolean hasPublicVoidConstructor;

    private boolean superClassHasVoidConstructor;

    private boolean directlyImplementsExternalizable;

    private final boolean testingEnabled;

    // private JavaClass serializable;
    // private JavaClass collection;
    // private JavaClass map;
    // private boolean isRemote;

    public SerializableIdiom(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
        testingEnabled = SystemProperties.getBoolean("report_TESTING_pattern_in_standard_detectors");
    }

    @Override
    public void visitClassContext(ClassContext classContext) {
        classContext.getJavaClass().accept(this);
        flush();
    }

    private void flush() {
        if (!isAbstract && !((sawReadExternal && sawWriteExternal) || (sawReadObject && sawWriteObject))) {
            for (BugInstance aFieldWarningList : fieldWarningList) {
                bugReporter.reportBug(aFieldWarningList);
            }
        }
        fieldWarningList.clear();
    }

    static final Pattern anonymousInnerClassNamePattern = Pattern.compile(".+\\$\\d+");

    boolean isAnonymousInnerClass;

    boolean innerClassHasOuterInstance;

    private boolean isEnum;

    @Override
    public void visit(JavaClass obj) {
        isEnum = Subtypes2.isEnum(obj);
        isRecord = Subtypes2.isRecord(obj);

        if (isEnum || isRecord) {
            return;
        }
        int flags = obj.getAccessFlags();
        isAbstract = (flags & Const.ACC_ABSTRACT) != 0 || (flags & Const.ACC_INTERFACE) != 0;
        isAnonymousInnerClass = anonymousInnerClassNamePattern.matcher(getClassName()).matches();
        innerClassHasOuterInstance = false;
        for (Field f : obj.getFields()) {
            if ("this$0".equals(f.getName())) {
                innerClassHasOuterInstance = true;
                break;
            }
        }

        sawSerialVersionUID = false;
        isSerializable = implementsSerializableDirectly = false;
        isExternalizable = false;
        directlyImplementsExternalizable = false;
        isGUIClass = false;
        isEjbImplClass = false;
        isJSPClass = false;
        isRecord = false;
        seenTransientField = false;
        // boolean isEnum = obj.getSuperclassName().equals("java.lang.Enum");
        fieldsThatMightBeAProblem.clear();
        transientFieldsUpdates.clear();
        transientFieldsSetInConstructor.clear();
        transientFieldsSetToDefaultValueInConstructor.clear();
        // isRemote = false;

        // Does this class directly implement Serializable?
        String[] interface_names = obj.getInterfaceNames();
        for (String interface_name : interface_names) {
            if ("java.io.Externalizable".equals(interface_name)) {
                directlyImplementsExternalizable = true;
                isExternalizable = true;
                if (DEBUG) {
                    System.out.println("Directly implements Externalizable: " + getClassName());
                }
            } else if ("java.io.Serializable".equals(interface_name)) {
                implementsSerializableDirectly = true;
                isSerializable = true;
                if (DEBUG) {
                    System.out.println("Directly implements Serializable: " + getClassName());
                }
                break;
            }
        }

        // Does this class indirectly implement Serializable?
        if (!isSerializable) {
            if (Subtypes2.instanceOf(obj, "java.io.Externalizable")) {
                isExternalizable = true;
                if (DEBUG) {
                    System.out.println("Indirectly implements Externalizable: " + getClassName());
                }
            }
            if (Subtypes2.instanceOf(obj, "java.io.Serializable")) {
                isSerializable = true;
                if (DEBUG) {
                    System.out.println("Indirectly implements Serializable: " + getClassName());
                }
            }
        }

        hasPublicVoidConstructor = false;
        superClassHasVoidConstructor = true;
        superClassHasReadObject = false;
        superClassImplementsSerializable = isSerializable && !implementsSerializableDirectly;
        ClassDescriptor superclassDescriptor = getXClass().getSuperclassDescriptor();
        if (superclassDescriptor != null) {
            try {
                XClass superXClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, superclassDescriptor);
                if (superXClass != null) {
                    superClassImplementsSerializable = AnalysisContext
                            .currentAnalysisContext()
                            .getSubtypes2()
                            .isSubtype(superXClass.getClassDescriptor(),
                                    DescriptorFactory.createClassDescriptor(java.io.Serializable.class));
                    superClassHasVoidConstructor = false;
                    for (XMethod m : superXClass.getXMethods()) {
                        if (Const.CONSTRUCTOR_NAME.equals(m.getName()) && "()V".equals(m.getSignature()) && !m.isPrivate()) {
                            superClassHasVoidConstructor = true;
                        }
                        if ("readObject".equals(m.getName()) && "(Ljava/io/ObjectInputStream;)V".equals(m.getSignature())
                                && m.isPrivate()) {
                            superClassHasReadObject = true;
                        }
                    }
                }
            } catch (ClassNotFoundException e) {
                bugReporter.reportMissingClass(e);
            } catch (CheckedAnalysisException e) {
                AnalysisContext.logError("Error while analyzing " + obj.getClassName(), e);
            }
        }

        // Is this a GUI or other class that is rarely serialized?

        isGUIClass = false;
        isEjbImplClass = Subtypes2.instanceOf(obj, "javax.ejb.SessionBean") || Subtypes2.instanceOf(obj, "jakarta.ejb.SessionBean");
        isJSPClass = Subtypes2.isJSP(obj);
        isGUIClass = (Subtypes2.instanceOf(obj, "java.lang.Throwable") || Subtypes2.instanceOf(obj, "java.awt.Component")
                || Subtypes2.instanceOf(obj, "java.awt.Component$AccessibleAWTComponent")
                || Subtypes2.instanceOf(obj, "java.awt.event.ActionListener") || Subtypes2.instanceOf(obj,
                        "java.util.EventListener"));
        if (!isGUIClass) {
            JavaClass o = obj;
            while (o != null) {
                if (o.getClassName().startsWith("java.awt") || o.getClassName().startsWith("javax.swing")) {
                    isGUIClass = true;
                    break;
                }
                try {
                    o = o.getSuperClass();
                } catch (ClassNotFoundException e) {
                    break;
                }
            }

        }

        foundSynthetic = false;
        foundSynchronizedMethods = false;
        writeObjectIsSynchronized = false;

        sawReadExternal = sawWriteExternal = sawReadObject = sawReadResolve = sawWriteObject = false;
        if (isSerializable) {
            for (Method m : obj.getMethods()) {

                if ("readObject".equals(m.getName()) && "(Ljava/io/ObjectInputStream;)V".equals(m.getSignature())) {
                    sawReadObject = true;
                } else if ("readResolve".equals(m.getName()) && m.getSignature().startsWith("()")) {
                    sawReadResolve = true;
                } else if ("readObjectNoData".equals(m.getName()) && "()V".equals(m.getSignature())) {
                    sawReadObject = true;
                } else if ("writeObject".equals(m.getName()) && "(Ljava/io/ObjectOutputStream;)V".equals(m.getSignature())) {
                    sawWriteObject = true;
                }
            }
            for (Field f : obj.getFields()) {
                if (f.isTransient()) {
                    seenTransientField = true;
                }
            }
        }
    }

    private boolean strongEvidenceForIntendedSerialization() {
        return implementsSerializableDirectly
                || sawReadObject
                || sawReadResolve
                || sawWriteObject
                || seenTransientField
                || AnalysisContext.currentAnalysisContext().getUnreadFieldsData()
                        .existsStrongEvidenceForIntendedSerialization(this.getClassDescriptor());
    }

    @Override
    public void visitAfter(JavaClass obj) {
        if (isEnum || isRecord) {
            return;
        }
        if (DEBUG) {
            System.out.println(getDottedClassName());
            System.out.println("  hasPublicVoidConstructor: " + hasPublicVoidConstructor);
            System.out.println("  superClassHasVoidConstructor: " + superClassHasVoidConstructor);
            System.out.println("  isExternalizable: " + isExternalizable);
            System.out.println("  isSerializable: " + isSerializable);
            System.out.println("  isAbstract: " + isAbstract);
            System.out.println("  superClassImplementsSerializable: " + superClassImplementsSerializable);
            System.out.println("  isGUIClass: " + isGUIClass);
            System.out.println("  isEjbImplClass: " + isEjbImplClass);
            System.out.println("  isJSPClass: " + isJSPClass);
        }
        if (isSerializable && !sawReadObject && !sawReadResolve && seenTransientField && !superClassHasReadObject) {
            for (Map.Entry e : transientFieldsUpdates.entrySet()) {

                XField fieldX = e.getKey();
                int priority = NORMAL_PRIORITY;
                if (transientFieldsSetInConstructor.contains(e.getKey())) {
                    priority--;
                }

                if (isGUIClass) {
                    priority++;
                }
                if (isEjbImplClass) {
                    priority++;
                }
                if (isJSPClass) {
                    priority++;
                }
                if (e.getValue() < 3) {
                    priority++;
                }
                if (transientFieldsSetToDefaultValueInConstructor.contains(e.getKey())) {
                    priority++;
                }
                if (obj.isAbstract()) {
                    priority++;
                    if (priority < Priorities.LOW_PRIORITY) {
                        priority = Priorities.LOW_PRIORITY;
                    }
                }

                try {
                    double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(fieldX.getSignature());
                    if (isSerializable < 0.6) {
                        priority++;
                    }
                } catch (ClassNotFoundException e1) {
                    // ignore it
                }

                bugReporter.reportBug(new BugInstance(this, "SE_TRANSIENT_FIELD_NOT_RESTORED", priority).addClass(getThisClass())
                        .addField(fieldX));

            }

        }
        if (isSerializable && !isExternalizable && !superClassHasVoidConstructor && !superClassImplementsSerializable) {
            int priority = implementsSerializableDirectly || seenTransientField ? HIGH_PRIORITY
                    : (sawSerialVersionUID ? NORMAL_PRIORITY : LOW_PRIORITY);
            if (isGUIClass || isEjbImplClass || isJSPClass) {
                priority++;
            }
            bugReporter.reportBug(new BugInstance(this, "SE_NO_SUITABLE_CONSTRUCTOR", priority).addClass(getThisClass()
                    .getClassName()));
        }
        // Downgrade class-level warnings if it's a GUI or EJB-implementation
        // class.
        int priority = (isGUIClass || isEjbImplClass || isJSPClass) ? LOW_PRIORITY : NORMAL_PRIORITY;
        if (obj.getClassName().endsWith("_Stub")) {
            priority++;
        }

        if (isExternalizable && !hasPublicVoidConstructor && !isAbstract) {
            bugReporter.reportBug(new BugInstance(this, "SE_NO_SUITABLE_CONSTRUCTOR_FOR_EXTERNALIZATION",
                    directlyImplementsExternalizable ? HIGH_PRIORITY : NORMAL_PRIORITY).addClass(getThisClass().getClassName()));
        }
        if (!foundSynthetic) {
            priority++;
        }
        if (seenTransientField) {
            priority--;
        }
        if (!isAnonymousInnerClass && !isExternalizable && !isGUIClass && !obj.isAbstract() && isSerializable && !isAbstract
                && !sawSerialVersionUID && !isEjbImplClass && !isJSPClass) {
            bugReporter.reportBug(new BugInstance(this, "SE_NO_SERIALVERSIONID", priority).addClass(this));
        }

        if (writeObjectIsSynchronized && !foundSynchronizedMethods) {
            bugReporter.reportBug(new BugInstance(this, "WS_WRITEOBJECT_SYNC", LOW_PRIORITY).addClass(this));
        }

        if (isExternalizable && sawReadExternal && !optionalBugsInReadExternal.isEmpty() && !initializedCheckerVariables.isEmpty()
                && initializedCheckerVariables.stream().noneMatch(optionalBugsInReadExternal::containsKey)) {
            optionalBugsInReadExternal.values().forEach(bugReporter::reportBug);
        }
    }

    @Override
    public void visit(Method obj) {
        int accessFlags = obj.getAccessFlags();
        boolean isSynchronized = (accessFlags & Const.ACC_SYNCHRONIZED) != 0;
        if (Const.CONSTRUCTOR_NAME.equals(getMethodName()) && "()V".equals(getMethodSig()) && (accessFlags & Const.ACC_PUBLIC) != 0) {
            hasPublicVoidConstructor = true;
        }
        if (!Const.CONSTRUCTOR_NAME.equals(getMethodName()) && isSynthetic(obj)) {
            foundSynthetic = true;
            // System.out.println(methodName + isSynchronized);
        }

        if ("readExternal".equals(getMethodName()) && "(Ljava/io/ObjectInput;)V".equals(getMethodSig())) {
            sawReadExternal = true;
            if (DEBUG && !obj.isPrivate()) {
                System.out.println("Non-private readExternal method in: " + getDottedClassName());
            }
        } else if ("writeExternal".equals(getMethodName()) && "(Ljava/io/Objectoutput;)V".equals(getMethodSig())) {
            sawWriteExternal = true;
            if (DEBUG && !obj.isPrivate()) {
                System.out.println("Non-private writeExternal method in: " + getDottedClassName());
            }
        } else if ("readResolve".equals(getMethodName()) && getMethodSig().startsWith("()") && isSerializable) {
            sawReadResolve = true;
            if (!"()Ljava/lang/Object;".equals(getMethodSig())) {
                bugReporter.reportBug(new BugInstance(this, "SE_READ_RESOLVE_MUST_RETURN_OBJECT", HIGH_PRIORITY)
                        .addClassAndMethod(this));
            } else if (obj.isStatic()) {
                bugReporter.reportBug(new BugInstance(this, "SE_READ_RESOLVE_IS_STATIC", HIGH_PRIORITY).addClassAndMethod(this));
            } else if (obj.isPrivate()) {
                try {
                    Set subtypes = AnalysisContext.currentAnalysisContext().getSubtypes2()
                            .getSubtypes(getClassDescriptor());
                    if (subtypes.size() > 1) {
                        BugInstance bug = new BugInstance(this, "SE_PRIVATE_READ_RESOLVE_NOT_INHERITED", NORMAL_PRIORITY)
                                .addClassAndMethod(this);
                        boolean nasty = false;
                        for (ClassDescriptor subclass : subtypes) {
                            if (!subclass.equals(getClassDescriptor())) {

                                XClass xSub = AnalysisContext.currentXFactory().getXClass(subclass);
                                if (xSub != null && xSub.findMethod("readResolve", "()Ljava/lang/Object;", false) == null
                                        && xSub.findMethod("writeReplace", "()Ljava/lang/Object;", false) == null) {
                                    bug.addClass(subclass).describe(ClassAnnotation.SUBCLASS_ROLE);
                                    nasty = true;
                                }
                            }
                        }
                        if (nasty) {
                            bug.setPriority(HIGH_PRIORITY);
                        } else if (!getThisClass().isPublic()) {
                            bug.setPriority(LOW_PRIORITY);
                        }
                        bugReporter.reportBug(bug);
                    }

                } catch (ClassNotFoundException e) {
                    bugReporter.reportMissingClass(e);
                }
            }

        } else if ("readObject".equals(getMethodName()) && "(Ljava/io/ObjectInputStream;)V".equals(getMethodSig())
                && isSerializable) {
            sawReadObject = true;
            if (!obj.isPrivate()) {
                bugReporter.reportBug(new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", isExternalizable ? NORMAL_PRIORITY : HIGH_PRIORITY)
                        .addClassAndMethod(this));
            }

        } else if ("readObjectNoData".equals(getMethodName()) && "()V".equals(getMethodSig()) && isSerializable) {

            if (!obj.isPrivate()) {
                bugReporter.reportBug(new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", isExternalizable ? NORMAL_PRIORITY : HIGH_PRIORITY)
                        .addClassAndMethod(this));
            }

        } else if ("writeObject".equals(getMethodName()) && "(Ljava/io/ObjectOutputStream;)V".equals(getMethodSig())
                && isSerializable) {
            sawWriteObject = true;
            if (!obj.isPrivate()) {
                bugReporter.reportBug(new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", isExternalizable ? NORMAL_PRIORITY : HIGH_PRIORITY)
                        .addClassAndMethod(this));
            }
        }

        if (isSynchronized) {
            if ("readObject".equals(getMethodName()) && "(Ljava/io/ObjectInputStream;)V".equals(getMethodSig()) && isSerializable) {
                bugReporter.reportBug(new BugInstance(this, "RS_READOBJECT_SYNC", isExternalizable ? LOW_PRIORITY : NORMAL_PRIORITY)
                        .addClassAndMethod(this));
            } else if ("writeObject".equals(getMethodName()) && "(Ljava/io/ObjectOutputStream;)V".equals(getMethodSig())
                    && isSerializable) {
                writeObjectIsSynchronized = true;
            } else {
                foundSynchronizedMethods = true;
            }
        }
        super.visit(obj);

    }

    boolean isSynthetic(FieldOrMethod obj) {
        Attribute[] a = obj.getAttributes();
        for (Attribute aA : a) {
            if (aA instanceof Synthetic) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void visit(Code obj) {
        if (isSerializable) {
            super.visit(obj);
        }
    }

    @Override
    public void sawOpcode(int seen) {
        if ("readExternal".equals(getMethodName())) {
            if ((seen == Const.IFEQ || seen == Const.IFNE || seen == Const.IFNULL || seen == Const.IFNONNULL) && isBranch(seen)) {
                initializedCheckerVariables.add(stack.getStackItem(0).getXField());
                initializeCheckerBranchTarget = getBranchTarget();
            } else if (seen == Const.ATHROW || isReturn(seen)) {
                sawReadExternalExit = true;
                if (getPC() < initializeCheckerBranchTarget) {
                    sawReadExternalBranchExit = true;
                }
            }
        }

        if (seen == Const.PUTFIELD) {
            XField xField = getXFieldOperand();
            if (xField != null && xField.getClassDescriptor().equals(getClassDescriptor())) {
                Item first = stack.getStackItem(0);

                boolean isPutOfDefaultValue = first.isNull(); // huh?? ||
                // first.isInitialParameter();
                if (!isPutOfDefaultValue && first.getConstant() != null && !first.isArray()) {
                    Object constant = first.getConstant();
                    if (constant instanceof Number && ((Number) constant).intValue() == 0 || constant.equals(Boolean.FALSE)) {
                        isPutOfDefaultValue = true;
                    }
                }

                if (isPutOfDefaultValue) {
                    if (Const.CONSTRUCTOR_NAME.equals(getMethodName())) {
                        transientFieldsSetToDefaultValueInConstructor.add(xField);
                    }
                } else {
                    String nameOfField = getNameConstantOperand();

                    if (transientFieldsUpdates.containsKey(xField)) {
                        if (Const.CONSTRUCTOR_NAME.equals(getMethodName())) {
                            transientFieldsSetInConstructor.add(xField);
                        } else {
                            transientFieldsUpdates.put(xField, transientFieldsUpdates.get(xField) + 1);
                        }
                    } else if (fieldsThatMightBeAProblem.containsKey(nameOfField)) {
                        try {

                            JavaClass classStored = first.getJavaClass();
                            if (classStored == null) {
                                return;
                            }
                            double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(classStored);
                            if (isSerializable <= 0.2) {
                                XField f = fieldsThatMightBeAProblem.get(nameOfField);

                                String sig = f.getSignature();
                                // System.out.println("Field signature: " +
                                // sig);
                                // System.out.println("Class stored: " +
                                // classStored.getClassName());
                                String genSig = "L" + ClassName.toSlashedClassName(classStored.getClassName()) + ";";
                                if (!sig.equals(genSig)) {
                                    double bias = 0.0;
                                    if (!Const.CONSTRUCTOR_NAME.equals(getMethodName())) {
                                        bias = 1.0;
                                    }
                                    int priority = computePriority(isSerializable, bias);

                                    fieldWarningList.add(new BugInstance(this, "SE_BAD_FIELD_STORE", priority)
                                            .addClass(getThisClass().getClassName()).addField(f).addType(genSig)
                                            .describe("TYPE_FOUND").addSourceLine(this));
                                }
                            }
                        } catch (ClassNotFoundException e) {
                            // ignore it
                        }
                    }

                    if ("readExternal".equals(getMethodName()) && !sawReadExternalBranchExit) {
                        BugInstance bug = new BugInstance(this, "SE_PREVENT_EXT_OBJ_OVERWRITE", LOW_PRIORITY)
                                .addClassAndMethod(this)
                                .addField(xField)
                                .addSourceLine(this);
                        // Collect the bugs and report them later, if the initializedCheckerVariable's value won't be changed
                        if (initializedCheckerVariables.contains(xField)) {
                            if (getPC() < initializeCheckerBranchTarget) {
                                optionalBugsInReadExternal.clear();
                            }
                        } else {
                            optionalBugsInReadExternal.put(xField, bug);
                        }
                        if (initializedCheckerVariables.isEmpty() || sawReadExternalExit) {
                            bugReporter.reportBug(bug);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void visit(Field obj) {
        if (isEnum || isRecord) {
            return;
        }

        int flags = obj.getAccessFlags();
        String genericSignature = obj.getGenericSignature();
        if (genericSignature != null && genericSignature.startsWith("T")) {
            return;
        }
        FieldSummary fieldSummary = AnalysisContext.currentAnalysisContext().getFieldSummary();
        Item summary = fieldSummary.getSummary(getXField());
        String fieldSig = summary.getSignature();

        if (isEjbImplClass) {
            ClassDescriptor fieldType = DescriptorFactory.createClassDescriptorFromFieldSignature(fieldSig);
            if (fieldType != null) {
                if (Subtypes2.instanceOf(fieldType, "javax.ejb.SessionContext")
                        || Subtypes2.instanceOf(fieldType, "jakarta.ejb.SessionContext")
                        || Subtypes2.instanceOf(fieldType, "javax.transaction.UserTransaction")
                        || Subtypes2.instanceOf(fieldType, "jakarta.transaction.UserTransaction")
                        || Subtypes2.instanceOf(fieldType, "javax.ejb.EJBHome")
                        || Subtypes2.instanceOf(fieldType, "jakarta.ejb.EJBHome")
                        || Subtypes2.instanceOf(fieldType, "javax.ejb.EJBObject")
                        || Subtypes2.instanceOf(fieldType, "jakarta.ejb.EJBObject")
                        || Subtypes2.instanceOf(fieldType, "javax.naming.Context")
                        || Subtypes2.instanceOf(fieldType, "jakarta.naming.Context")) {
                    if (testingEnabled && obj.isTransient()) {
                        bugReporter.reportBug(new BugInstance(this, "TESTING", NORMAL_PRIORITY).addClass(this)
                                .addVisitedField(this)
                                .addString("EJB implementation classes should not have fields of this type"));
                    }
                    return;
                }
            }
        }

        if (obj.isTransient()) {
            if (isSerializable && !isExternalizable) {
                seenTransientField = true;
                transientFieldsUpdates.put(getXField(), 0);
            } else if (reportTransientFieldOfNonSerializableClass) {
                bugReporter.reportBug(new BugInstance(this, "SE_TRANSIENT_FIELD_OF_NONSERIALIZABLE_CLASS", NORMAL_PRIORITY)
                        .addClass(this).addVisitedField(this));
            }
        } else if (getClassName().indexOf("ObjectStreamClass") == -1 && isSerializable && !isExternalizable
                && fieldSig.indexOf('L') >= 0 && !obj.isTransient() && !obj.isStatic()) {
            if (DEBUG) {
                System.out.println("Examining non-transient field with name: " + getFieldName() + ", sig: " + fieldSig);
            }
            XField xfield = getXField();
            Type type = TypeFrameModelingVisitor.getType(xfield);
            if (type instanceof ReferenceType) {
                try {
                    ReferenceType rtype = (ReferenceType) type;

                    double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(rtype);
                    if (DEBUG) {
                        System.out.println("  isSerializable: " + isSerializable);
                    }
                    if (isSerializable < 1.0) {
                        fieldsThatMightBeAProblem.put(obj.getName(), xfield);
                    }
                    if (isSerializable < 0.9) {
                        ReferenceType problemType = DeepSubtypeAnalysis.getLeastSerializableTypeComponent(rtype);

                        // Priority is LOW for GUI classes (unless explicitly marked
                        // Serializable),
                        // HIGH if the class directly implements Serializable,
                        // NORMAL otherwise.
                        int priority = computePriority(isSerializable, 0);
                        if (!strongEvidenceForIntendedSerialization()) {
                            if (obj.getName().startsWith("this$")) {
                                priority = Math.max(priority, NORMAL_PRIORITY);
                            }
                            if (innerClassHasOuterInstance) {
                                if (isAnonymousInnerClass) {
                                    priority += 2;
                                } else {
                                    priority += 1;
                                }
                            }
                            if (isGUIClass || isEjbImplClass || isJSPClass) {
                                priority++;
                            }
                        } else if (isGUIClass || isEjbImplClass || isJSPClass) {
                            priority = Math.max(priority, NORMAL_PRIORITY);
                        }
                        if (DEBUG) {
                            System.out.println("SE_BAD_FIELD: " + getThisClass().getClassName() + " " + obj.getName() + " "
                                    + isSerializable + " " + implementsSerializableDirectly + " " + sawSerialVersionUID + " "
                                    + isGUIClass + " " + isEjbImplClass);
                            // Report is queued until after the entire class has been
                            // seen.
                        }

                        if ("this$0".equals(obj.getName())) {
                            fieldWarningList.add(new BugInstance(this, "SE_BAD_FIELD_INNER_CLASS", priority).addClass(getThisClass()
                                    .getClassName()));
                        } else if (isSerializable < 0.9) {
                            fieldWarningList.add(new BugInstance(this, "SE_BAD_FIELD", priority)
                                    .addClass(getThisClass().getClassName())
                                    .addField(xfield).addType(problemType)
                                    .describe("TYPE_FOUND"));
                        }
                    } else if (!isGUIClass && !isEjbImplClass && !isJSPClass && "this$0".equals(obj.getName())) {
                        fieldWarningList.add(new BugInstance(this, "SE_INNER_CLASS", implementsSerializableDirectly ? NORMAL_PRIORITY
                                : LOW_PRIORITY).addClass(getThisClass().getClassName()));
                    }
                } catch (ClassNotFoundException e) {
                    if (DEBUG) {
                        System.out.println("Caught ClassNotFoundException");
                    }
                    bugReporter.reportMissingClass(e);
                }
            }
        }

        if (!getFieldName().startsWith("this") && isSynthetic(obj)) {
            foundSynthetic = true;
        }
        if (!"serialVersionUID".equals(getFieldName())) {
            return;
        }
        int mask = Const.ACC_STATIC | Const.ACC_FINAL;
        if (!"I".equals(fieldSig) && !"J".equals(fieldSig)) {
            return;
        }
        if ((flags & mask) == mask && "I".equals(fieldSig)) {
            bugReporter.reportBug(new BugInstance(this, "SE_NONLONG_SERIALVERSIONID", LOW_PRIORITY).addClass(this)
                    .addVisitedField(this));
            sawSerialVersionUID = true;
            return;
        } else if ((flags & Const.ACC_STATIC) == 0) {
            bugReporter.reportBug(new BugInstance(this, "SE_NONSTATIC_SERIALVERSIONID", NORMAL_PRIORITY).addClass(this)
                    .addVisitedField(this));
            return;
        } else if ((flags & Const.ACC_FINAL) == 0) {
            bugReporter.reportBug(new BugInstance(this, "SE_NONFINAL_SERIALVERSIONID", NORMAL_PRIORITY).addClass(this)
                    .addVisitedField(this));
            return;
        }
        sawSerialVersionUID = true;
    }

    private int computePriority(double isSerializable, double bias) {
        int priority = (int) (1.9 + isSerializable * 3 + bias);

        if (strongEvidenceForIntendedSerialization()) {
            priority--;
        } else if (sawSerialVersionUID && priority > NORMAL_PRIORITY) {
            priority--;
        } else {
            priority = Math.max(priority, NORMAL_PRIORITY);
        }
        return priority;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy