Package joust.analysers.sideeffects

Source Code of joust.analysers.sideeffects.SideEffectVisitor

package joust.analysers.sideeffects;

import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.util.List;
import joust.joustcache.JOUSTCache;
import joust.joustcache.data.ClassInfo;
import joust.tree.annotatedtree.AJCForest;
import joust.tree.annotatedtree.AJCTreeVisitor;
import joust.tree.annotatedtree.treeinfo.EffectSet;
import joust.tree.annotatedtree.treeinfo.TreeInfoManager;
import joust.utils.data.SymbolSet;
import joust.utils.logging.LogUtils;
import joust.utils.data.SetHashMap;
import lombok.experimental.ExtensionMethod;
import lombok.extern.java.Log;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import static joust.tree.annotatedtree.AJCTree.*;
import static com.sun.tools.javac.code.Symbol.*;
import static joust.tree.annotatedtree.treeinfo.EffectSet.*;
import static joust.utils.compiler.StaticCompilerUtils.types;

@Log
@ExtensionMethod({Logger.class, LogUtils.LogExtensions.class})
public class SideEffectVisitor extends AJCTreeVisitor {
    private boolean saveResults;

    // Track the method calls which depend on incomplete methods so we can go back and fix them up when we complete
    // the method in question. Keyed by MethodSymbol of the incomplete method.
    private final SetHashMap<MethodSymbol, AJCEffectAnnotatedTree> incompleteCalls = new SetHashMap<MethodSymbol, AJCEffectAnnotatedTree>();

    // The incomplete Effects objects for methods that have unresolved deps.
    private final HashMap<MethodSymbol, Effects> unfinishedMethodEffects = new HashMap<MethodSymbol, Effects>();

    private final HashSet<MethodSymbol> calledMethodsWithoutSource = new HashSet<MethodSymbol>();

    // The set of methods each method depends upon.
    private final SetHashMap<MethodSymbol, MethodSymbol> methodDeps = new SetHashMap<MethodSymbol, MethodSymbol>();

    // The set of methods depended on by each method.
    private final SetHashMap<MethodSymbol, MethodSymbol> reverseMethodDeps = new SetHashMap<MethodSymbol, MethodSymbol>();

    // The set of methods with a particular name - the set we check to see if an inheritance exists whenever
    // we see a new symbol.
    private final SetHashMap<String, MethodSymbol> possibleInheitors = new SetHashMap<String, MethodSymbol>();

    private MethodSymbol methodBeingVisited;

    public SideEffectVisitor(boolean shouldSave) {
        saveResults = shouldSave;
    }

    @Override
    public void visitMethodDef(AJCMethodDecl that) {
        MethodSymbol sym = that.getTargetSymbol();
        methodBeingVisited = sym;

        log.debug("Visiting method: {}:{}", sym, sym.owner);

        // Create the sets of filtered methods that might inherit from each other.
        // Constructors are omitted, since calls to super() or this() are explicit in the AST of all constructors
        // at this point, so the proper deps will already be present.
        if (!"<init>".equals(sym.toString())) {
            possibleInheitors.listAdd(sym.toString(), sym);
        }

        // Bail out early for abstracts - they just inherit the effects of their implementations, provided
        // they have one.
        if ((sym.flags() & Flags.ABSTRACT) != 0) {
            Effects placeholderEffects = new Effects(NO_EFFECTS, NO_EFFECTS);

            unfinishedMethodEffects.put(sym, placeholderEffects);

            return;
        }

        if ((sym.flags() & Flags.NATIVE) != 0) {
            // Ha. No.
            unfinishedMethodEffects.put(sym, new Effects(ALL_EFFECTS, ALL_EFFECTS));
            methodDeps.ensure(sym);
            return;
        }

        super.visitMethodDef(that);

        unfinishedMethodEffects.put(sym, that.body.effects);
        methodDeps.ensure(sym);
    }

    /**
     * Check all possible overrides for this method and add dependenies as needed.
     */
    private void ensureInheritorLinks() {
        log.debug("Resolving polymorphic effect dependencies...");
        Set<String> inheritorKeys = possibleInheitors.keySet();
        for (String symName : inheritorKeys) {
            Set<MethodSymbol> inheritorSet = possibleInheitors.get(symName);

            // Nothing to do...
            if (inheritorSet.size() < 2) {
                continue;
            }

            log.debug("Checking inheritor links for {}", symName);
            log.debug("Inheritor set: {}", Arrays.toString(inheritorSet.toArray()));

            for (MethodSymbol s : inheritorSet) {
                for (MethodSymbol s2 : inheritorSet) {
                    if (s == s2) {
                        continue;
                    }

                    if (s.overrides(s2, (TypeSymbol) s.owner, types, true)) {
                        log.debug("{}:{} overrides {}:{}!", s, s.owner, s2, s2.owner);

                        Effects e1 = unfinishedMethodEffects.get(s);
                        Effects e2 = unfinishedMethodEffects.get(s2);

                        // s overrides s2, so create a dependency link from s2 to s.
                        e2.deps.add(e1);
                        e1.dependantOnThis.add(e2);

                        // And mark it for resolution...
                        methodDeps.listAdd(s2, s);
                        reverseMethodDeps.listAdd(s, s2);
                    }
                }
            }
        }
    }

    /**
     * After visiting all input nodes, finalise the initial effect tree, ready to handle future incremental
     * updates.
     * It is at this step that cyclic dependencies need to be explicitly resolved.
     */
    public void bootstrap() {
        // Create the missing dependencies, due to polymorphism, between methods...
        ensureInheritorLinks();

        log.debug("Commencing effect dependency resolution.");
        // Ask the cache for information on all called methods that aren't in the method table. These
        // can be resolved right away.
        Set<MethodSymbol> knownSymbols = AJCForest.getInstance().methodTable.keySet();

        log.debug("Keyset: {}", Arrays.toString(methodDeps.keySet().toArray()));
        log.debug("mTable Keyset: {}", Arrays.toString(AJCForest.getInstance().methodTable.keySet().toArray()));
        calledMethodsWithoutSource.removeAll(knownSymbols);
        log.debug("calledCopy: {}", Arrays.toString(calledMethodsWithoutSource.toArray()));

        // Only load from the cache the first time we run the analyser in a particular session.
        if (saveResults) {
            for (MethodSymbol mSym : calledMethodsWithoutSource) {
                log.debug("Loading effects for {} from cache... (Owner is {})", mSym, mSym.enclClass());
                JOUSTCache.loadCachedInfoForClass(mSym.enclClass());
            }
        }

        // Now everything we need is loaded, let's start completing methods. Firstly the easy ones...
        for (MethodSymbol mSym : calledMethodsWithoutSource) {
            methodCompleted(mSym, TreeInfoManager.getEffectsForMethod(mSym));
        }

        // Dump the deps...
        for (MethodSymbol sym : methodDeps.keySet()) {
            log.debug("{}:{} depends on: {}", sym, sym.owner, Arrays.toString(methodDeps.get(sym).toArray()));
        }

        for (MethodSymbol sym : reverseMethodDeps.keySet()) {
            log.debug("{}:{} is required by: {}", sym, sym.owner, Arrays.toString(reverseMethodDeps.get(sym).toArray()));
        }

        log.debug("Unfinished: {}", Arrays.toString(unfinishedMethodEffects.keySet().toArray()));

        // As long as there are no cycles in the graph, this will resolve absolutely everything.
        while (resolveMethodsWithoutDeps());

        if (methodDeps.keySet().isEmpty()) {
            return;
        }

        log.debug("After simple resolution have these symbols outstanding: {}", Arrays.toString(methodDeps.keySet().toArray()));

        // Set up unresolved call deps..
        for (MethodSymbol sym : incompleteCalls.keySet()) {
            Set<AJCEffectAnnotatedTree> callsForSym = incompleteCalls.get(sym);
            for (AJCEffectAnnotatedTree tree : callsForSym) {
                Effects callEffects = tree.effects;
                Effects targetEffects = unfinishedMethodEffects.get(sym);

                if (targetEffects == null) {
                    continue;
                }

                callEffects.deps.add(targetEffects);
                targetEffects.dependantOnThis.add(callEffects);
            }
        }

        Set<MethodSymbol> missing = new HashSet<MethodSymbol>(methodDeps.keySet());

        // Resolve cycles!
        for (MethodSymbol sym : missing) {
            // Handle concurrent modification.
            if (!methodDeps.containsKey(sym)) {
                continue;
            }

            log.debug("Cycle breaking for: {}", missing);
            Effects missingEff = unfinishedMethodEffects.get(sym);
            log.debug("Before: {}", missingEff);
            missingEff.rebuildFromChildren();
            log.debug("After: {}", missingEff);
            methodCompleted(sym, missingEff);
        }
    }

    /**
     * Find all methods that have no outstanding deps and complete them.
     * @return true if any methods were completed.
     */
    private boolean resolveMethodsWithoutDeps() {
        boolean success = false;

        Set<MethodSymbol> keyCopy = new HashSet<MethodSymbol>(methodDeps.keySet());

        for (MethodSymbol sym : keyCopy) {
            // Concurrent modification may occur...
            if (!methodDeps.containsKey(sym)) {
                continue;
            }

            log.debug("{}:{} has deps: {}", sym, sym.owner, methodDeps.get(sym));
            if (methodDeps.get(sym).isEmpty()) {
                methodCompleted(sym, unfinishedMethodEffects.get(sym));
                unfinishedMethodEffects.remove(sym);
                success = true;
            }
        }

        return success;
    }

    /**
     * Called when the effect set for a method becomes available.
     * Used to update all incomplete nodes that rely on this method.
     */
    private void methodCompleted(MethodSymbol completedSym, Effects effects) {
        log.debug("{}:{} completed with {}", completedSym, completedSym.owner, effects);
        methodDeps.remove(completedSym);
        TreeInfoManager.registerMethodEffects(completedSym, effects, saveResults);
        unfinishedMethodEffects.remove(completedSym);

        Set<AJCEffectAnnotatedTree> incompleted = incompleteCalls.get(completedSym);
        if (incompleted != null) {
            for (AJCEffectAnnotatedTree t : incompleted) {
                Effects tEffects = t.effects;
                EffectSet newEffectSet = tEffects.effectSet.union(effects.effectSet);

                // Update dependencies...
                tEffects.deps.add(effects);
                effects.dependantOnThis.add(tEffects);

                log.debug("Setting effects on: {} to: {}", t, newEffectSet);
                tEffects.setEffectSet(newEffectSet);
            }

            incompleteCalls.remove(completedSym);
        }

        // Determine if this completes any methods. If so, recurse!
        Set<MethodSymbol> requireThis = reverseMethodDeps.get(completedSym);
        if (requireThis != null) {
            log.debug("Reverse deps: {}", Arrays.toString(requireThis.toArray()));
            for (MethodSymbol t : requireThis) {
                Set<MethodSymbol> thingsRequiredByT = methodDeps.get(t);
                if (thingsRequiredByT == null) {
                    continue;
                }
                log.debug("t: {}, Required by t: {}", t, thingsRequiredByT);
                thingsRequiredByT.remove(completedSym);

                if (thingsRequiredByT.isEmpty()) {
                    log.debug("Cascading completion of: {}", t, t.owner);
                    methodCompleted(t, unfinishedMethodEffects.get(t));
                    methodDeps.remove(t);
                }
            }

            reverseMethodDeps.remove(completedSym);
        } else {
            log.debug("Reverse deps: []");
        }
    }

    @Override
    public void visitSkip(AJCSkip that) {
        super.visitSkip(that);
        that.effects = new Effects(NO_EFFECTS);
    }

    @Override
    public void visitDoWhileLoop(AJCDoWhileLoop that) {
        super.visitDoWhileLoop(that);

        // The effect set of a do-while loop is the union of its condition with its body.
        that.effects = Effects.unionTrees(that.cond, that.body);
    }

    @Override
    public void visitBlock(AJCBlock that) {
        super.visitBlock(that);

        that.effects = Effects.unionTrees(that.stats);
    }

    @Override
    public void visitWhileLoop(AJCWhileLoop that) {
        super.visitWhileLoop(that);

        // The effect set of a while loop is the union of its condition with its body.
        that.effects = Effects.unionTrees(that.cond, that.body);
    }

    @Override
    public void visitForLoop(AJCForLoop that) {
        super.visitForLoop(that);

        // The effect set of a while loop is the union of its condition with its body.
        Effects initEffects = Effects.unionTrees(that.init);
        Effects stepEffects = Effects.unionTrees(that.step);

        that.effects = Effects.unionOf(initEffects, stepEffects, Effects.unionTrees(that.cond, that.body));
    }

    @Override
    public void visitLabelledStatement(AJCLabeledStatement that) {
        super.visitLabelledStatement(that);

        that.effects = Effects.unionOf(that.body.effects);
    }

    @Override
    public void visitSwitch(AJCSwitch that) {
        super.visitSwitch(that);

        Effects caseEffects = Effects.unionTrees(that.cases);
        that.effects = Effects.unionOf(caseEffects, that.selector.effects);
    }

    @Override
    public void visitCase(AJCCase that) {
        super.visitCase(that);

        Effects bodyEffects = Effects.unionTrees(that.stats);
        that.effects = Effects.unionOf(bodyEffects, that.pat.effects);
    }

    @Override
    public void visitSynchronized(AJCSynchronized that) {
        super.visitSynchronized(that);

        that.effects = Effects.unionTrees(that.lock, that.body);
    }

    @Override
    public void visitTry(AJCTry that) {
        super.visitTry(that);

        Effects catcherEffects = Effects.unionTrees(that.catchers);
        that.effects = Effects.unionOf(catcherEffects, that.body.effects, that.finalizer.effects);
    }

    @Override
    public void visitCatch(AJCCatch that) {
        super.visitCatch(that);

        that.effects = Effects.unionOf(that.body.effects);
    }

    @Override
    public void visitConditional(AJCConditional that) {
        super.visitConditional(that);

        that.effects = Effects.unionTrees(that.cond, that.truepart, that.falsepart);
    }

    @Override
    public void visitIf(AJCIf that) {
        super.visitIf(that);

        that.effects = Effects.unionTrees(that.cond, that.thenpart, that.elsepart);
    }

    @Override
    public void visitExpressionStatement(AJCExpressionStatement that) {
        super.visitExpressionStatement(that);

        that.effects = Effects.unionOf(that.expr.effects);
    }

    @Override
    public void visitBreak(AJCBreak that) {
        super.visitBreak(that);

        that.effects = new Effects(NO_EFFECTS);
    }

    @Override
    public void visitContinue(AJCContinue that) {
        super.visitContinue(that);

        that.effects = new Effects(NO_EFFECTS);
    }

    @Override
    public void visitReturn(AJCReturn that) {
        super.visitReturn(that);

        that.effects = Effects.unionOf(that.expr.effects);
    }

    @Override
    public void visitThrow(AJCThrow that) {
        super.visitThrow(that);

        that.effects = Effects.unionWithDirect(new EffectSet(EffectType.EXCEPTION), that.expr.effects);
    }

    /**
     * In this, the first stage of the bootstrapping pass, we neglect call side effects entirely.
     */
    private void handleCallEffects(List<AJCExpressionTree> args, AJCSymbolRefTree<MethodSymbol> that, AJCEffectAnnotatedTree extra) {
        MethodSymbol calledMethod = that.getTargetSymbol();

        if (extra != null) {
            int i = 0;
            AJCEffectAnnotatedTree[] trees = new AJCEffectAnnotatedTree[args.size() + 1];
            for (AJCEffectAnnotatedTree t : args) {
                trees[i] = t;
                i++;
            }
            trees[i] = extra;

            // The effects of the arguments to the function and the method reference tree.
            that.effects = Effects.unionTrees(trees);
        } else {
            that.effects = Effects.unionTrees(args);
        }

        // Add to the list of calls needing to be fixed up.
        incompleteCalls.listAdd(calledMethod, that);
        calledMethodsWithoutSource.add(calledMethod);

        // The method being visited requires the called method to complete.
        methodDeps.listAdd(methodBeingVisited, calledMethod);
        methodDeps.ensure(calledMethod);

        // The called method is required by the method being visited.
        reverseMethodDeps.listAdd(calledMethod, methodBeingVisited);
    }

    @Override
    public void visitCall(AJCCall that) {
        super.visitCall(that);
        handleCallEffects(that.args, that, that.meth);
    }

    @Override
    public void visitNewClass(AJCNewClass that) {
        // This'll be a field initialiser...
        if (methodBeingVisited == null) {
            return;
        }

        super.visitNewClass(that);
        handleCallEffects(that.args, that, null);
    }

    @Override
    public void visitNewArray(AJCNewArray that) {
        super.visitNewArray(that);

        // A new array operation *by itself* has no side effects (Neglecting ever-present possibilities
        // such as OutOfMemoryException. It is the corresponding assignment (If any exists) which
        // has a side effect.
        // Of course, the argument to the new array call might have side effects, as might the
        // elements of the array (If given explicitly).
        Effects elementEffects = Effects.unionTrees(that.elems);
        Effects dimensionEffects = Effects.unionTrees(that.dims);

        that.effects = Effects.unionOf(elementEffects, dimensionEffects);
    }

    @Override
    public void visitAssign(AJCAssign that) {
        super.visitAssign(that);

        // The direct part of an assignment will be a subset of writes to all of the symbols read by the lhs.
        // *usually* we can narrow it down a lot more than that, but not always. Bridge methods are particularly
        // troublesome in this regard, yeilding constructs like f()[x] = y;, which defy attempts to resolve them to
        // an underlying VarSymbol.

        // Firstly, try the simple way out.
        VarSymbol directSymbol = that.getTargetSymbol();
        if (directSymbol != null) {
            that.effects = Effects.unionWithDirect(write(directSymbol), that.rhs.effects);
            return;
        }

        log.trace("Entering special assignment effects routine for {}", that);
        // TODO: This routine is almost comically inefficient.
        // Construct an EffectSet for "Writes everything read by the lhs".
        EffectSet lhsEffects = that.lhs.effects.getEffectSet();

        SymbolSet readEscaping = lhsEffects.readEscaping == SymbolSet.UNIVERSAL_SET ? SymbolSet.UNIVERSAL_SET : new SymbolSet(lhsEffects.readEscaping);
        SymbolSet readInternal = lhsEffects.readInternal == SymbolSet.UNIVERSAL_SET ? SymbolSet.UNIVERSAL_SET : new SymbolSet(lhsEffects.readInternal);

        EffectSet newEffects = new EffectSet((readEscaping.isEmpty() ? 0 : EffectType.WRITE_ESCAPING.maskValue)
                                           | (readInternal.isEmpty() ? 0 : EffectType.WRITE_INTERNAL.maskValue));

        newEffects.writeEscaping = readEscaping;
        newEffects.writeInternal = readInternal;

        that.effects = Effects.unionWithDirect(newEffects, that.rhs.effects);
    }

    @Override
    public void visitAssignop(AJCAssignOp that) {
        super.visitAssignop(that);

        VarSymbol varSym = that.getTargetSymbol();

        if (varSym != null) {
            that.effects = Effects.unionWithDirect(EffectSet.write(varSym).union(read(varSym)), that.rhs.effects);
            return;
        }


        log.trace("Entering special assignment effects routine for {}", that);
        // TODO: This routine is almost comically inefficient.
        // Similar to assign, but we also immediately *read* everything, too.
        EffectSet lhsEffects = that.lhs.effects.getEffectSet();

        SymbolSet readEscaping = lhsEffects.readEscaping == SymbolSet.UNIVERSAL_SET ? SymbolSet.UNIVERSAL_SET : new SymbolSet(lhsEffects.readEscaping);
        SymbolSet writeEscaping = lhsEffects.readEscaping == SymbolSet.UNIVERSAL_SET ? SymbolSet.UNIVERSAL_SET : new SymbolSet(lhsEffects.readEscaping);
        SymbolSet readInternal = lhsEffects.readInternal == SymbolSet.UNIVERSAL_SET ? SymbolSet.UNIVERSAL_SET : new SymbolSet(lhsEffects.readInternal);
        SymbolSet writeInternal = lhsEffects.readInternal == SymbolSet.UNIVERSAL_SET ? SymbolSet.UNIVERSAL_SET : new SymbolSet(lhsEffects.readInternal);

        EffectSet newEffects = new EffectSet((readEscaping.isEmpty() ? 0 : EffectType.WRITE_ESCAPING.maskValue
                                                                         | EffectType.READ_ESCAPING.maskValue)
                                           | (readInternal.isEmpty() ? 0 : EffectType.WRITE_INTERNAL.maskValue
                                                                         | EffectType.READ_INTERNAL.maskValue));

        newEffects.writeEscaping = writeEscaping;
        newEffects.writeInternal = writeInternal;
        newEffects.readEscaping = readEscaping;
        newEffects.readInternal = readInternal;

        that.effects = Effects.unionWithDirect(newEffects, that.rhs.effects);
    }

    @Override
    public void visitUnary(AJCUnary that) {
        super.visitUnary(that);

        that.effects = Effects.unionOf(that.arg.effects);
    }

    @Override
    public void visitUnaryAsg(AJCUnaryAsg that) {
        super.visitUnaryAsg(that);

        VarSymbol varSym = that.getTargetSymbol();
        that.effects = Effects.unionWithDirect(EffectSet.write(varSym).union(read(varSym)), that.arg.effects);
    }

    @Override
    public void visitBinary(AJCBinary that) {
        super.visitBinary(that);

        that.effects = Effects.unionTrees(that.lhs, that.rhs);
    }

    @Override
    public void visitArrayAccess(AJCArrayAccess that) {
        super.visitArrayAccess(that);

        log.trace("Visiting array access: {}", that);
        // The read effect on the underlying array will be brought in by the ident in indexed.
        // If it's a call or some other crazy thing being used to refer to the array, this also catches that.
        that.effects = Effects.unionTrees(that.indexed, that.index);
    }

    @Override
    public void visitFieldAccess(AJCFieldAccess that) {
        super.visitFieldAccess(that);

        // If it's a field access to an instance method, we read that symbol.
        Symbol targetSym  = that.getTargetSymbol();
        if (!(targetSym instanceof VarSymbol)) {
            that.effects = Effects.unionWithDirect(NO_EFFECTS, ((AJCEffectAnnotatedTree) that.selected).effects);
            return;
        }

        String hash = ClassInfo.getHashForVariable((VarSymbol) targetSym);
        if (!JOUSTCache.varSymbolTable.containsKey(hash)) {
            JOUSTCache.varSymbolTable.put(hash, (VarSymbol) targetSym);
        }

        VarSymbol tSym = (VarSymbol) targetSym;

        if (that.selected instanceof AJCEffectAnnotatedTree) {
            that.effects = Effects.unionWithDirect(read(tSym), ((AJCEffectAnnotatedTree) that.selected).effects);
        } else {
            that.effects = Effects.unionWithDirect(read(tSym));
        }
    }

    @Override
    public void visitIdent(AJCIdent that) {
        super.visitIdent(that);

        Symbol targetSym = that.getTargetSymbol();

        if (targetSym instanceof VarSymbol) {
            String hash = ClassInfo.getHashForVariable((VarSymbol) targetSym);
            if (!JOUSTCache.varSymbolTable.containsKey(hash)) {
                JOUSTCache.varSymbolTable.put(hash, (VarSymbol) targetSym);
            }

            that.effects = new Effects(NO_EFFECTS, read((VarSymbol) targetSym));
            return;
        }

        that.effects = new Effects(NO_EFFECTS);
    }

    @Override
    public void visitLiteral(AJCLiteral that) {
        super.visitLiteral(that);
        that.effects = new Effects(NO_EFFECTS);
    }

    @Override
    public void visitErroneous(AJCErroneous that) {
        super.visitErroneous(that);
        log.error("Encountered erroneous tree: {}", that);
    }

    @Override
    public void visitLetExpr(AJCLetExpr that) {
        super.visitLetExpr(that);

        Effects defEffects = Effects.unionTrees(that.defs);
        that.effects = Effects.unionOf(defEffects, that.expr.effects);
    }

    @Override
    public void visitTypeCast(AJCTypeCast that) {
        super.visitTypeCast(that);
        that.effects = Effects.unionOf(that.expr.effects);
    }

    @Override
    public void visitInstanceOf(AJCInstanceOf that) {
        super.visitInstanceOf(that);
        that.effects = Effects.unionOf(that.expr.effects);
    }

    @Override
    public void visitVariableDecl(AJCVariableDecl that) {
        super.visitVariableDecl(that);

        VarSymbol sym = that.getTargetSymbol();
        JOUSTCache.varSymbolTable.put(ClassInfo.getHashForVariable(sym), sym);

        that.effects = Effects.unionWithDirect(write(that.getTargetSymbol()), that.getInit().effects);
    }

    @Override
    public void visitAnnotation(AJCAnnotation that) {
        super.visitAnnotation(that);
        that.effects = new Effects(NO_EFFECTS);
    }
}
TOP

Related Classes of joust.analysers.sideeffects.SideEffectVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.