recoder.service.KeYCrossReferenceSourceInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of key.core Show documentation
Show all versions of key.core Show documentation
Core functionality (terms, rules, prover, ...) for deductive verification of Java programs
/* 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 extends CompilationUnit> 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