com.facebook.buck.tools.dxanalysis.MutabilityAnalyzer.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.tools.dxanalysis.MutabilityAnalyzer.java

Source

/*
 * Copyright 2014-present Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.facebook.buck.tools.dxanalysis;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.Nullable;

public class MutabilityAnalyzer {
    /**
     *  Deeply immutable "sorts" of types.  See {@link org.objectweb.asm.Type#getSort()}.
     */
    static final ImmutableSet<Integer> IMMUTABLE_TYPE_SORTS = ImmutableSet.of(Type.BOOLEAN, Type.BYTE, Type.CHAR,
            Type.DOUBLE, Type.FLOAT, Type.INT, Type.LONG, Type.SHORT);

    /**
     * External classes that are final and deeply immutable.
     */
    private static final ImmutableSet<String> EXTERNAL_IMMUTABLE_CLASSES = ImmutableSet.of("java/lang/String",
            "java/lang/Integer");

    /**
     * NonExternal classes that are non-final but have no inherent mutability.
     */
    public static final ImmutableSet<String> EXTERNAL_IMMUTABLE_BASE_CLASSES = ImmutableSet.of("java/lang/Object",
            "java/lang/Enum");

    /**
     * All classes under analysis.
     */
    private final ImmutableMap<String, ClassNode> allClasses;

    /**
     * Any class that is definitely mutable.  All subclasses become mutable as well.
     * Superclasses are marked has having mutable descendents.
     */
    private final Set<String> trulyMutableClasses;

    /**
     * Classes that have truly mutable descendents.  Having a field of one of these types
     * makes you truly mutable, but extending one does not.
     */
    private final Set<String> classesWithMutableDescendents;

    /**
     * Only set once in go().
     */
    @Nullable
    private ImmutableSet<String> immutableClasses;

    /**
     * True iff we made progress during this round.
     */
    private boolean madeProgress;

    /**
     * Log of messages.
     */
    private List<String> log = new ArrayList<>();

    public static MutabilityAnalyzer analyze(ImmutableMap<String, ClassNode> allClasses) {
        MutabilityAnalyzer analyzer = new MutabilityAnalyzer(allClasses);
        analyzer.go();
        return analyzer;
    }

    private MutabilityAnalyzer(ImmutableMap<String, ClassNode> allClasses) {
        this.allClasses = allClasses;
        trulyMutableClasses = new HashSet<>();
        classesWithMutableDescendents = new HashSet<>();
    }

    public ImmutableList<String> getLog() {
        return ImmutableList.copyOf(log);
    }

    public ImmutableSet<String> getImmutableClasses() {
        return immutableClasses;
    }

    public boolean isTypeImmutable(String desc) {
        Type type = Type.getType(desc);
        if (IMMUTABLE_TYPE_SORTS.contains(type.getSort())) {
            return true;
        }
        if (type.getSort() != Type.OBJECT) {
            return false;
        }
        if (Sets.union(EXTERNAL_IMMUTABLE_CLASSES, immutableClasses).contains(type.getInternalName())) {
            return true;
        }
        return false;
    }

    private void go() {
        while (true) {
            madeProgress = false;
            for (ClassNode klass : allClasses.values()) {
                analyzeClass(klass);
            }
            if (!madeProgress) {
                break;
            }
        }

        immutableClasses = ImmutableSet.copyOf(Sets.difference(allClasses.keySet(),
                Sets.union(trulyMutableClasses, classesWithMutableDescendents)));
    }

    private void markClassTrulyMutable(String className) {
        if (trulyMutableClasses.contains(className)) {
            return;
        }
        if (!allClasses.containsKey(className)) {
            throw new RuntimeException("Tried to mark external class '" + className + "' truly mutable");
        }

        trulyMutableClasses.add(className);
        madeProgress = true;

        markClassAsHavingMutableDescendents(className);
    }

    private void markClassAsHavingMutableDescendents(String className) {
        if (classesWithMutableDescendents.contains(className)) {
            return;
        }
        ClassNode klass = allClasses.get(className);
        if (klass == null) {
            return;
        }

        classesWithMutableDescendents.add(className);
        madeProgress = true;

        markClassAsHavingMutableDescendents(klass.superName);
        for (String iface : klass.interfaces) {
            markClassAsHavingMutableDescendents(iface);
        }
    }

    private boolean superClassIsDefinitelyMutable(String superClassName) {
        if (trulyMutableClasses.contains(superClassName)) {
            return true;
        }
        if (EXTERNAL_IMMUTABLE_BASE_CLASSES.contains(superClassName)) {
            return false;
        }
        if (!allClasses.containsKey(superClassName)) {
            // All external classes are assumed to be mutable unless whitelisted.
            return true;
        }
        // Assume internal classes are immutable until proven otherwise (in later rounds).
        return false;
    }

    private boolean classIsDefinitelyMutable(ClassNode klass) {
        if (superClassIsDefinitelyMutable(klass.superName)) {
            log.add("Mutable parent: " + klass.name + " < " + klass.superName);
            return true;
        }

        for (FieldNode field : klass.fields) {
            if ((field.access & Opcodes.ACC_STATIC) != 0) {
                continue;
            }
            if ((field.access & Opcodes.ACC_FINAL) == 0) {
                log.add("Non-final field: " + klass.name + "#" + field.name + ":" + field.desc);
                return true;
            }
            if (field.name.contains("$")) {
                // Generated fields are assumed to be effectively immutable.
                // This could, in principle, miss an issue like a static reference to a
                // seemingly-immutable inner class object that maintains a hidden reference
                // to its mutable outer object, but that seems unlikely.
                continue;
            }

            Type type = Type.getType(field.desc);

            if (IMMUTABLE_TYPE_SORTS.contains(type.getSort())) {
                continue;
            }

            if (type.getSort() != Type.OBJECT) {
                log.add("Odd sort: " + klass.name + "#" + field.name + ":" + field.desc);
                return true;
            }

            if (allClasses.keySet().contains(type.getInternalName())) {
                if (classesWithMutableDescendents.contains(type.getInternalName())) {
                    log.add("Internal mutable field: " + klass.name + "#" + field.name + ":" + field.desc);
                    return true;
                }
            } else {
                if (!EXTERNAL_IMMUTABLE_CLASSES.contains(type.getInternalName())) {
                    log.add("External mutable field: " + klass.name + "#" + field.name + ":" + field.desc);
                    return true;
                }
            }
        }

        return false;
    }

    private void analyzeClass(ClassNode klass) {
        if (trulyMutableClasses.contains(klass.name)) {
            return;
        }
        if (classIsDefinitelyMutable(klass)) {
            markClassTrulyMutable(klass.name);
        }
    }

}