package org.overture.codegen.analysis.vdm; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.Vector; import org.apache.log4j.Logger; import org.overture.ast.analysis.AnalysisException; import org.overture.ast.analysis.DepthFirstAnalysisAdaptor; import org.overture.ast.definitions.AClassClassDefinition; import org.overture.ast.definitions.AExplicitFunctionDefinition; import org.overture.ast.definitions.AExplicitOperationDefinition; import org.overture.ast.definitions.AInstanceVariableDefinition; import org.overture.ast.definitions.ANamedTraceDefinition; import org.overture.ast.definitions.AStateDefinition; import org.overture.ast.definitions.ASystemClassDefinition; import org.overture.ast.definitions.ATypeDefinition; import org.overture.ast.definitions.AValueDefinition; import org.overture.ast.definitions.PDefinition; import org.overture.ast.definitions.SClassDefinition; import org.overture.ast.definitions.SFunctionDefinition; import org.overture.ast.definitions.SOperationDefinition; import org.overture.ast.definitions.traces.ALetBeStBindingTraceDefinition; import org.overture.ast.expressions.ACaseAlternative; import org.overture.ast.expressions.ACasesExp; import org.overture.ast.expressions.AExistsExp; import org.overture.ast.expressions.AForAllExp; import org.overture.ast.expressions.ALambdaExp; import org.overture.ast.expressions.ALetBeStExp; import org.overture.ast.expressions.ALetDefExp; import org.overture.ast.expressions.AMapCompMapExp; import org.overture.ast.expressions.ASetCompSetExp; import org.overture.ast.expressions.AVariableExp; import org.overture.ast.expressions.PExp; import org.overture.ast.intf.lex.ILexLocation; import org.overture.ast.intf.lex.ILexNameToken; import org.overture.ast.lex.LexNameList; import org.overture.ast.modules.AModuleModules; import org.overture.ast.node.INode; import org.overture.ast.patterns.AIdentifierPattern; import org.overture.ast.patterns.PMultipleBind; import org.overture.ast.patterns.PPattern; import org.overture.ast.statements.ABlockSimpleBlockStm; import org.overture.ast.statements.ACaseAlternativeStm; import org.overture.ast.statements.ACasesStm; import org.overture.ast.statements.AForAllStm; import org.overture.ast.statements.AForIndexStm; import org.overture.ast.statements.AForPatternBindStm; import org.overture.ast.statements.AIdentifierStateDesignator; import org.overture.ast.statements.ALetBeStStm; import org.overture.ast.statements.ALetStm; import org.overture.ast.statements.ATixeStm; import org.overture.ast.statements.ATixeStmtAlternative; import org.overture.ast.statements.ATrapStm; import org.overture.ast.statements.PStm; import org.overture.ast.typechecker.NameScope; import org.overture.ast.types.AFieldField; import org.overture.ast.types.PType; import org.overture.codegen.ir.TempVarNameGen; import org.overture.typechecker.assistant.ITypeCheckerAssistantFactory; import org.overture.typechecker.assistant.definition.SFunctionDefinitionAssistantTC; /** * This analysis is used to compute new names for variables that shadow other variables. A renaming is suggested if a * definition hides another variable or causes a duplicate definition. In addition to renaming the definition itself, * occurrences of the definition must also be renamed. Occurrences include both variable expressions (e.g. return a), * identifier state designators (e.g. x := 5) as well as identifier patterns (in mk_(a,a) the first 'a' is a definition * whereas the second 'a' is an identifier pattern occurrence of the first 'a') When computed, the renamings can be * applied in order to get rid of warnings related to hidden variables and duplicate definitions. The current version of * this analysis only considers renamings in operations and functions. This is sufficient if the VDM AST is code * generated to Java since Java allows hiding of class fields. * * @author pvj */ public class VarShadowingRenameCollector extends DepthFirstAnalysisAdaptor { private ITypeCheckerAssistantFactory af; private PDefinition enclosingDef; private Map<AIdentifierStateDesignator, PDefinition> idDefs; private Stack<ILexNameToken> localDefsInScope; private int enclosingCounter; private Set<Renaming> renamings; private Set<String> namesToAvoid; private TempVarNameGen nameGen; private Logger log = Logger.getLogger(this.getClass().getSimpleName()); public VarShadowingRenameCollector(ITypeCheckerAssistantFactory af, Map<AIdentifierStateDesignator, PDefinition> idDefs) { this.af = af; this.enclosingDef = null; this.idDefs = idDefs; this.localDefsInScope = new Stack<ILexNameToken>(); this.enclosingCounter = 0; this.renamings = new HashSet<Renaming>(); this.namesToAvoid = new HashSet<String>(); this.nameGen = new TempVarNameGen(); } @Override public void caseAModuleModules(AModuleModules node) throws AnalysisException { if (enclosingDef != null) { return; } visitModuleDefs(node.getDefs(), node); } @Override public void caseAClassClassDefinition(AClassClassDefinition node) throws AnalysisException { if (enclosingDef != null) { return; } visitModuleDefs(node.getDefinitions(), node); } @Override public void caseASystemClassDefinition(ASystemClassDefinition node) throws AnalysisException { if (enclosingDef != null) { return; } visitModuleDefs(node.getDefinitions(), node); } // For operations and functions it works as a single pattern // Thus f(1,mk_(2,2),5) will fail // public f : nat * (nat * nat) * nat -> nat // f (b,mk_(b,b), a) == b; @Override public void caseAExplicitOperationDefinition( AExplicitOperationDefinition node) throws AnalysisException { if (!proceed(node)) { return; } DefinitionInfo defInfo = new DefinitionInfo(node.getParamDefinitions(), af); openScope(defInfo, node); node.getBody().apply(this); endScope(defInfo); } @Override public void caseAExplicitFunctionDefinition( AExplicitFunctionDefinition node) throws AnalysisException { if (!proceed(node)) { return; } DefinitionInfo defInfo = new DefinitionInfo(getParamDefs(node), af); openScope(defInfo, node); node.getBody().apply(this); endScope(defInfo); } @Override public void caseABlockSimpleBlockStm(ABlockSimpleBlockStm node) throws AnalysisException { if (!proceed(node)) { return; } DefinitionInfo defInfo = new DefinitionInfo(node.getAssignmentDefs(), af); visitDefs(defInfo.getNodeDefs()); openScope(defInfo, node); visitStms(node.getStatements()); endScope(defInfo); } @Override public void caseALetDefExp(ALetDefExp node) throws AnalysisException { if (!proceed(node)) { return; } DefinitionInfo defInfo = new DefinitionInfo(node.getLocalDefs(), af); visitDefs(defInfo.getNodeDefs()); openScope(defInfo, node); node.getExpression().apply(this); endScope(defInfo); } @Override public void caseALetStm(ALetStm node) throws AnalysisException { if (!proceed(node)) { return; } DefinitionInfo defInfo = new DefinitionInfo(node.getLocalDefs(), af); visitDefs(defInfo.getNodeDefs()); openScope(defInfo, node); node.getStatement().apply(this); endScope(defInfo); } @Override public void caseALetBeStExp(ALetBeStExp node) throws AnalysisException { if (!proceed(node)) { return; } node.getDef().apply(this); DefinitionInfo defInfo = new DefinitionInfo(node.getDef().getDefs(), af); openScope(defInfo, node); if (node.getSuchThat() != null) { node.getSuchThat().apply(this); } node.getValue().apply(this); endScope(defInfo); } /* * Exists1 needs no treatment it uses only a bind */ @Override public void caseAForAllExp(AForAllExp node) throws AnalysisException { if (!proceed(node)) { return; } handleMultipleBindConstruct(node, node.getBindList(), null, node.getPredicate()); } @Override public void caseAExistsExp(AExistsExp node) throws AnalysisException { if (!proceed(node)) { return; } handleMultipleBindConstruct(node, node.getBindList(), null, node.getPredicate()); } /* * Sequence comp needs no treatment it uses only a bind */ @Override public void caseASetCompSetExp(ASetCompSetExp node) throws AnalysisException { if (!proceed(node)) { return; } handleMultipleBindConstruct(node, node.getBindings(), node.getFirst(), node.getPredicate()); } @Override public void caseAMapCompMapExp(AMapCompMapExp node) throws AnalysisException { if (!proceed(node)) { return; } handleMultipleBindConstruct(node, node.getBindings(), node.getFirst(), node.getPredicate()); } @Override public void caseALetBeStStm(ALetBeStStm node) throws AnalysisException { if (!proceed(node)) { return; } node.getDef().apply(this); DefinitionInfo defInfo = new DefinitionInfo(node.getDef().getDefs(), af); openScope(defInfo, node); if (node.getSuchThat() != null) { node.getSuchThat().apply(this); } node.getStatement().apply(this); endScope(defInfo); } @Override public void caseALetBeStBindingTraceDefinition( ALetBeStBindingTraceDefinition node) throws AnalysisException { if (!proceed(node)) { return; } node.getDef().apply(this); DefinitionInfo defInfo = new DefinitionInfo(node.getDef().getDefs(), af); openScope(defInfo, node); if (node.getStexp() != null) { node.getStexp().apply(this); } node.getBody().apply(this); endScope(defInfo); } @Override public void caseALambdaExp(ALambdaExp node) throws AnalysisException { if (!proceed(node)) { return; } DefinitionInfo defInfo = new DefinitionInfo(node.getParamDefinitions(), af); openScope(defInfo, node); node.getExpression().apply(this); endScope(defInfo); } @Override public void caseATixeStm(ATixeStm node) throws AnalysisException { if (node.getBody() != null) { node.getBody().apply(this); } // The trap alternatives will be responsible for opening/ending the scope for (ATixeStmtAlternative trap : node.getTraps()) { trap.apply(this); } } @Override public void caseATixeStmtAlternative(ATixeStmtAlternative node) throws AnalysisException { openScope(node.getPatternBind(), node.getPatternBind().getDefs(), node.getStatement()); node.getStatement().apply(this); // End scope for (PDefinition def : node.getPatternBind().getDefs()) { removeLocalDefFromScope(def); } } @Override public void caseATrapStm(ATrapStm node) throws AnalysisException { if (!proceed(node)) { return; } if (node.getBody() != null) { node.getBody().apply(this); } openScope(node.getPatternBind().getPattern(), node.getPatternBind().getDefs(), node.getWith()); if (node.getWith() != null) { node.getWith().apply(this); } for (PDefinition def : node.getPatternBind().getDefs()) { removeLocalDefFromScope(def); } } @Override public void caseAForPatternBindStm(AForPatternBindStm node) throws AnalysisException { if (!proceed(node)) { return; } if (node.getExp() != null) { node.getExp().apply(this); } openScope(node.getPatternBind().getPattern(), node.getPatternBind().getDefs(), node.getStatement()); node.getStatement().apply(this); for (PDefinition def : node.getPatternBind().getDefs()) { removeLocalDefFromScope(def); } } @Override public void caseAForAllStm(AForAllStm node) throws AnalysisException { if (!proceed(node)) { return; } if (node.getSet() != null) { node.getSet().apply(this); } PType possibleType = af.createPPatternAssistant().getPossibleType(node.getPattern()); List<PDefinition> defs = af.createPPatternAssistant().getDefinitions(node.getPattern(), possibleType, NameScope.LOCAL); for (PDefinition d : defs) { openLoop(d.getName(), node.getPattern(), node.getStatement()); } node.getStatement().apply(this); for (PDefinition def : defs) { removeLocalDefFromScope(def); } } @Override public void caseAForIndexStm(AForIndexStm node) throws AnalysisException { if (!proceed(node)) { return; } if (node.getFrom() != null) { node.getFrom().apply(this); } if (node.getTo() != null) { node.getTo().apply(this); } if (node.getBy() != null) { node.getBy().apply(this); } ILexNameToken var = node.getVar(); openLoop(var, null, node.getStatement()); node.getStatement().apply(this); localDefsInScope.remove(var); } @Override public void caseACasesStm(ACasesStm node) throws AnalysisException { if (!proceed(node)) { return; } handleCaseNode(node.getExp(), node.getCases(), node.getOthers()); } @Override public void caseACasesExp(ACasesExp node) throws AnalysisException { if (!proceed(node)) { return; } handleCaseNode(node.getExpression(), node.getCases(), node.getOthers()); } @Override public void caseACaseAlternativeStm(ACaseAlternativeStm node) throws AnalysisException { if (!proceed(node)) { return; } handleCase(node.getDefs(), node.getPattern(), node.getResult()); } @Override public void caseACaseAlternative(ACaseAlternative node) throws AnalysisException { if (!proceed(node)) { return; } handleCase(node.getDefs(), node.getPattern(), node.getResult()); } @Override public void caseILexNameToken(ILexNameToken node) throws AnalysisException { // No need to visit names } private void handleCaseNode(PExp cond, List<? extends INode> cases, INode others) throws AnalysisException { if (cond != null) { cond.apply(this); } // The cases will be responsible for opening/ending the scope for (INode c : cases) { c.apply(this); } if (others != null) { others.apply(this); } } private void openLoop(ILexNameToken var, INode varParent, PStm body) throws AnalysisException { if (!contains(var)) { localDefsInScope.add(var); } else { String newName = computeNewName(var.getName()); registerRenaming(var, newName); if (varParent != null) { Set<AIdentifierPattern> idPatterns = collectIdOccurences(var, varParent); for (AIdentifierPattern id : idPatterns) { registerRenaming(id.getName(), newName); } } Set<AVariableExp> vars = collectVarOccurences(var.getLocation(), body); for (AVariableExp varExp : vars) { registerRenaming(varExp.getName(), newName); } Set<AIdentifierStateDesignator> idStateDesignators = collectIdDesignatorOccurrences(var.getLocation(), body); for (AIdentifierStateDesignator id : idStateDesignators) { registerRenaming(id.getName(), newName); } } } private void handleCase(LinkedList<PDefinition> localDefs, PPattern pattern, INode result) throws AnalysisException { // Do not visit the conditional exp (cexp) openScope(pattern, localDefs, result); result.apply(this); // End scope for (PDefinition def : localDefs) { removeLocalDefFromScope(def); } } private void handleMultipleBindConstruct(INode node, LinkedList<PMultipleBind> bindings, PExp first, PExp pred) throws AnalysisException { DefinitionInfo defInfo = new DefinitionInfo(getMultipleBindDefs(bindings), af); openScope(defInfo, node); if (first != null) { first.apply(this); } if (pred != null) { pred.apply(this); } endScope(defInfo); } private List<PDefinition> getMultipleBindDefs(List<PMultipleBind> bindings) { List<PDefinition> defs = new Vector<PDefinition>(); for (PMultipleBind mb : bindings) { for (PPattern pattern : mb.getPlist()) { defs.addAll(af.createPPatternAssistant().getDefinitions(pattern, af.createPMultipleBindAssistant().getPossibleType(mb), NameScope.LOCAL)); } } return defs; } public String computeNewName(String original) { String prefix = original + "_"; String newNameSuggestion = nameGen.nextVarName(prefix); while (namesToAvoid.contains(newNameSuggestion)) { newNameSuggestion = nameGen.nextVarName(prefix); } namesToAvoid.add(newNameSuggestion); return newNameSuggestion; } // Note that this methods is intended to work both for SL modules and PP/RT classes private void visitModuleDefs(List<PDefinition> defs, INode module) throws AnalysisException { DefinitionInfo defInfo = getStateDefs(defs, module); if (defInfo != null) { addLocalDefs(defInfo); handleExecutables(defs); removeLocalDefs(defInfo); } else { handleExecutables(defs); } } private void handleExecutables(List<PDefinition> defs) throws AnalysisException { for (PDefinition def : defs) { if (def instanceof SOperationDefinition || def instanceof SFunctionDefinition || def instanceof ANamedTraceDefinition) { enclosingDef = def; enclosingCounter = 0; setNamesToAvoid(def); this.nameGen = new TempVarNameGen(); def.apply(this); } } } private DefinitionInfo getStateDefs(List<PDefinition> defs, INode module) { if (module instanceof AModuleModules) { List<PDefinition> fieldDefs = new LinkedList<PDefinition>(); AStateDefinition stateDef = getStateDef(defs); if (stateDef != null) { fieldDefs.addAll(findFieldDefs(stateDef.getStateDefs(), stateDef.getFields())); } for (PDefinition def : defs) { if (def instanceof AValueDefinition) { fieldDefs.add(def); } } return new DefinitionInfo(fieldDefs, af); } else if (module instanceof SClassDefinition) { SClassDefinition classDef = (SClassDefinition) module; List<PDefinition> allDefs = new LinkedList<PDefinition>(); LinkedList<PDefinition> enclosedDefs = classDef.getDefinitions(); LinkedList<PDefinition> inheritedDefs = classDef.getAllInheritedDefinitions(); allDefs.addAll(enclosedDefs); allDefs.addAll(inheritedDefs); List<PDefinition> fields = new LinkedList<PDefinition>(); for (PDefinition def : allDefs) { if (def instanceof AInstanceVariableDefinition || def instanceof AValueDefinition) { fields.add(def); } } return new DefinitionInfo(fields, af); } else { log.error("Expected module or class definition. Got: " + module); return null; } } private AStateDefinition getStateDef(List<PDefinition> defs) { for (PDefinition def : defs) { if (def instanceof AStateDefinition) { return (AStateDefinition) def; } } return null; } private List<PDefinition> findFieldDefs(List<PDefinition> stateDefs, List<AFieldField> fields) { List<PDefinition> fieldDefs = new LinkedList<PDefinition>(); for (PDefinition d : stateDefs) { for (AFieldField f : fields) { if (f.getTagname().equals(d.getName())) { fieldDefs.add(d); break; } } } return fieldDefs; } private void setNamesToAvoid(PDefinition def) throws AnalysisException { NameCollector collector = new NameCollector(); def.apply(collector); namesToAvoid = collector.namesToAvoid(); } public void init(boolean clearRenamings) { this.enclosingDef = null; this.enclosingCounter = 0; this.namesToAvoid.clear(); this.nameGen = new TempVarNameGen(); if (renamings != null && clearRenamings) { renamings.clear(); } } public Set<Renaming> getRenamings() { return renamings; } private List<PDefinition> getParamDefs(AExplicitFunctionDefinition node) { SFunctionDefinitionAssistantTC funcAssistant = this.af.createSFunctionDefinitionAssistant(); List<List<PDefinition>> paramDefs = funcAssistant.getParamDefinitions(node, node.getType(), node.getParamPatternList(), node.getLocation()); List<PDefinition> paramDefFlattened = new LinkedList<PDefinition>(); for (List<PDefinition> list : paramDefs) { paramDefFlattened.addAll(list); } return paramDefFlattened; } private boolean proceed(INode node) { if (node == enclosingDef) { enclosingCounter++; } if (enclosingCounter > 1) { // To protect against recursion return false; } PDefinition def = node.getAncestor(SOperationDefinition.class); if (def == null) { def = node.getAncestor(SFunctionDefinition.class); if (def == null) { def = node.getAncestor(ANamedTraceDefinition.class); if (def == null) { def = node.getAncestor(AValueDefinition.class); if (def == null) { def = node.getAncestor(AInstanceVariableDefinition.class); if (def == null) { def = node.getAncestor(ATypeDefinition.class); if (def == null) { def = node.getAncestor(AStateDefinition.class); } } } } } } if (def == null) { log.error("Got unexpected definition: " + enclosingDef); } return enclosingDef == def; } private void addLocalDefs(DefinitionInfo defInfo) { List<PDefinition> allLocalDefs = defInfo.getAllLocalDefs(); for (PDefinition localDef : allLocalDefs) { if (!contains(localDef)) { localDefsInScope.add(localDef.getName()); } } } private void removeLocalDefs(DefinitionInfo defInfo) { localDefsInScope.removeAll(defInfo.getAllLocalDefNames()); } public void openScope(DefinitionInfo defInfo, INode defScope) throws AnalysisException { List<? extends PDefinition> nodeDefs = defInfo.getNodeDefs(); for (int i = 0; i < nodeDefs.size(); i++) { PDefinition parentDef = nodeDefs.get(i); List<? extends PDefinition> localDefs = defInfo.getLocalDefs(parentDef); for (PDefinition localDef : localDefs) { if (contains(localDef)) { findRenamings(localDef, parentDef, defScope); } else { localDefsInScope.add(localDef.getName()); } } } } public void openScope(INode parentNode, List<PDefinition> localDefs, INode defScope) throws AnalysisException { for (PDefinition localDef : localDefs) { if (contains(localDef)) { findRenamings(localDef, parentNode, defScope); } else { localDefsInScope.add(localDef.getName()); } } } public void endScope(DefinitionInfo defInfo) { this.localDefsInScope.removeAll(defInfo.getAllLocalDefNames()); } public void removeLocalDefFromScope(PDefinition localDef) { localDefsInScope.remove(localDef.getName()); } private void findRenamings(PDefinition localDefToRename, INode parentNode, INode defScope) throws AnalysisException { ILexNameToken localDefName = getName(localDefToRename); if (localDefName == null) { return; } String newName = computeNewName(localDefName.getName()); if (!contains(localDefName.getLocation())) { registerRenaming(localDefName, newName); } Set<AVariableExp> vars = collectVarOccurences(localDefToRename.getLocation(), defScope); for (AVariableExp varExp : vars) { registerRenaming(varExp.getName(), newName); } Set<AIdentifierStateDesignator> idStateDesignators = collectIdDesignatorOccurrences(localDefToRename.getLocation(), defScope); for (AIdentifierStateDesignator id : idStateDesignators) { registerRenaming(id.getName(), newName); } Set<AIdentifierPattern> idPatterns = collectIdOccurences(localDefName, parentNode); for (AIdentifierPattern id : idPatterns) { registerRenaming(id.getName(), newName); } } private void registerRenaming(ILexNameToken name, String newName) { if (!contains(name.getLocation())) { renamings.add(new Renaming(name.getLocation(), name.getName(), newName, name.getModule(), name.getModule())); } } private boolean contains(ILexLocation loc) { for (Renaming r : renamings) { if (r.getLoc().equals(loc)) { return true; } } return false; } private Set<AVariableExp> collectVarOccurences(ILexLocation defLoc, INode defScope) throws AnalysisException { VarOccurencesCollector collector = new VarOccurencesCollector(defLoc); defScope.apply(collector); return collector.getVars(); } private Set<AIdentifierStateDesignator> collectIdDesignatorOccurrences( ILexLocation defLoc, INode defScope) throws AnalysisException { IdDesignatorOccurencesCollector collector = new IdDesignatorOccurencesCollector(defLoc, idDefs); defScope.apply(collector); return collector.getIds(); } private Set<AIdentifierPattern> collectIdOccurences(ILexNameToken name, INode parent) throws AnalysisException { IdOccurencesCollector collector = new IdOccurencesCollector(name, parent); parent.apply(collector); return collector.getIdOccurences(); } private boolean contains(PDefinition defToCheck) { ILexNameToken nameToCheck = getName(defToCheck); return contains(nameToCheck); } private boolean contains(ILexNameToken nameToCheck) { // Can be null if we try to find the name for the ignore pattern if (nameToCheck != null) { for (ILexNameToken name : localDefsInScope) { if (name != null && nameToCheck.getName().equals(name.getName())) { return true; } } } return false; } private ILexNameToken getName(PDefinition def) { LexNameList varNames = af.createPDefinitionAssistant().getVariableNames(def); if (varNames.isEmpty()) { // Can be empty if we try to find the name for the ignore pattern return null; } else { return varNames.firstElement(); } } private void visitDefs(List<? extends PDefinition> defs) throws AnalysisException { for (PDefinition def : defs) { def.apply(this); } } private void visitStms(List<? extends PStm> stms) throws AnalysisException { for (PStm stm : stms) { stm.apply(this); } } }