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

recoder.service.KeYCrossReferenceSourceInfo Maven / Gradle / Ivy

Go to download

Core functionality (terms, rules, prover, ...) for deductive verification of Java programs

There is a newer version: 2.12.3
Show newest version
/* This file is part of KeY - https://key-project.org
 * KeY is licensed under the GNU General Public License Version 2
 * SPDX-License-Identifier: GPL-2.0-only */
package recoder.service;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import de.uka.ilkd.key.java.recoderext.ClassFileDeclarationBuilder;
import de.uka.ilkd.key.java.recoderext.EnumClassDeclaration;
import de.uka.ilkd.key.java.recoderext.EscapeExpression;
import de.uka.ilkd.key.java.recoderext.ExecutionContext;
import de.uka.ilkd.key.java.recoderext.MethodCallStatement;
import de.uka.ilkd.key.java.recoderext.adt.AllFields;
import de.uka.ilkd.key.java.recoderext.adt.AllObjects;
import de.uka.ilkd.key.java.recoderext.adt.EmptySeqLiteral;
import de.uka.ilkd.key.java.recoderext.adt.EmptySetLiteral;
import de.uka.ilkd.key.java.recoderext.adt.Intersect;
import de.uka.ilkd.key.java.recoderext.adt.SeqConcat;
import de.uka.ilkd.key.java.recoderext.adt.SeqIndexOf;
import de.uka.ilkd.key.java.recoderext.adt.SeqLength;
import de.uka.ilkd.key.java.recoderext.adt.SeqReverse;
import de.uka.ilkd.key.java.recoderext.adt.SeqSingleton;
import de.uka.ilkd.key.java.recoderext.adt.SeqSub;
import de.uka.ilkd.key.java.recoderext.adt.SetMinus;
import de.uka.ilkd.key.java.recoderext.adt.SetUnion;
import de.uka.ilkd.key.java.recoderext.adt.Singleton;
import de.uka.ilkd.key.util.ExceptionHandlerException;
import de.uka.ilkd.key.util.SpecDataLocation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import recoder.ParserException;
import recoder.ServiceConfiguration;
import recoder.abstraction.ClassType;
import recoder.abstraction.PrimitiveType;
import recoder.abstraction.Type;
import recoder.abstraction.Variable;
import recoder.convenience.Naming;
import recoder.java.CompilationUnit;
import recoder.java.Expression;
import recoder.java.Import;
import recoder.java.ProgramElement;
import recoder.java.Statement;
import recoder.java.StatementBlock;
import recoder.java.StatementContainer;
import recoder.java.TypeScope;
import recoder.java.VariableScope;
import recoder.java.declaration.EnumConstantSpecification;
import recoder.java.declaration.EnumDeclaration;
import recoder.java.declaration.InheritanceSpecification;
import recoder.java.declaration.TypeDeclaration;
import recoder.java.declaration.TypeDeclarationContainer;
import recoder.java.declaration.VariableDeclaration;
import recoder.java.declaration.VariableSpecification;
import recoder.java.reference.TypeReference;
import recoder.java.reference.UncollatedReferenceQualifier;
import recoder.java.reference.VariableReference;
import recoder.java.statement.Case;
import recoder.list.generic.ASTList;


public class KeYCrossReferenceSourceInfo extends DefaultCrossReferenceSourceInfo {

    public static final Logger LOGGER = LoggerFactory.getLogger(KeYCrossReferenceSourceInfo.class);

    private HashMap names2vars = null;
    private PrimitiveType locsetType;
    private PrimitiveType seqType;
    private PrimitiveType freeType;
    private PrimitiveType mapType;
    private PrimitiveType bigintType;
    private PrimitiveType realType;


    public KeYCrossReferenceSourceInfo(ServiceConfiguration config) {
        super(config);
    }

    public void setNames2Vars(
            HashMap names2vars) {
        this.names2vars = names2vars;
    }

    /**
     * Called by the service configuration indicating that all services are known. Services may now
     * start communicating or linking among their configuration partners. The service configuration
     * can be memorized if it has not been passed in by a constructor already.
     *
     * @param cfg the service configuration this services has been assigned to.
     */
    public void initialize(ServiceConfiguration cfg) {
        super.initialize(cfg);
        cfg.getChangeHistory().removeChangeHistoryListener(this);
        cfg.getChangeHistory().addChangeHistoryListener(this);

        locsetType = new PrimitiveType("\\locset", this);
        seqType = new PrimitiveType("\\seq", this);
        freeType = new PrimitiveType("\\free", this);
        mapType = new PrimitiveType("\\map", this);
        bigintType = new PrimitiveType("\\bigint", this);
        realType = new PrimitiveType("\\real", this);

        // HEAP
        name2primitiveType.put(locsetType.getName(), locsetType);

        // ADTs
        name2primitiveType.put(seqType.getName(), seqType);
        name2primitiveType.put(freeType.getName(), freeType);
        name2primitiveType.put(mapType.getName(), mapType);

        // JML's primitive types
        name2primitiveType.put(bigintType.getName(), bigintType);
        name2primitiveType.put(realType.getName(), realType);
    }

    @Override
    public boolean isWidening(PrimitiveType from, PrimitiveType to) {
        // we do not handle null's
        if (from == null || to == null) {
            return false;
        }
        // equal types can be coerced
        if (from == to) {
            return true;
        }

        // These types cannot be coerced to anything else
        if (from == locsetType || from == seqType || from == freeType || from == mapType) {
            return false;
        }

        NameInfo ni = getNameInfo();
        // all smaller int types can be coerced to bigint
        if (to == bigintType) {
            return from == ni.getLongType()
                    || from == ni.getIntType()
                    || from == ni.getCharType()
                    || from == ni.getShortType()
                    || from == ni.getByteType();
        }
        // but a bigint cannot be coerced to anything else
        if (from == bigintType) {
            return false;
        }

        // all float and int types can be coerced to real
        if (to == realType) {
            return from == ni.getDoubleType()
                    || from == ni.getFloatType()
                    || from == bigintType
                    || from == ni.getLongType()
                    || from == ni.getIntType()
                    || from == ni.getCharType()
                    || from == ni.getShortType()
                    || from == ni.getByteType();
        }
        // but a real cannot be coerced to anything else
        if (from == realType) {
            return false;
        }

        return super.isWidening(from, to);
    }

    @Override
    public ClassType getBoxedType(PrimitiveType unboxedType) {
        if (unboxedType == locsetType || unboxedType == seqType || unboxedType == freeType
                || unboxedType == mapType || unboxedType == bigintType || unboxedType == realType) {
            return null;
        }
        return super.getBoxedType(unboxedType);
    }

    /**
     * Returns the class type that contains the given program element.
     *
     * @param context a program element.
     * @return the type to which the given program element belongs (may be null).
     */
    public ClassType getContainingClassType(ProgramElement context) {
        if (context instanceof TypeDeclaration) {
            context = context.getASTParent();
        }
        do {
            if (context instanceof ClassType) {
                return (ClassType) context;
            } else if (context instanceof MethodCallStatement) {
                return (ClassType) getType(
                    ((MethodCallStatement) context).getExecutionContext().getTypeReference());
            }
            context = context.getASTParent();
        } while (context != null);
        return null;
    }


    public void modelChanged(ChangeHistoryEvent event) {
        List changes = new ArrayList<>(event.getChanges());
        super.modelChanged(event);

        for (TreeChange change : changes) {
            if (change instanceof AttachChange) {
                TypeDeclarationContainer pe = change.getCompilationUnit();
                if (pe instanceof TypeDeclarationContainer) {
                    TypeDeclarationContainer tdc = pe;
                    for (int i = 0; i < tdc.getTypeDeclarationCount(); i++) {
                        ClassType ct = tdc.getTypeDeclarationAt(i);
                        for (ClassType superType : ct.getSupertypes()) {
                            registerSubtype(ct, superType);
                        }
                    }
                }
            }
        }
    }

    void registerSubtype(ClassType c1, ClassType c2) {

        try {
            super.registerSubtype(c1, c2);
        } catch (IllegalAccessError iae) {
            // eclipse uses different classloaders and they cause an exception here
            // TODO: package new recoder library with protected registerSubtype
            // and delete the exception handling code below
            eclipseWorkaroundMethodAccess(c1, c2);
        }


    }

    // Woraround for eclipse as we need to access a package private method from the superclass
    // even this class is logically in the same package, eclipse uses different classloaders
    // and will not allow this trick
    // TODO: package new recoder library with protected registerSubtype
    // and delete the exception handling code below
    private void eclipseWorkaroundMethodAccess(ClassType c1, ClassType c2) {
        try {
            Method m = DefaultProgramModelInfo.class.getDeclaredMethod("registerSubtype",
                ClassType.class, ClassType.class);
            m.setAccessible(true);
            m.invoke(this, c1, c2);
        } catch (IllegalAccessException | NoSuchMethodException | SecurityException
                | InvocationTargetException e) {
            throw (IllegalAccessError) new IllegalAccessError().initCause(e);
        }
    }

    public Variable getVariable(String name, ProgramElement context) {
        updateModel();
        // look for the next variable scope equals to or parent of context
        ProgramElement pe = context;

        // Enum constants:
        // 2 cases:
        // 1) its an original enum constant (see DefaultSourceInfo)
        // or
        // 2) its an already transformed enum class constant
        //
        // In the KeY gui, however, the references will always be qualified
        // like in "EnumName.ConstantName"

        // 1)
        if ((context instanceof VariableReference
                || context instanceof UncollatedReferenceQualifier)
                && context.getASTParent() instanceof Case && getType(((Case) context.getASTParent())
                        .getParent().getExpression()) instanceof EnumDeclaration) {
            /*
             * is it an enum constant? Possible iff: 1) parent is "case" and 2) switch-selector is
             * an enum type (that way, the selector specifies the scope!)
             */
            EnumConstantSpecification ecs = (EnumConstantSpecification) ((EnumDeclaration) getType(
                ((Case) context.getASTParent()).getParent().getExpression()))
                        .getVariableInScope(name);
            // must not resolve! qualifying enum constant in case-statements is forbidden!
            return ecs;
        }

        // 2)
        if ((context instanceof VariableReference
                || context instanceof UncollatedReferenceQualifier)
                && context.getASTParent() instanceof Case && getType(((Case) context.getASTParent())
                        .getParent().getExpression()) instanceof EnumClassDeclaration) {
            /*
             * is it an enum class constant (after transformation)? Possible iff: 1) parent is
             * "case" and 2) switch-selector is an enum type (that way, the selector specifies the
             * scope!)
             */
            EnumClassDeclaration ecd = ((EnumClassDeclaration) getType(
                ((Case) context.getASTParent()).getParent().getExpression()));
            VariableSpecification vs = ecd.getVariableInScope(name);
            // must not resolve! qualifying enum constant in case-statements is forbidden!
            return vs;
        }

        while (pe != null && !(pe instanceof VariableScope)
                && !((pe instanceof MethodCallStatement) && !(context instanceof ExecutionContext)
                        && !(context.equals(((MethodCallStatement) pe).getResultVariable())))) {
            context = pe;
            pe = pe.getASTParent();

        }
        if (pe == null) {
            // a null scope can happen if we try to find a variable
            // speculatively (for URQ resolution)
            return null;
        }
        if (pe instanceof MethodCallStatement && !(context instanceof ExecutionContext)
                && !(context.equals(((MethodCallStatement) pe).getResultVariable()))) {
            pe = getTypeDeclaration((ClassType) getType(
                ((MethodCallStatement) pe).getExecutionContext().getTypeReference()));
        }
        VariableScope scope = (VariableScope) pe;
        Variable result;
        do {
            result = scope.getVariableInScope(name);
            if (result != null) {
                // must double check this result - rare cases of confusion
                // involving field references before a local variable of the
                // same name has been specified
                if (scope instanceof StatementBlock) {
                    StatementContainer cont = (StatementBlock) scope;
                    // we need the topmost var-scope including context,
                    // or context itself if the found scope is the topmost one
                    VariableDeclaration def = ((VariableSpecification) result).getParent();
                    for (int i = 0; true; i += 1) {
                        Statement s = cont.getStatementAt(i);
                        if (s == def) {
                            // Debug.log(">>> Not ignored: " +
                            // Format.toString("%c \"%s\" @%p", result)
                            // + " for context " +
                            // Format.toString("@%p", context));

                            // stop if definition comes first
                            break;
                        }
                        if (s == context) {
                            // tricky: reference before definition - must
                            // ignore the definition :(

                            // Debug.log(">>> Ignored: " +
                            // Format.toString("%c \"%s\" @%p", result)
                            // + " for context " +
                            // Format.toString("@%p", context));

                            result = null;
                            break;
                        }
                    }
                }
                if (result != null) {
                    // leave _now_
                    break;
                }
            }
            if (scope instanceof TypeDeclaration) {
                result = getInheritedField(name, (TypeDeclaration) scope);
                if (result != null) {
                    break;
                }
                // might want to check for ambiguity of outer class fields!!!
            }
            pe = scope.getASTParent();
            while (pe != null && !(pe instanceof VariableScope)
                    && !((pe instanceof MethodCallStatement)
                            && !(context instanceof ExecutionContext))) {
                context = pe; // proceed the context
                pe = pe.getASTParent();
            }
            if (pe instanceof MethodCallStatement && !(context instanceof ExecutionContext)
                    && !(context.equals(((MethodCallStatement) pe).getResultVariable()))) {
                pe = getTypeDeclaration((ClassType) getType(
                    ((MethodCallStatement) pe).getExecutionContext().getTypeReference()));
            }
            scope = (VariableScope) pe;
        } while (scope != null);
        // we were at the compilation unit scope, leave for good now
        if (result == null && names2vars != null) {
            return names2vars.get(name);
        }
        return result;
    }

    /**
     * Tries to find a type with the given name using the given program element as context. Useful
     * to check for name clashes when introducing a new identifier. Neither name nor context may be
     * null.
     *
     * This method is identical to
     * {@link DefaultCrossReferenceSourceInfo#getType(String, ProgramElement)} but it uses
     * pe = redirectScopeNesting(pe); instead of s.getASTParent(); in
     * Recoder 0.84.
     *
     * @param name the name for the type to be looked up; may or may not be qualified.
     * @param context a program element defining the lookup context (scope).
     * @return the corresponding type (may be null).
     */
    public Type getType(String name, ProgramElement context) {

        NameInfo ni = getNameInfo();

        // check primitive types, array types of primitive types,
        // and void --- these happen often
        Type t = name2primitiveType.get(name);
        if (t != null) {
            return t;
        }

        if (name.startsWith("\\dl_")) {
            var pt = new PrimitiveType(name, this);
            name2primitiveType.put(name, pt);
            return pt;
        }

        if (name.equals("void")) {
            return null;
        }
        // catch array types
        if (name.endsWith("]")) {
            int px = name.indexOf('[');
            // compute base type
            Type baseType = getType(name.substring(0, px), context);
            if (baseType == null) {
                return null;
            }
            String indexExprs = name.substring(px);
            // the basetype exists now, so fetch a corresponding array type
            // (if there is none, the name info will create one)
            return ni.getType(baseType.getFullName() + indexExprs);
        }

        updateModel();

        // in the very special case that we are asking from the point of
        // view of a supertype reference, we must move to the enclosing unit
        // or parent type
        if (context.getASTParent() instanceof InheritanceSpecification) {
            context = context.getASTParent().getASTParent().getASTParent();
        }

        ProgramElement pe = context;
        while (pe != null && !(pe instanceof TypeScope)) {
            context = pe;
            pe = redirectScopeNesting(pe);
        }
        TypeScope scope = (TypeScope) pe;
        ClassType result = null;

        // do the scope walk
        TypeScope s = scope;
        while (s != null) {
            result = getLocalType(name, s);
            if (result != null) {
                // must double check this result - rare cases of confusion
                // involving type references before a local class of the
                // corresponding name has been specified
                if (s instanceof StatementBlock) {
                    StatementContainer cont = (StatementBlock) s;
                    for (int i = 0; true; i += 1) {
                        Statement stmt = cont.getStatementAt(i);
                        if (stmt == result) {
                            // stop if definition comes first
                            break;
                        }
                        if (stmt == context) {
                            // tricky: reference before definition - must
                            // ignore the definition :(
                            result = null;
                            break;
                        }
                    }
                }
                if (result != null) {
                    // leave _now_
                    break;
                }
            }
            if (s instanceof TypeDeclaration) {
                TypeDeclaration td = (TypeDeclaration) s;
                ClassType newResult = getInheritedType(name, td);

                if (newResult != null) {
                    result = newResult;
                    break;
                }
            }
            scope = s;
            pe = s.getASTParent();
            while (pe != null && !(pe instanceof TypeScope)) {
                context = pe;
                pe = redirectScopeNesting(pe);
            }
            s = (TypeScope) pe;
        }
        if (result != null) {
            return result;
        }

        // now the outer scope is null, so we have arrived at the top
        CompilationUnit cu = (CompilationUnit) scope;

        ASTList il = cu.getImports();
        if (il != null) {
            // first check type imports
            result = getFromTypeImports(name, il);
        }
        if (result == null) {
            // then check same package
            result = getFromUnitPackage(name, cu);
            if (result == null && il != null) {
                // then check package imports
                result = getFromPackageImports(name, il,
                    cu.getTypeDeclarationAt(0 /*
                                               * doesn't matter which one to check, since this is
                                               * important for static imports only
                                               */));
            }
        }
        if (result == null) {
            // check global types: if unqualified, attempt "java.lang.":
            // any unqualified local type would have been imported already!
            String defaultName = Naming.dot("java.lang", name);
            result = ni.getClassType(defaultName);
            if (result == null) {
                result = ni.getClassType(name);
            }
        }
        if (result != null) {
            scope.addTypeToScope(result, name); // add it to the CU scope
        }
        return result;
    }

    /**
     * redirects the nesting of scopes when a method-frame occurs
     *
     * @param scope the current scope
     * @return the new scope
     */
    private ProgramElement redirectScopeNesting(ProgramElement scope) {
        if (scope instanceof MethodCallStatement) {
            Type type =
                getType(((MethodCallStatement) scope).getExecutionContext().getTypeReference());
            if (!(type instanceof TypeDeclaration)) {
                throw new IllegalStateException(
                    "In the source section of" + "method-frame only types for which source code is "
                        + "available are supported.");
            }
            return (TypeDeclaration) getType(
                ((MethodCallStatement) scope).getExecutionContext().getTypeReference());
        } else if (scope instanceof ExecutionContext || (scope
                .getASTParent() instanceof MethodCallStatement
                && scope == ((MethodCallStatement) scope.getASTParent()).getResultVariable())) {
            scope = scope.getASTParent();
        }

        return scope.getASTParent();
    }

    /// -------------- Handling of stub class generation

    /**
     * The mapping from class names to stub compilation units.
     */
    protected final Map stubClasses =
        new LinkedHashMap<>();

    /**
     * The flag which decides on the behaviour on undefined classes
     */
    private boolean ignoreUnresolvedClasses = false;

    /**
     * Sets if unresolved classes result in an exception or lead to stubs.
     *
     * If unresolved classes are ignored, we use {@link #registerUnresolvedTypeRef(TypeReference)}
     * to create dummy stubs.
     *
     * @param ignoreUnresolvedClasses ignore unresolved classes iff true
     */
    public void setIgnoreUnresolvedClasses(boolean ignoreUnresolvedClasses) {
        this.ignoreUnresolvedClasses = ignoreUnresolvedClasses;
        if (ignoreUnresolvedClasses) {
            stubClasses.clear();
        }
    }

    /*
     * overwrite the default behaviour: if the normal lookup fails, generate a stub class, register
     * it and try to look up again. This might fail again, should never.
     *
     * @see recoder.service.DefaultSourceInfo#getType(recoder.java.reference.TypeReference)
     */
    @Override
    public Type getType(TypeReference tr) {
        try {
            return super.getType(tr);
        } catch (ExceptionHandlerException e) {
            if (ignoreUnresolvedClasses && e.getCause() instanceof UnresolvedReferenceException) {
                registerUnresolvedTypeRef(tr);
                return super.getType(tr);
            } else {
                throw e;
            }
        }
    }

    /*
     * make dummy classes for unresolved type references, store newly created classes to stubClasses
     * and register the compilation unit.
     */
    private void registerUnresolvedTypeRef(TypeReference tyref) {
        NameInfo ni = serviceConfiguration.getNameInfo();
        String typeString = Naming.toPathName(tyref);

        // bugfix: The reference might be to an array. Remove the array reference then.
        while (typeString.endsWith("[]")) {
            typeString = typeString.substring(0, typeString.length() - 2);
        }

        // look in the already created classes:
        CompilationUnit stub = stubClasses.get(typeString);
        if (stub != null) {
            throw new IllegalStateException("try to resolve an unknown type twice");
        }

        recoder.abstraction.Type ty;

        try {
            ty = ni.getType(typeString);
        } catch (UnresolvedReferenceException e) {
            // this is still an unknown type, so set ty = null
            ty = null;
        }
        if (ty == null) {
            if (!typeString.contains(".")) {
                throw new UnresolvedReferenceException(
                    "Type references to undefined classes may only appear if they are fully qualified: "
                        + tyref.toSource(),
                    tyref);
            }

            recoder.java.CompilationUnit cu;
            try {
                cu = ClassFileDeclarationBuilder.makeEmptyClassDeclaration(
                    serviceConfiguration.getProgramFactory(), typeString);
                cu.setDataLocation(new SpecDataLocation("stub", typeString));
            } catch (ParserException e) {
                throw new de.uka.ilkd.key.java.ConvertException(e);
            }

            ChangeHistory changeHistory = serviceConfiguration.getChangeHistory();
            changeHistory.attached(cu);
            changeHistory.updateModel();

            stubClasses.put(typeString, cu);
            LOGGER.debug("Dynamically created class: {}", typeString);

            register(cu);

        }
    }

    /**
     * Gets the collection of created stub classes ion their compilation units
     *
     * @return the unmodifiable collection of created compilation units
     */
    public Collection getCreatedStubClasses() {
        return stubClasses.values();
    }

    /**
     * clears the cache for the TypeReference to Type resolution. This is necessary if types are
     * added after model evalutation.
     *
     * public void clearTypeRefCache() { shit: reference2element.clear(); }
     */

    @Override
    public Type getType(Expression expr) {
        if (expr instanceof EmptySetLiteral || expr instanceof Singleton || expr instanceof SetUnion
                || expr instanceof SetMinus || expr instanceof Intersect
                || expr instanceof AllObjects || expr instanceof AllFields) {
            return name2primitiveType.get("\\locset");
        } else if (expr instanceof EmptySeqLiteral || expr instanceof SeqSingleton
                || expr instanceof SeqConcat || expr instanceof SeqSub
                || expr instanceof SeqReverse) {
            return name2primitiveType.get("\\seq");
        } else if (expr instanceof EscapeExpression) {
            // w/o further resolution, a type cannot be determined.
            // but this does not fail.
            return getNameInfo().getUnknownType();
        } else if (expr instanceof SeqLength || expr instanceof SeqIndexOf) {
            return name2primitiveType.get("\\bigint");
            // TODO: handle SeqGet
        } else {
            return super.getType(expr);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy