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

src.main.java.com.mebigfatguy.fbcontrib.detect.Section508Compliance Maven / Gradle / Ivy

Go to download

An auxiliary findbugs.sourceforge.net plugin for java bug detectors that fall outside the narrow scope of detectors to be packaged with the product itself.

There is a newer version: 7.6.8
Show newest version
/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2018 Dave Brosius
 *
 * 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 com.mebigfatguy.fbcontrib.detect;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;

import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.FQMethod;
import com.mebigfatguy.fbcontrib.utils.OpcodeUtils;
import com.mebigfatguy.fbcontrib.utils.RegisterUtils;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.TernaryPatcher;
import com.mebigfatguy.fbcontrib.utils.Values;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.FieldAnnotation;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;

/**
 * looks for interfaces that ignore 508 compliance, including not using JLabel.setLabelFor, Using null layouts,
 */
@CustomUserValue
public class Section508Compliance extends BytecodeScanningDetector {

    private enum S508UserValue {
        SAW_TEXT_LABEL, FROM_UIMANAGER, APPENDED_STRING
    };

    private static JavaClass windowClass;
    private static JavaClass componentClass;
    private static JavaClass jcomponentClass;
    private static JavaClass accessibleClass;
    private static ClassNotFoundException clsNFException;

    static {
        try {
            windowClass = Repository.lookupClass("java/awt/Window");
        } catch (ClassNotFoundException cnfe) {
            windowClass = null;
            clsNFException = cnfe;
        }
        try {
            componentClass = Repository.lookupClass("java/awt/Component");
        } catch (ClassNotFoundException cnfe) {
            componentClass = null;
            clsNFException = cnfe;
        }
        try {
            jcomponentClass = Repository.lookupClass("javax/swing/JComponent");
        } catch (ClassNotFoundException cnfe) {
            jcomponentClass = null;
            clsNFException = cnfe;
        }
        try {
            accessibleClass = Repository.lookupClass("javax/accessibility/Accessible");
        } catch (ClassNotFoundException cnfe) {
            accessibleClass = null;
            clsNFException = cnfe;
        }
    }

    private static final Map displayTextMethods = new HashMap<>();

    static {
        String awtDialog = "java/awt/Dialog";
        String awtFrame = "java/awt/Frame";
        String awtGraphics = "java/awt/GraphicsConfiguration";
        String swingIcon = "javax/swing/Icon";
        displayTextMethods.put(new FQMethod("javax/swing/JLabel", Values.CONSTRUCTOR, SignatureBuilder.SIG_STRING_TO_VOID), Values.ZERO);
        displayTextMethods.put(new FQMethod("javax/swing/JLabel", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING, swingIcon, Values.SIG_PRIMITIVE_INT).toString()), Values.ONE);
        displayTextMethods.put(new FQMethod("javax/swing/JLabel", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING, Values.SIG_PRIMITIVE_INT).toString()), Values.TWO);
        displayTextMethods.put(new FQMethod("javax/swing/JButton", Values.CONSTRUCTOR, SignatureBuilder.SIG_STRING_TO_VOID), Values.ZERO);
        displayTextMethods.put(new FQMethod("javax/swing/JButton", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING, swingIcon).toString()), Values.ONE);
        displayTextMethods.put(new FQMethod("javax/swing/JFrame", Values.CONSTRUCTOR, SignatureBuilder.SIG_STRING_TO_VOID), Values.ZERO);
        displayTextMethods.put(new FQMethod("javax/swing/JFrame", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING, awtGraphics).toString()), Values.ONE);
        displayTextMethods.put(new FQMethod("javax/swing/JDialog", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(awtDialog, Values.SLASHED_JAVA_LANG_STRING).toString()), Values.ZERO);
        displayTextMethods.put(
                new FQMethod("javax/swing/JDialog", Values.CONSTRUCTOR,
                        new SignatureBuilder().withParamTypes(awtDialog, Values.SLASHED_JAVA_LANG_STRING, Values.SIG_PRIMITIVE_BOOLEAN).toString()),
                Values.ONE);
        displayTextMethods.put(new FQMethod("javax/swing/JDialog", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(awtDialog, Values.SLASHED_JAVA_LANG_STRING, Values.SIG_PRIMITIVE_BOOLEAN, awtGraphics).toString()),
                Values.TWO);
        displayTextMethods.put(new FQMethod("javax/swing/JDialog", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(awtFrame, Values.SLASHED_JAVA_LANG_STRING).toString()), Values.ZERO);
        displayTextMethods.put(new FQMethod("javax/swing/JDialog", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(awtFrame, Values.SLASHED_JAVA_LANG_STRING, Values.SIG_PRIMITIVE_BOOLEAN).toString()), Values.ONE);
        displayTextMethods.put(
                new FQMethod("javax/swing/JDialog", Values.CONSTRUCTOR,
                        new SignatureBuilder().withParamTypes(awtFrame, Values.SLASHED_JAVA_LANG_STRING, Values.SIG_PRIMITIVE_BOOLEAN, awtGraphics).toString()),
                Values.TWO);
        displayTextMethods.put(new FQMethod(awtDialog, "setTitle", SignatureBuilder.SIG_STRING_TO_VOID), Values.ZERO);
        displayTextMethods.put(new FQMethod(awtFrame, "setTitle", SignatureBuilder.SIG_STRING_TO_VOID), Values.ZERO);
        displayTextMethods.put(new FQMethod("javax/swing/JMenu", Values.CONSTRUCTOR, SignatureBuilder.SIG_STRING_TO_VOID), Values.ZERO);
        displayTextMethods.put(new FQMethod("javax/swing/JMenu", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING, Values.SIG_PRIMITIVE_BOOLEAN).toString()), Values.ONE);
        displayTextMethods.put(new FQMethod("javax/swing/JMenuItem", Values.CONSTRUCTOR, SignatureBuilder.SIG_STRING_TO_VOID), Values.ZERO);
        displayTextMethods.put(new FQMethod("javax/swing/JMenuItem", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING, swingIcon).toString()), Values.ONE);
        displayTextMethods.put(new FQMethod("javax/swing/JMenuItem", Values.CONSTRUCTOR,
                new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING, Values.SIG_PRIMITIVE_INT).toString()), Values.ONE);
    }

    private final BugReporter bugReporter;
    private OpcodeStack stack;
    private Set fieldLabels;
    private Map localLabels;

    /**
     * constructs a S508C detector given the reporter to report bugs on
     *
     * @param bugReporter
     *            the sync of bug reports
     */
    public Section508Compliance(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
        if (clsNFException != null) {
            bugReporter.reportMissingClass(clsNFException);
        }
    }

    /**
     * implements the visitor to create and clear the stack
     *
     * @param classContext
     *            the context object of the currently visited class
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            if ((jcomponentClass != null) && (accessibleClass != null)) {
                JavaClass cls = classContext.getJavaClass();
                if (cls.instanceOf(jcomponentClass) && !cls.implementationOf(accessibleClass)) {
                    bugReporter.reportBug(new BugInstance(this, BugType.S508C_NON_ACCESSIBLE_JCOMPONENT.name(), NORMAL_PRIORITY).addClass(cls));
                }
            }

            stack = new OpcodeStack();
            fieldLabels = new HashSet<>();
            localLabels = new HashMap<>();
            super.visitClassContext(classContext);
            for (XField fa : fieldLabels) {
                bugReporter.reportBug(new BugInstance(this, BugType.S508C_NO_SETLABELFOR.name(), NORMAL_PRIORITY).addClass(this).addField(fa));
            }
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
        } finally {
            stack = null;
            fieldLabels = null;
            localLabels = null;
        }
    }

    /**
     * looks for fields that are JLabels and stores them in a set
     *
     * @param obj
     *            the field object of the current field
     */
    @Override
    public void visitField(Field obj) {
        String fieldSig = obj.getSignature();
        if ("Ljavax/swing/JLabel;".equals(fieldSig)) {
            FieldAnnotation fa = FieldAnnotation.fromVisitedField(this);

            fieldLabels.add(XFactory.createXField(fa));
        }
    }

    /**
     * implements the visitor to reset the stack
     *
     * @param obj
     *            the context object for the currently visited code block
     */
    @Override
    public void visitCode(Code obj) {
        stack.resetForMethodEntry(this);
        localLabels.clear();
        super.visitCode(obj);
        for (SourceLineAnnotation sla : localLabels.values()) {
            BugInstance bug = new BugInstance(this, BugType.S508C_NO_SETLABELFOR.name(), NORMAL_PRIORITY).addClass(this).addMethod(this);

            if (sla != null) {
                bug.addSourceLine(sla);
            }

            bugReporter.reportBug(bug);
        }
    }

    /**
     * implements the visitor to find 508 compliance concerns
     *
     * @param seen
     *            the opcode of the currently parsed instruction
     */
    @Override
    public void sawOpcode(int seen) {
        boolean sawTextLabel = false;
        boolean sawUIManager = false;
        boolean sawAppend = false;
        try {
            stack.precomputation(this);

            if (OpcodeUtils.isAStore(seen)) {
                if (stack.getStackDepth() > 0) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    if ("Ljavax/swing/JLabel;".equals(item.getSignature()) && (S508UserValue.SAW_TEXT_LABEL == item.getUserValue())) {
                        int reg = RegisterUtils.getAStoreReg(this, seen);
                        localLabels.put(Integer.valueOf(reg), SourceLineAnnotation.fromVisitedInstruction(this));
                    }
                }
            } else if (seen == PUTFIELD) {
                if (stack.getStackDepth() > 0) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    if (S508UserValue.SAW_TEXT_LABEL != item.getUserValue()) {
                        FieldAnnotation fa = new FieldAnnotation(getDottedClassName(), getNameConstantOperand(), getSigConstantOperand(), false);
                        fieldLabels.remove(XFactory.createXField(fa));
                    }
                }
            } else if (seen == INVOKESPECIAL) {
                String className = getClassConstantOperand();
                String methodName = getNameConstantOperand();
                if ("javax/swing/JLabel".equals(className) && Values.CONSTRUCTOR.equals(methodName)) {
                    String signature = getSigConstantOperand();
                    if (signature.indexOf(Values.SIG_JAVA_LANG_STRING) >= 0) {
                        sawTextLabel = true;
                    }
                }
            } else if (seen == INVOKEVIRTUAL) {
                String className = getClassConstantOperand();
                String methodName = getNameConstantOperand();

                if ("javax/swing/JLabel".equals(className)) {
                    if ("setLabelFor".equals(methodName) && (stack.getStackDepth() > 1)) {
                        OpcodeStack.Item item = stack.getStackItem(1);
                        XField field = item.getXField();
                        if (field != null) {
                            fieldLabels.remove(field);
                        } else {
                            int reg = item.getRegisterNumber();
                            if (reg >= 0) {
                                localLabels.remove(Integer.valueOf(reg));
                            }
                        }
                    }
                } else if (SignatureUtils.isPlainStringConvertableClass(className)) {
                    if ("append".equals(methodName)) {
                        if (stack.getStackDepth() > 0) {
                            OpcodeStack.Item item = stack.getStackItem(0);
                            Object con = item.getConstant();
                            if (con instanceof String) {
                                String literal = (String) con;
                                sawAppend = !literal.startsWith("<");
                            } else {
                                sawAppend = true;
                            }
                        }
                    } else if (Values.TOSTRING.equals(methodName) && (stack.getStackDepth() > 0)) {
                        OpcodeStack.Item item = stack.getStackItem(0);
                        if (S508UserValue.APPENDED_STRING == item.getUserValue()) {
                            sawAppend = true;
                        }
                    }
                }

                processSetSizeOps(methodName);
                processNullLayouts(className, methodName);
                processSetColorOps(methodName);
            } else if ((seen == INVOKESTATIC) && "javax/swing/UIManager".equals(getClassConstantOperand())) {
                sawUIManager = true;
            }

            if ((seen == INVOKEVIRTUAL) || (seen == INVOKESPECIAL) || (seen == INVOKEINTERFACE)) {
                processFaultyGuiStrings();
            }
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
        } finally {
            TernaryPatcher.pre(stack, seen);
            stack.sawOpcode(this, seen);
            TernaryPatcher.post(stack, seen);
            if (sawTextLabel) {
                if (stack.getStackDepth() > 0) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    item.setUserValue(S508UserValue.SAW_TEXT_LABEL);
                }
            } else if (sawUIManager) {
                if (stack.getStackDepth() > 0) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    item.setUserValue(S508UserValue.FROM_UIMANAGER);
                }
            } else if (sawAppend && (stack.getStackDepth() > 0)) {
                OpcodeStack.Item item = stack.getStackItem(0);
                item.setUserValue(S508UserValue.APPENDED_STRING);
            }
        }
    }

    /**
     * looks for calls to set a readable string that is generated from a static constant, as these strings are not translatable. also looks for setting readable
     * strings that are appended together. This is likely not to be internationalizable.
     */
    private void processFaultyGuiStrings() {
        FQMethod methodInfo = new FQMethod(getClassConstantOperand(), getNameConstantOperand(), getSigConstantOperand());
        Integer parmIndex = displayTextMethods.get(methodInfo);
        if ((parmIndex != null) && (stack.getStackDepth() > parmIndex.intValue())) {
            OpcodeStack.Item item = stack.getStackItem(parmIndex.intValue());
            if (item.getConstant() != null) {
                bugReporter.reportBug(new BugInstance(this, BugType.S508C_NON_TRANSLATABLE_STRING.name(), NORMAL_PRIORITY).addClass(this).addMethod(this)
                        .addSourceLine(this));
            } else if (S508UserValue.APPENDED_STRING == item.getUserValue()) {
                bugReporter.reportBug(
                        new BugInstance(this, BugType.S508C_APPENDED_STRING.name(), NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));

            }
        }
    }

    /**
     * looks for containers where a null layout is installed
     *
     * @param className
     *            class that a method call is made on
     * @param methodName
     *            name of the method that is called
     */
    private void processNullLayouts(String className, String methodName) {
        if ("java/awt/Container".equals(className) && "setLayout".equals(methodName) && (stack.getStackDepth() > 0) && stack.getStackItem(0).isNull()) {
            bugReporter.reportBug(new BugInstance(this, BugType.S508C_NULL_LAYOUT.name(), NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
        }
    }

    /**
     * looks for calls to set the color of components where the color isn't from UIManager
     *
     * @param methodName
     *            the method that is called
     *
     * @throws ClassNotFoundException
     *             if the gui component class can't be found
     */
    private void processSetColorOps(String methodName) throws ClassNotFoundException {
        if ("setBackground".equals(methodName) || "setForeground".equals(methodName)) {
            int argCount = SignatureUtils.getNumParameters(getSigConstantOperand());
            if (stack.getStackDepth() > argCount) {
                OpcodeStack.Item item = stack.getStackItem(0);
                if (S508UserValue.FROM_UIMANAGER != item.getUserValue()) {
                    item = stack.getStackItem(argCount);
                    JavaClass cls = item.getJavaClass();
                    if (((jcomponentClass != null) && cls.instanceOf(jcomponentClass)) || ((componentClass != null) && cls.instanceOf(componentClass))) {
                        bugReporter.reportBug(
                                new BugInstance(this, BugType.S508C_SET_COMP_COLOR.name(), NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
                    }
                }
            }
        }
    }

    /**
     * looks for calls to setSize on components, rather than letting the layout manager set them
     *
     * @param methodName
     *            the method that was called on a component
     *
     * @throws ClassNotFoundException
     *             if the gui class wasn't found
     */
    private void processSetSizeOps(String methodName) throws ClassNotFoundException {
        if ("setSize".equals(methodName)) {
            int argCount = SignatureUtils.getNumParameters(getSigConstantOperand());
            if ((windowClass != null) && (stack.getStackDepth() > argCount)) {
                OpcodeStack.Item item = stack.getStackItem(argCount);
                JavaClass cls = item.getJavaClass();
                if ((cls != null) && cls.instanceOf(windowClass)) {
                    bugReporter.reportBug(
                            new BugInstance(this, BugType.S508C_NO_SETSIZE.name(), NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy